diff options
Diffstat (limited to 'drivers')
496 files changed, 36440 insertions, 13613 deletions
diff --git a/drivers/Kconfig b/drivers/Kconfig index 9bfb71ff3a6a..177c7d156933 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -117,4 +117,6 @@ source "drivers/staging/Kconfig" source "drivers/platform/Kconfig" source "drivers/clk/Kconfig" + +source "drivers/hwspinlock/Kconfig" endmenu diff --git a/drivers/Makefile b/drivers/Makefile index b423bb16c3a8..3f135b6fb014 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -117,3 +117,5 @@ obj-y += platform/ obj-y += ieee802154/ #common clk code obj-y += clk/ + +obj-$(CONFIG_HWSPINLOCK) += hwspinlock/ diff --git a/drivers/acpi/apei/Kconfig b/drivers/acpi/apei/Kconfig index fca34ccfd294..e91680c7e047 100644 --- a/drivers/acpi/apei/Kconfig +++ b/drivers/acpi/apei/Kconfig @@ -1,5 +1,6 @@ config ACPI_APEI bool "ACPI Platform Error Interface (APEI)" + select PSTORE depends on X86 help APEI allows to report errors (for example from the chipset) diff --git a/drivers/acpi/apei/erst.c b/drivers/acpi/apei/erst.c index cf6db6b7662a..c02005abce43 100644 --- a/drivers/acpi/apei/erst.c +++ b/drivers/acpi/apei/erst.c @@ -34,6 +34,7 @@ #include <linux/cper.h> #include <linux/nmi.h> #include <linux/hardirq.h> +#include <linux/pstore.h> #include <acpi/apei.h> #include "apei-internal.h" @@ -781,6 +782,128 @@ static int erst_check_table(struct acpi_table_erst *erst_tab) return 0; } +static size_t erst_reader(u64 *id, enum pstore_type_id *type, + struct timespec *time); +static u64 erst_writer(enum pstore_type_id type, size_t size); + +static struct pstore_info erst_info = { + .owner = THIS_MODULE, + .name = "erst", + .read = erst_reader, + .write = erst_writer, + .erase = erst_clear +}; + +#define CPER_CREATOR_PSTORE \ + UUID_LE(0x75a574e3, 0x5052, 0x4b29, 0x8a, 0x8e, 0xbe, 0x2c, \ + 0x64, 0x90, 0xb8, 0x9d) +#define CPER_SECTION_TYPE_DMESG \ + UUID_LE(0xc197e04e, 0xd545, 0x4a70, 0x9c, 0x17, 0xa5, 0x54, \ + 0x94, 0x19, 0xeb, 0x12) +#define CPER_SECTION_TYPE_MCE \ + UUID_LE(0xfe08ffbe, 0x95e4, 0x4be7, 0xbc, 0x73, 0x40, 0x96, \ + 0x04, 0x4a, 0x38, 0xfc) + +struct cper_pstore_record { + struct cper_record_header hdr; + struct cper_section_descriptor sec_hdr; + char data[]; +} __packed; + +static size_t erst_reader(u64 *id, enum pstore_type_id *type, + struct timespec *time) +{ + int rc; + ssize_t len; + unsigned long flags; + u64 record_id; + struct cper_pstore_record *rcd = (struct cper_pstore_record *) + (erst_info.buf - sizeof(*rcd)); + + if (erst_disable) + return -ENODEV; + + raw_spin_lock_irqsave(&erst_lock, flags); +skip: + rc = __erst_get_next_record_id(&record_id); + if (rc) { + raw_spin_unlock_irqrestore(&erst_lock, flags); + return rc; + } + /* no more record */ + if (record_id == APEI_ERST_INVALID_RECORD_ID) { + raw_spin_unlock_irqrestore(&erst_lock, flags); + return 0; + } + + len = __erst_read(record_id, &rcd->hdr, sizeof(*rcd) + + erst_erange.size); + if (uuid_le_cmp(rcd->hdr.creator_id, CPER_CREATOR_PSTORE) != 0) + goto skip; + raw_spin_unlock_irqrestore(&erst_lock, flags); + + *id = record_id; + if (uuid_le_cmp(rcd->sec_hdr.section_type, + CPER_SECTION_TYPE_DMESG) == 0) + *type = PSTORE_TYPE_DMESG; + else if (uuid_le_cmp(rcd->sec_hdr.section_type, + CPER_SECTION_TYPE_MCE) == 0) + *type = PSTORE_TYPE_MCE; + else + *type = PSTORE_TYPE_UNKNOWN; + + if (rcd->hdr.validation_bits & CPER_VALID_TIMESTAMP) + time->tv_sec = rcd->hdr.timestamp; + else + time->tv_sec = 0; + time->tv_nsec = 0; + + return len - sizeof(*rcd); +} + +static u64 erst_writer(enum pstore_type_id type, size_t size) +{ + struct cper_pstore_record *rcd = (struct cper_pstore_record *) + (erst_info.buf - sizeof(*rcd)); + + memset(rcd, 0, sizeof(*rcd)); + memcpy(rcd->hdr.signature, CPER_SIG_RECORD, CPER_SIG_SIZE); + rcd->hdr.revision = CPER_RECORD_REV; + rcd->hdr.signature_end = CPER_SIG_END; + rcd->hdr.section_count = 1; + rcd->hdr.error_severity = CPER_SEV_FATAL; + /* timestamp valid. platform_id, partition_id are invalid */ + rcd->hdr.validation_bits = CPER_VALID_TIMESTAMP; + rcd->hdr.timestamp = get_seconds(); + rcd->hdr.record_length = sizeof(*rcd) + size; + rcd->hdr.creator_id = CPER_CREATOR_PSTORE; + rcd->hdr.notification_type = CPER_NOTIFY_MCE; + rcd->hdr.record_id = cper_next_record_id(); + rcd->hdr.flags = CPER_HW_ERROR_FLAGS_PREVERR; + + rcd->sec_hdr.section_offset = sizeof(*rcd); + rcd->sec_hdr.section_length = size; + rcd->sec_hdr.revision = CPER_SEC_REV; + /* fru_id and fru_text is invalid */ + rcd->sec_hdr.validation_bits = 0; + rcd->sec_hdr.flags = CPER_SEC_PRIMARY; + switch (type) { + case PSTORE_TYPE_DMESG: + rcd->sec_hdr.section_type = CPER_SECTION_TYPE_DMESG; + break; + case PSTORE_TYPE_MCE: + rcd->sec_hdr.section_type = CPER_SECTION_TYPE_MCE; + break; + default: + return -EINVAL; + } + rcd->sec_hdr.section_severity = CPER_SEV_FATAL; + + erst_write(&rcd->hdr); + + return rcd->hdr.record_id; +} + static int __init erst_init(void) { int rc = 0; @@ -788,6 +911,7 @@ static int __init erst_init(void) struct apei_exec_context ctx; struct apei_resources erst_resources; struct resource *r; + char *buf; if (acpi_disabled) goto err; @@ -854,6 +978,18 @@ static int __init erst_init(void) if (!erst_erange.vaddr) goto err_release_erange; + buf = kmalloc(erst_erange.size, GFP_KERNEL); + mutex_init(&erst_info.buf_mutex); + if (buf) { + erst_info.buf = buf + sizeof(struct cper_pstore_record); + erst_info.bufsize = erst_erange.size - + sizeof(struct cper_pstore_record); + if (pstore_register(&erst_info)) { + pr_info(ERST_PFX "Could not register with persistent store\n"); + kfree(buf); + } + } + pr_info(ERST_PFX "Error Record Serialization Table (ERST) support is initialized.\n"); diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 76bbb78a5ad9..12c28f4adb67 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -98,6 +98,7 @@ struct acpi_button { struct input_dev *input; char phys[32]; /* for input device */ unsigned long pushed; + bool wakeup_enabled; }; static const struct file_operations acpi_button_info_fops = { @@ -430,8 +431,10 @@ static int acpi_button_add(struct acpi_device *device) /* Button's GPE is run-wake GPE */ acpi_enable_gpe(device->wakeup.gpe_device, device->wakeup.gpe_number); - device->wakeup.run_wake_count++; - device_set_wakeup_enable(&device->dev, true); + if (!device_may_wakeup(&device->dev)) { + device_set_wakeup_enable(&device->dev, true); + button->wakeup_enabled = true; + } } printk(KERN_INFO PREFIX "%s [%s]\n", name, acpi_device_bid(device)); @@ -453,8 +456,8 @@ static int acpi_button_remove(struct acpi_device *device, int type) if (device->wakeup.flags.valid) { acpi_disable_gpe(device->wakeup.gpe_device, device->wakeup.gpe_number); - device->wakeup.run_wake_count--; - device_set_wakeup_enable(&device->dev, false); + if (button->wakeup_enabled) + device_set_wakeup_enable(&device->dev, false); } acpi_button_remove_fs(device); diff --git a/drivers/acpi/nvs.c b/drivers/acpi/nvs.c index fa5a1df42b79..096787b43c96 100644 --- a/drivers/acpi/nvs.c +++ b/drivers/acpi/nvs.c @@ -26,6 +26,7 @@ struct nvs_page { unsigned int size; void *kaddr; void *data; + bool unmap; struct list_head node; }; @@ -44,6 +45,9 @@ int suspend_nvs_register(unsigned long start, unsigned long size) { struct nvs_page *entry, *next; + pr_info("PM: Registering ACPI NVS region at %lx (%ld bytes)\n", + start, size); + while (size > 0) { unsigned int nr_bytes; @@ -81,7 +85,13 @@ void suspend_nvs_free(void) free_page((unsigned long)entry->data); entry->data = NULL; if (entry->kaddr) { - iounmap(entry->kaddr); + if (entry->unmap) { + iounmap(entry->kaddr); + entry->unmap = false; + } else { + acpi_os_unmap_memory(entry->kaddr, + entry->size); + } entry->kaddr = NULL; } } @@ -115,8 +125,14 @@ int suspend_nvs_save(void) list_for_each_entry(entry, &nvs_list, node) if (entry->data) { - entry->kaddr = acpi_os_ioremap(entry->phys_start, - entry->size); + unsigned long phys = entry->phys_start; + unsigned int size = entry->size; + + entry->kaddr = acpi_os_get_iomem(phys, size); + if (!entry->kaddr) { + entry->kaddr = acpi_os_ioremap(phys, size); + entry->unmap = !!entry->kaddr; + } if (!entry->kaddr) { suspend_nvs_free(); return -ENOMEM; diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index c90c76aa7f8b..45c6ac8790d7 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -76,7 +76,6 @@ EXPORT_SYMBOL(acpi_in_debugger); extern char line_buf[80]; #endif /*ENABLE_DEBUGGER */ -static unsigned int acpi_irq_irq; static acpi_osd_handler acpi_irq_handler; static void *acpi_irq_context; static struct workqueue_struct *kacpid_wq; @@ -105,11 +104,11 @@ struct acpi_ioremap { void __iomem *virt; acpi_physical_address phys; acpi_size size; - struct kref ref; + unsigned long refcount; }; static LIST_HEAD(acpi_ioremaps); -static DEFINE_SPINLOCK(acpi_ioremap_lock); +static DEFINE_MUTEX(acpi_ioremap_lock); static void __init acpi_osi_setup_late(void); @@ -285,6 +284,22 @@ acpi_map_vaddr_lookup(acpi_physical_address phys, unsigned int size) return NULL; } +void __iomem *acpi_os_get_iomem(acpi_physical_address phys, unsigned int size) +{ + struct acpi_ioremap *map; + void __iomem *virt = NULL; + + mutex_lock(&acpi_ioremap_lock); + map = acpi_map_lookup(phys, size); + if (map) { + virt = map->virt + (phys - map->phys); + map->refcount++; + } + mutex_unlock(&acpi_ioremap_lock); + return virt; +} +EXPORT_SYMBOL_GPL(acpi_os_get_iomem); + /* Must be called with 'acpi_ioremap_lock' or RCU read lock held. */ static struct acpi_ioremap * acpi_map_lookup_virt(void __iomem *virt, acpi_size size) @@ -302,8 +317,7 @@ acpi_map_lookup_virt(void __iomem *virt, acpi_size size) void __iomem *__init_refok acpi_os_map_memory(acpi_physical_address phys, acpi_size size) { - struct acpi_ioremap *map, *tmp_map; - unsigned long flags; + struct acpi_ioremap *map; void __iomem *virt; acpi_physical_address pg_off; acpi_size pg_sz; @@ -316,14 +330,25 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) if (!acpi_gbl_permanent_mmap) return __acpi_map_table((unsigned long)phys, size); + mutex_lock(&acpi_ioremap_lock); + /* Check if there's a suitable mapping already. */ + map = acpi_map_lookup(phys, size); + if (map) { + map->refcount++; + goto out; + } + map = kzalloc(sizeof(*map), GFP_KERNEL); - if (!map) + if (!map) { + mutex_unlock(&acpi_ioremap_lock); return NULL; + } pg_off = round_down(phys, PAGE_SIZE); pg_sz = round_up(phys + size, PAGE_SIZE) - pg_off; virt = acpi_os_ioremap(pg_off, pg_sz); if (!virt) { + mutex_unlock(&acpi_ioremap_lock); kfree(map); return NULL; } @@ -332,62 +357,51 @@ acpi_os_map_memory(acpi_physical_address phys, acpi_size size) map->virt = virt; map->phys = pg_off; map->size = pg_sz; - kref_init(&map->ref); - - spin_lock_irqsave(&acpi_ioremap_lock, flags); - /* Check if page has already been mapped. */ - tmp_map = acpi_map_lookup(phys, size); - if (tmp_map) { - kref_get(&tmp_map->ref); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); - iounmap(map->virt); - kfree(map); - return tmp_map->virt + (phys - tmp_map->phys); - } + map->refcount = 1; + list_add_tail_rcu(&map->list, &acpi_ioremaps); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + out: + mutex_unlock(&acpi_ioremap_lock); return map->virt + (phys - map->phys); } EXPORT_SYMBOL_GPL(acpi_os_map_memory); -static void acpi_kref_del_iomap(struct kref *ref) +static void acpi_os_drop_map_ref(struct acpi_ioremap *map) { - struct acpi_ioremap *map; + if (!--map->refcount) + list_del_rcu(&map->list); +} - map = container_of(ref, struct acpi_ioremap, ref); - list_del_rcu(&map->list); +static void acpi_os_map_cleanup(struct acpi_ioremap *map) +{ + if (!map->refcount) { + synchronize_rcu(); + iounmap(map->virt); + kfree(map); + } } void __ref acpi_os_unmap_memory(void __iomem *virt, acpi_size size) { struct acpi_ioremap *map; - unsigned long flags; - int del; if (!acpi_gbl_permanent_mmap) { __acpi_unmap_table(virt, size); return; } - spin_lock_irqsave(&acpi_ioremap_lock, flags); + mutex_lock(&acpi_ioremap_lock); map = acpi_map_lookup_virt(virt, size); if (!map) { - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); - printk(KERN_ERR PREFIX "%s: bad address %p\n", __func__, virt); - dump_stack(); + mutex_unlock(&acpi_ioremap_lock); + WARN(true, PREFIX "%s: bad address %p\n", __func__, virt); return; } + acpi_os_drop_map_ref(map); + mutex_unlock(&acpi_ioremap_lock); - del = kref_put(&map->ref, acpi_kref_del_iomap); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); - - if (!del) - return; - - synchronize_rcu(); - iounmap(map->virt); - kfree(map); + acpi_os_map_cleanup(map); } EXPORT_SYMBOL_GPL(acpi_os_unmap_memory); @@ -397,7 +411,7 @@ void __init early_acpi_os_unmap_memory(void __iomem *virt, acpi_size size) __acpi_unmap_table(virt, size); } -int acpi_os_map_generic_address(struct acpi_generic_address *addr) +static int acpi_os_map_generic_address(struct acpi_generic_address *addr) { void __iomem *virt; @@ -413,13 +427,10 @@ int acpi_os_map_generic_address(struct acpi_generic_address *addr) return 0; } -EXPORT_SYMBOL_GPL(acpi_os_map_generic_address); -void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) +static void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) { - void __iomem *virt; - unsigned long flags; - acpi_size size = addr->bit_width / 8; + struct acpi_ioremap *map; if (addr->space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) return; @@ -427,13 +438,17 @@ void acpi_os_unmap_generic_address(struct acpi_generic_address *addr) if (!addr->address || !addr->bit_width) return; - spin_lock_irqsave(&acpi_ioremap_lock, flags); - virt = acpi_map_vaddr_lookup(addr->address, size); - spin_unlock_irqrestore(&acpi_ioremap_lock, flags); + mutex_lock(&acpi_ioremap_lock); + map = acpi_map_lookup(addr->address, addr->bit_width / 8); + if (!map) { + mutex_unlock(&acpi_ioremap_lock); + return; + } + acpi_os_drop_map_ref(map); + mutex_unlock(&acpi_ioremap_lock); - acpi_os_unmap_memory(virt, size); + acpi_os_map_cleanup(map); } -EXPORT_SYMBOL_GPL(acpi_os_unmap_generic_address); #ifdef ACPI_FUTURE_USAGE acpi_status @@ -516,11 +531,15 @@ acpi_os_install_interrupt_handler(u32 gsi, acpi_osd_handler handler, acpi_irq_stats_init(); /* - * Ignore the GSI from the core, and use the value in our copy of the - * FADT. It may not be the same if an interrupt source override exists - * for the SCI. + * ACPI interrupts different from the SCI in our copy of the FADT are + * not supported. */ - gsi = acpi_gbl_FADT.sci_interrupt; + if (gsi != acpi_gbl_FADT.sci_interrupt) + return AE_BAD_PARAMETER; + + if (acpi_irq_handler) + return AE_ALREADY_ACQUIRED; + if (acpi_gsi_to_irq(gsi, &irq) < 0) { printk(KERN_ERR PREFIX "SCI (ACPI GSI %d) not registered\n", gsi); @@ -531,20 +550,20 @@ acpi_os_install_interrupt_handler(u32 gsi, acpi_osd_handler handler, acpi_irq_context = context; if (request_irq(irq, acpi_irq, IRQF_SHARED, "acpi", acpi_irq)) { printk(KERN_ERR PREFIX "SCI (IRQ%d) allocation failed\n", irq); + acpi_irq_handler = NULL; return AE_NOT_ACQUIRED; } - acpi_irq_irq = irq; return AE_OK; } acpi_status acpi_os_remove_interrupt_handler(u32 irq, acpi_osd_handler handler) { - if (irq) { - free_irq(irq, acpi_irq); - acpi_irq_handler = NULL; - acpi_irq_irq = 0; - } + if (irq != acpi_gbl_FADT.sci_interrupt) + return AE_BAD_PARAMETER; + + free_irq(irq, acpi_irq); + acpi_irq_handler = NULL; return AE_OK; } @@ -1603,7 +1622,7 @@ acpi_status __init acpi_os_initialize1(void) acpi_status acpi_os_terminate(void) { if (acpi_irq_handler) { - acpi_os_remove_interrupt_handler(acpi_irq_irq, + acpi_os_remove_interrupt_handler(acpi_gbl_FADT.sci_interrupt, acpi_irq_handler); } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index b99e62494607..b136c9c1e531 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -797,7 +797,6 @@ static void acpi_bus_set_run_wake_flags(struct acpi_device *device) acpi_status status; acpi_event_status event_status; - device->wakeup.run_wake_count = 0; device->wakeup.flags.notifier_present = 0; /* Power button, Lid switch always enable wakeup */ diff --git a/drivers/acpi/sleep.c b/drivers/acpi/sleep.c index d6a8cd14de2e..84f57143ad7c 100644 --- a/drivers/acpi/sleep.c +++ b/drivers/acpi/sleep.c @@ -199,8 +199,6 @@ static void acpi_pm_end(void) #endif /* CONFIG_ACPI_SLEEP */ #ifdef CONFIG_SUSPEND -extern void do_suspend_lowlevel(void); - static u32 acpi_suspend_states[] = { [PM_SUSPEND_ON] = ACPI_STATE_S0, [PM_SUSPEND_STANDBY] = ACPI_STATE_S1, @@ -243,20 +241,11 @@ static int acpi_suspend_begin(suspend_state_t pm_state) static int acpi_suspend_enter(suspend_state_t pm_state) { acpi_status status = AE_OK; - unsigned long flags = 0; u32 acpi_state = acpi_target_sleep_state; + int error; ACPI_FLUSH_CPU_CACHE(); - /* Do arch specific saving of state. */ - if (acpi_state == ACPI_STATE_S3) { - int error = acpi_save_state_mem(); - - if (error) - return error; - } - - local_irq_save(flags); switch (acpi_state) { case ACPI_STATE_S1: barrier(); @@ -264,7 +253,10 @@ static int acpi_suspend_enter(suspend_state_t pm_state) break; case ACPI_STATE_S3: - do_suspend_lowlevel(); + error = acpi_suspend_lowlevel(); + if (error) + return error; + pr_info(PREFIX "Low-level resume complete\n"); break; } @@ -290,13 +282,6 @@ static int acpi_suspend_enter(suspend_state_t pm_state) /* Allow EC transactions to happen. */ acpi_ec_unblock_transactions_early(); - local_irq_restore(flags); - printk(KERN_DEBUG "Back to C!\n"); - - /* restore processor state */ - if (acpi_state == ACPI_STATE_S3) - acpi_restore_state_mem(); - suspend_nvs_restore(); return ACPI_SUCCESS(status) ? 0 : -EFAULT; @@ -472,16 +457,13 @@ static int acpi_hibernation_begin(void) static int acpi_hibernation_enter(void) { acpi_status status = AE_OK; - unsigned long flags = 0; ACPI_FLUSH_CPU_CACHE(); - local_irq_save(flags); /* This shouldn't return. If it returns, we have a problem */ status = acpi_enter_sleep_state(ACPI_STATE_S4); /* Reprogram control registers and execute _BFS */ acpi_leave_sleep_state_prep(ACPI_STATE_S4); - local_irq_restore(flags); return ACPI_SUCCESS(status) ? 0 : -EFAULT; } diff --git a/drivers/amba/bus.c b/drivers/amba/bus.c index e7df019d29d4..6d2bb2524b6e 100644 --- a/drivers/amba/bus.c +++ b/drivers/amba/bus.c @@ -13,16 +13,17 @@ #include <linux/string.h> #include <linux/slab.h> #include <linux/io.h> +#include <linux/pm.h> +#include <linux/pm_runtime.h> #include <linux/amba/bus.h> #include <asm/irq.h> #include <asm/sizes.h> -#define to_amba_device(d) container_of(d, struct amba_device, dev) #define to_amba_driver(d) container_of(d, struct amba_driver, drv) -static struct amba_id * -amba_lookup(struct amba_id *table, struct amba_device *dev) +static const struct amba_id * +amba_lookup(const struct amba_id *table, struct amba_device *dev) { int ret = 0; @@ -57,26 +58,6 @@ static int amba_uevent(struct device *dev, struct kobj_uevent_env *env) #define amba_uevent NULL #endif -static int amba_suspend(struct device *dev, pm_message_t state) -{ - struct amba_driver *drv = to_amba_driver(dev->driver); - int ret = 0; - - if (dev->driver && drv->suspend) - ret = drv->suspend(to_amba_device(dev), state); - return ret; -} - -static int amba_resume(struct device *dev) -{ - struct amba_driver *drv = to_amba_driver(dev->driver); - int ret = 0; - - if (dev->driver && drv->resume) - ret = drv->resume(to_amba_device(dev)); - return ret; -} - #define amba_attr_func(name,fmt,arg...) \ static ssize_t name##_show(struct device *_dev, \ struct device_attribute *attr, char *buf) \ @@ -102,17 +83,330 @@ static struct device_attribute amba_dev_attrs[] = { __ATTR_NULL, }; +#ifdef CONFIG_PM_SLEEP + +static int amba_legacy_suspend(struct device *dev, pm_message_t mesg) +{ + struct amba_driver *adrv = to_amba_driver(dev->driver); + struct amba_device *adev = to_amba_device(dev); + int ret = 0; + + if (dev->driver && adrv->suspend) + ret = adrv->suspend(adev, mesg); + + return ret; +} + +static int amba_legacy_resume(struct device *dev) +{ + struct amba_driver *adrv = to_amba_driver(dev->driver); + struct amba_device *adev = to_amba_device(dev); + int ret = 0; + + if (dev->driver && adrv->resume) + ret = adrv->resume(adev); + + return ret; +} + +static int amba_pm_prepare(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (drv && drv->pm && drv->pm->prepare) + ret = drv->pm->prepare(dev); + + return ret; +} + +static void amba_pm_complete(struct device *dev) +{ + struct device_driver *drv = dev->driver; + + if (drv && drv->pm && drv->pm->complete) + drv->pm->complete(dev); +} + +#else /* !CONFIG_PM_SLEEP */ + +#define amba_pm_prepare NULL +#define amba_pm_complete NULL + +#endif /* !CONFIG_PM_SLEEP */ + +#ifdef CONFIG_SUSPEND + +static int amba_pm_suspend(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->suspend) + ret = drv->pm->suspend(dev); + } else { + ret = amba_legacy_suspend(dev, PMSG_SUSPEND); + } + + return ret; +} + +static int amba_pm_suspend_noirq(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->suspend_noirq) + ret = drv->pm->suspend_noirq(dev); + } + + return ret; +} + +static int amba_pm_resume(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->resume) + ret = drv->pm->resume(dev); + } else { + ret = amba_legacy_resume(dev); + } + + return ret; +} + +static int amba_pm_resume_noirq(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->resume_noirq) + ret = drv->pm->resume_noirq(dev); + } + + return ret; +} + +#else /* !CONFIG_SUSPEND */ + +#define amba_pm_suspend NULL +#define amba_pm_resume NULL +#define amba_pm_suspend_noirq NULL +#define amba_pm_resume_noirq NULL + +#endif /* !CONFIG_SUSPEND */ + +#ifdef CONFIG_HIBERNATION + +static int amba_pm_freeze(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->freeze) + ret = drv->pm->freeze(dev); + } else { + ret = amba_legacy_suspend(dev, PMSG_FREEZE); + } + + return ret; +} + +static int amba_pm_freeze_noirq(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->freeze_noirq) + ret = drv->pm->freeze_noirq(dev); + } + + return ret; +} + +static int amba_pm_thaw(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->thaw) + ret = drv->pm->thaw(dev); + } else { + ret = amba_legacy_resume(dev); + } + + return ret; +} + +static int amba_pm_thaw_noirq(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->thaw_noirq) + ret = drv->pm->thaw_noirq(dev); + } + + return ret; +} + +static int amba_pm_poweroff(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->poweroff) + ret = drv->pm->poweroff(dev); + } else { + ret = amba_legacy_suspend(dev, PMSG_HIBERNATE); + } + + return ret; +} + +static int amba_pm_poweroff_noirq(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->poweroff_noirq) + ret = drv->pm->poweroff_noirq(dev); + } + + return ret; +} + +static int amba_pm_restore(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->restore) + ret = drv->pm->restore(dev); + } else { + ret = amba_legacy_resume(dev); + } + + return ret; +} + +static int amba_pm_restore_noirq(struct device *dev) +{ + struct device_driver *drv = dev->driver; + int ret = 0; + + if (!drv) + return 0; + + if (drv->pm) { + if (drv->pm->restore_noirq) + ret = drv->pm->restore_noirq(dev); + } + + return ret; +} + +#else /* !CONFIG_HIBERNATION */ + +#define amba_pm_freeze NULL +#define amba_pm_thaw NULL +#define amba_pm_poweroff NULL +#define amba_pm_restore NULL +#define amba_pm_freeze_noirq NULL +#define amba_pm_thaw_noirq NULL +#define amba_pm_poweroff_noirq NULL +#define amba_pm_restore_noirq NULL + +#endif /* !CONFIG_HIBERNATION */ + +#ifdef CONFIG_PM + +static const struct dev_pm_ops amba_pm = { + .prepare = amba_pm_prepare, + .complete = amba_pm_complete, + .suspend = amba_pm_suspend, + .resume = amba_pm_resume, + .freeze = amba_pm_freeze, + .thaw = amba_pm_thaw, + .poweroff = amba_pm_poweroff, + .restore = amba_pm_restore, + .suspend_noirq = amba_pm_suspend_noirq, + .resume_noirq = amba_pm_resume_noirq, + .freeze_noirq = amba_pm_freeze_noirq, + .thaw_noirq = amba_pm_thaw_noirq, + .poweroff_noirq = amba_pm_poweroff_noirq, + .restore_noirq = amba_pm_restore_noirq, + SET_RUNTIME_PM_OPS( + pm_generic_runtime_suspend, + pm_generic_runtime_resume, + pm_generic_runtime_idle + ) +}; + +#define AMBA_PM (&amba_pm) + +#else /* !CONFIG_PM */ + +#define AMBA_PM NULL + +#endif /* !CONFIG_PM */ + /* * Primecells are part of the Advanced Microcontroller Bus Architecture, * so we call the bus "amba". */ -static struct bus_type amba_bustype = { +struct bus_type amba_bustype = { .name = "amba", .dev_attrs = amba_dev_attrs, .match = amba_match, .uevent = amba_uevent, - .suspend = amba_suspend, - .resume = amba_resume, + .pm = AMBA_PM, }; static int __init amba_init(void) @@ -188,7 +482,7 @@ static int amba_probe(struct device *dev) { struct amba_device *pcdev = to_amba_device(dev); struct amba_driver *pcdrv = to_amba_driver(dev->driver); - struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev); + const struct amba_id *id = amba_lookup(pcdrv->id_table, pcdev); int ret; do { diff --git a/drivers/ata/Kconfig b/drivers/ata/Kconfig index c2328aed0836..8a1fc396f055 100644 --- a/drivers/ata/Kconfig +++ b/drivers/ata/Kconfig @@ -202,6 +202,18 @@ config SATA_DWC If unsure, say N. +config SATA_DWC_DEBUG + bool "Debugging driver version" + depends on SATA_DWC + help + This option enables debugging output in the driver. + +config SATA_DWC_VDEBUG + bool "Verbose debug output" + depends on SATA_DWC_DEBUG + help + This option enables the taskfile dumping and NCQ debugging. + config SATA_MV tristate "Marvell SATA support" help diff --git a/drivers/ata/ahci.c b/drivers/ata/ahci.c index b8d96ce37fc9..256d2b72cd1c 100644 --- a/drivers/ata/ahci.c +++ b/drivers/ata/ahci.c @@ -175,8 +175,7 @@ static const struct ata_port_info ahci_port_info[] = { { AHCI_HFLAGS (AHCI_HFLAG_NO_NCQ | AHCI_HFLAG_NO_MSI | AHCI_HFLAG_MV_PATA | AHCI_HFLAG_NO_PMP), - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA, + .flags = ATA_FLAG_SATA | ATA_FLAG_PIO_DMA, .pio_mask = ATA_PIO4, .udma_mask = ATA_UDMA6, .port_ops = &ahci_ops, @@ -383,6 +382,8 @@ static const struct pci_device_id ahci_pci_tbl[] = { .class = PCI_CLASS_STORAGE_SATA_AHCI, .class_mask = 0xffffff, .driver_data = board_ahci_yes_fbs }, /* 88se9128 */ + { PCI_DEVICE(0x1b4b, 0x9125), + .driver_data = board_ahci_yes_fbs }, /* 88se9125 */ /* Promise */ { PCI_VDEVICE(PROMISE, 0x3f20), board_ahci }, /* PDC42819 */ diff --git a/drivers/ata/ahci.h b/drivers/ata/ahci.h index 3e606c34f57b..ccaf08122058 100644 --- a/drivers/ata/ahci.h +++ b/drivers/ata/ahci.h @@ -213,10 +213,8 @@ enum { /* ap->flags bits */ - AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | - ATA_FLAG_ACPI_SATA | ATA_FLAG_AN | - ATA_FLAG_LPM, + AHCI_FLAG_COMMON = ATA_FLAG_SATA | ATA_FLAG_PIO_DMA | + ATA_FLAG_ACPI_SATA | ATA_FLAG_AN, ICH_MAP = 0x90, /* ICH MAP register */ diff --git a/drivers/ata/ata_generic.c b/drivers/ata/ata_generic.c index 6981f7680a00..721d38bfa339 100644 --- a/drivers/ata/ata_generic.c +++ b/drivers/ata/ata_generic.c @@ -237,7 +237,7 @@ static struct pci_device_id ata_generic[] = { #endif /* Intel, IDE class device */ { PCI_VENDOR_ID_INTEL, PCI_ANY_ID, PCI_ANY_ID, PCI_ANY_ID, - PCI_CLASS_STORAGE_IDE << 8, 0xFFFFFF00UL, + PCI_CLASS_STORAGE_IDE << 8, 0xFFFFFF00UL, .driver_data = ATA_GEN_INTEL_IDER }, /* Must come last. If you add entries adjust this table appropriately */ { PCI_DEVICE_CLASS(PCI_CLASS_STORAGE_IDE << 8, 0xFFFFFF00UL), diff --git a/drivers/ata/ata_piix.c b/drivers/ata/ata_piix.c index 6cb14ca8ee85..cdec4ab3b159 100644 --- a/drivers/ata/ata_piix.c +++ b/drivers/ata/ata_piix.c @@ -230,7 +230,7 @@ static const struct pci_device_id piix_pci_tbl[] = { { 0x8086, 0x2850, PCI_ANY_ID, PCI_ANY_ID, 0, 0, ich_pata_100 }, /* SATA ports */ - + /* 82801EB (ICH5) */ { 0x8086, 0x24d1, PCI_ANY_ID, PCI_ANY_ID, 0, 0, ich5_sata }, /* 82801EB (ICH5) */ diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 8b5ea399a4f4..a791b8ce6294 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -660,8 +660,7 @@ static int ata_acpi_filter_tf(struct ata_device *dev, * @dev: target ATA device * @gtf: raw ATA taskfile register set (0x1f1 - 0x1f7) * - * Outputs ATA taskfile to standard ATA host controller using MMIO - * or PIO as indicated by the ATA_FLAG_MMIO flag. + * Outputs ATA taskfile to standard ATA host controller. * Writes the control, feature, nsect, lbal, lbam, and lbah registers. * Optionally (ATA_TFLAG_LBA48) writes hob_feature, hob_nsect, * hob_lbal, hob_lbam, and hob_lbah. diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index d4e52e214859..b91e19cab102 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -4210,7 +4210,7 @@ static int glob_match (const char *text, const char *pattern) return 0; /* End of both strings: match */ return 1; /* No match */ } - + static unsigned long ata_dev_blacklisted(const struct ata_device *dev) { unsigned char model_num[ATA_ID_PROD_LEN + 1]; @@ -5479,7 +5479,7 @@ struct ata_port *ata_port_alloc(struct ata_host *host) ap = kzalloc(sizeof(*ap), GFP_KERNEL); if (!ap) return NULL; - + ap->pflags |= ATA_PFLAG_INITIALIZING; ap->lock = &host->lock; ap->print_id = -1; @@ -5887,21 +5887,9 @@ void ata_host_init(struct ata_host *host, struct device *dev, host->ops = ops; } - -static void async_port_probe(void *data, async_cookie_t cookie) +int ata_port_probe(struct ata_port *ap) { - int rc; - struct ata_port *ap = data; - - /* - * If we're not allowed to scan this host in parallel, - * we need to wait until all previous scans have completed - * before going further. - * Jeff Garzik says this is only within a controller, so we - * don't need to wait for port 0, only for later ports. - */ - if (!(ap->host->flags & ATA_HOST_PARALLEL_SCAN) && ap->port_no != 0) - async_synchronize_cookie(cookie); + int rc = 0; /* probe */ if (ap->ops->error_handler) { @@ -5927,23 +5915,33 @@ static void async_port_probe(void *data, async_cookie_t cookie) DPRINTK("ata%u: bus probe begin\n", ap->print_id); rc = ata_bus_probe(ap); DPRINTK("ata%u: bus probe end\n", ap->print_id); - - if (rc) { - /* FIXME: do something useful here? - * Current libata behavior will - * tear down everything when - * the module is removed - * or the h/w is unplugged. - */ - } } + return rc; +} + + +static void async_port_probe(void *data, async_cookie_t cookie) +{ + struct ata_port *ap = data; + + /* + * If we're not allowed to scan this host in parallel, + * we need to wait until all previous scans have completed + * before going further. + * Jeff Garzik says this is only within a controller, so we + * don't need to wait for port 0, only for later ports. + */ + if (!(ap->host->flags & ATA_HOST_PARALLEL_SCAN) && ap->port_no != 0) + async_synchronize_cookie(cookie); + + (void)ata_port_probe(ap); /* in order to keep device order, we need to synchronize at this point */ async_synchronize_cookie(cookie); ata_scsi_scan_host(ap, 1); - } + /** * ata_host_register - register initialized ATA host * @host: ATA host to register @@ -5983,7 +5981,7 @@ int ata_host_register(struct ata_host *host, struct scsi_host_template *sht) for (i = 0; i < host->n_ports; i++) host->ports[i]->print_id = ata_print_id++; - + /* Create associated sysfs transport objects */ for (i = 0; i < host->n_ports; i++) { rc = ata_tport_add(host->dev,host->ports[i]); @@ -6471,7 +6469,7 @@ static int __init ata_init(void) ata_sff_exit(); rc = -ENOMEM; goto err_out; - } + } printk(KERN_DEBUG "libata version " DRV_VERSION " loaded.\n"); return 0; diff --git a/drivers/ata/libata-eh.c b/drivers/ata/libata-eh.c index 17a637877d03..e02455b751ca 100644 --- a/drivers/ata/libata-eh.c +++ b/drivers/ata/libata-eh.c @@ -587,11 +587,43 @@ static void ata_eh_unload(struct ata_port *ap) void ata_scsi_error(struct Scsi_Host *host) { struct ata_port *ap = ata_shost_to_port(host); - int i; unsigned long flags; + LIST_HEAD(eh_work_q); DPRINTK("ENTER\n"); + spin_lock_irqsave(host->host_lock, flags); + list_splice_init(&host->eh_cmd_q, &eh_work_q); + spin_unlock_irqrestore(host->host_lock, flags); + + ata_scsi_cmd_error_handler(host, ap, &eh_work_q); + + /* If we timed raced normal completion and there is nothing to + recover nr_timedout == 0 why exactly are we doing error recovery ? */ + ata_scsi_port_error_handler(host, ap); + + /* finish or retry handled scmd's and clean up */ + WARN_ON(host->host_failed || !list_empty(&eh_work_q)); + + DPRINTK("EXIT\n"); +} + +/** + * ata_scsi_cmd_error_handler - error callback for a list of commands + * @host: scsi host containing the port + * @ap: ATA port within the host + * @eh_work_q: list of commands to process + * + * process the given list of commands and return those finished to the + * ap->eh_done_q. This function is the first part of the libata error + * handler which processes a given list of failed commands. + */ +void ata_scsi_cmd_error_handler(struct Scsi_Host *host, struct ata_port *ap, + struct list_head *eh_work_q) +{ + int i; + unsigned long flags; + /* make sure sff pio task is not running */ ata_sff_flush_pio_task(ap); @@ -627,7 +659,7 @@ void ata_scsi_error(struct Scsi_Host *host) if (ap->ops->lost_interrupt) ap->ops->lost_interrupt(ap); - list_for_each_entry_safe(scmd, tmp, &host->eh_cmd_q, eh_entry) { + list_for_each_entry_safe(scmd, tmp, eh_work_q, eh_entry) { struct ata_queued_cmd *qc; for (i = 0; i < ATA_MAX_QUEUE; i++) { @@ -671,8 +703,20 @@ void ata_scsi_error(struct Scsi_Host *host) } else spin_unlock_wait(ap->lock); - /* If we timed raced normal completion and there is nothing to - recover nr_timedout == 0 why exactly are we doing error recovery ? */ +} +EXPORT_SYMBOL(ata_scsi_cmd_error_handler); + +/** + * ata_scsi_port_error_handler - recover the port after the commands + * @host: SCSI host containing the port + * @ap: the ATA port + * + * Handle the recovery of the port @ap after all the commands + * have been recovered. + */ +void ata_scsi_port_error_handler(struct Scsi_Host *host, struct ata_port *ap) +{ + unsigned long flags; /* invoke error handler */ if (ap->ops->error_handler) { @@ -761,9 +805,6 @@ void ata_scsi_error(struct Scsi_Host *host) ap->ops->eng_timeout(ap); } - /* finish or retry handled scmd's and clean up */ - WARN_ON(host->host_failed || !list_empty(&host->eh_cmd_q)); - scsi_eh_flush_done_q(&ap->eh_done_q); /* clean up */ @@ -784,9 +825,8 @@ void ata_scsi_error(struct Scsi_Host *host) wake_up_all(&ap->eh_wait_q); spin_unlock_irqrestore(ap->lock, flags); - - DPRINTK("EXIT\n"); } +EXPORT_SYMBOL_GPL(ata_scsi_port_error_handler); /** * ata_port_wait_eh - Wait for the currently pending EH to complete diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index 600f6353ecf8..c11675f34b93 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -3759,7 +3759,7 @@ struct ata_port *ata_sas_port_alloc(struct ata_host *host, return NULL; ap->port_no = 0; - ap->lock = shost->host_lock; + ap->lock = &host->lock; ap->pio_mask = port_info->pio_mask; ap->mwdma_mask = port_info->mwdma_mask; ap->udma_mask = port_info->udma_mask; @@ -3821,7 +3821,7 @@ int ata_sas_port_init(struct ata_port *ap) if (!rc) { ap->print_id = ata_print_id++; - rc = ata_bus_probe(ap); + rc = ata_port_probe(ap); } return rc; diff --git a/drivers/ata/libata-sff.c b/drivers/ata/libata-sff.c index af6141bb1ba3..e75a02d61a8f 100644 --- a/drivers/ata/libata-sff.c +++ b/drivers/ata/libata-sff.c @@ -1336,7 +1336,7 @@ static void ata_sff_pio_task(struct work_struct *work) u8 status; int poll_next; - BUG_ON(ap->sff_pio_task_link == NULL); + BUG_ON(ap->sff_pio_task_link == NULL); /* qc can be NULL if timeout occurred */ qc = ata_qc_from_tag(ap, link->active_tag); if (!qc) { diff --git a/drivers/ata/libata.h b/drivers/ata/libata.h index a9be110dbf51..773de97988a2 100644 --- a/drivers/ata/libata.h +++ b/drivers/ata/libata.h @@ -103,6 +103,7 @@ extern int ata_task_ioctl(struct scsi_device *scsidev, void __user *arg); extern int ata_cmd_ioctl(struct scsi_device *scsidev, void __user *arg); extern struct ata_port *ata_port_alloc(struct ata_host *host); extern const char *sata_spd_string(unsigned int spd); +extern int ata_port_probe(struct ata_port *ap); /* libata-acpi.c */ #ifdef CONFIG_ATA_ACPI diff --git a/drivers/ata/pata_acpi.c b/drivers/ata/pata_acpi.c index c8d47034d5e9..91949d997555 100644 --- a/drivers/ata/pata_acpi.c +++ b/drivers/ata/pata_acpi.c @@ -245,7 +245,7 @@ static struct ata_port_operations pacpi_ops = { static int pacpi_init_one (struct pci_dev *pdev, const struct pci_device_id *id) { static const struct ata_port_info info = { - .flags = ATA_FLAG_SLAVE_POSS | ATA_FLAG_SRST, + .flags = ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, diff --git a/drivers/ata/pata_at32.c b/drivers/ata/pata_at32.c index 66ce6a526f27..36f189c7ee8c 100644 --- a/drivers/ata/pata_at32.c +++ b/drivers/ata/pata_at32.c @@ -194,7 +194,7 @@ static int __init pata_at32_init_one(struct device *dev, /* Setup ATA bindings */ ap->ops = &at32_port_ops; ap->pio_mask = PIO_MASK; - ap->flags |= ATA_FLAG_MMIO | ATA_FLAG_SLAVE_POSS; + ap->flags |= ATA_FLAG_SLAVE_POSS; /* * Since all 8-bit taskfile transfers has to go on the lower diff --git a/drivers/ata/pata_bf54x.c b/drivers/ata/pata_bf54x.c index 7aed5c792597..e0b58b8dfe6f 100644 --- a/drivers/ata/pata_bf54x.c +++ b/drivers/ata/pata_bf54x.c @@ -1454,9 +1454,7 @@ static struct ata_port_operations bfin_pata_ops = { static struct ata_port_info bfin_port_info[] = { { - .flags = ATA_FLAG_SLAVE_POSS - | ATA_FLAG_MMIO - | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, .mwdma_mask = 0, .udma_mask = 0, diff --git a/drivers/ata/pata_hpt3x3.c b/drivers/ata/pata_hpt3x3.c index b63d5e2d4628..24d7df81546b 100644 --- a/drivers/ata/pata_hpt3x3.c +++ b/drivers/ata/pata_hpt3x3.c @@ -151,7 +151,7 @@ static struct ata_port_operations hpt3x3_port_ops = { .check_atapi_dma= hpt3x3_atapi_dma, .freeze = hpt3x3_freeze, #endif - + }; /** diff --git a/drivers/ata/pata_it821x.c b/drivers/ata/pata_it821x.c index aa0e0c51cc08..2d15f2548a10 100644 --- a/drivers/ata/pata_it821x.c +++ b/drivers/ata/pata_it821x.c @@ -616,7 +616,7 @@ static void it821x_display_disk(int n, u8 *buf) if (buf[52] > 4) /* No Disk */ return; - ata_id_c_string((u16 *)buf, id, 0, 41); + ata_id_c_string((u16 *)buf, id, 0, 41); if (buf[51]) { mode = ffs(buf[51]); @@ -910,7 +910,7 @@ static int it821x_init_one(struct pci_dev *pdev, const struct pci_device_id *id) rc = pcim_enable_device(pdev); if (rc) return rc; - + if (pdev->vendor == PCI_VENDOR_ID_RDC) { /* Deal with Vortex86SX */ if (pdev->revision == 0x11) diff --git a/drivers/ata/pata_ixp4xx_cf.c b/drivers/ata/pata_ixp4xx_cf.c index ba54b089f98c..5253b271b3fe 100644 --- a/drivers/ata/pata_ixp4xx_cf.c +++ b/drivers/ata/pata_ixp4xx_cf.c @@ -177,7 +177,7 @@ static __devinit int ixp4xx_pata_probe(struct platform_device *pdev) ap->ops = &ixp4xx_port_ops; ap->pio_mask = ATA_PIO4; - ap->flags |= ATA_FLAG_MMIO | ATA_FLAG_NO_LEGACY | ATA_FLAG_NO_ATAPI; + ap->flags |= ATA_FLAG_NO_ATAPI; ixp4xx_setup_port(ap, data, cs0->start, cs1->start); diff --git a/drivers/ata/pata_macio.c b/drivers/ata/pata_macio.c index 75b49d01780b..46f589edccdb 100644 --- a/drivers/ata/pata_macio.c +++ b/drivers/ata/pata_macio.c @@ -1053,8 +1053,7 @@ static int __devinit pata_macio_common_init(struct pata_macio_priv *priv, /* Allocate libata host for 1 port */ memset(&pinfo, 0, sizeof(struct ata_port_info)); pmac_macio_calc_timing_masks(priv, &pinfo); - pinfo.flags = ATA_FLAG_SLAVE_POSS | ATA_FLAG_MMIO | - ATA_FLAG_NO_LEGACY; + pinfo.flags = ATA_FLAG_SLAVE_POSS; pinfo.port_ops = &pata_macio_ops; pinfo.private_data = priv; diff --git a/drivers/ata/pata_marvell.c b/drivers/ata/pata_marvell.c index dd38083dcbeb..75a6a0c0094f 100644 --- a/drivers/ata/pata_marvell.c +++ b/drivers/ata/pata_marvell.c @@ -38,7 +38,7 @@ static int marvell_pata_active(struct pci_dev *pdev) /* We don't yet know how to do this for other devices */ if (pdev->device != 0x6145) - return 1; + return 1; barp = pci_iomap(pdev, 5, 0x10); if (barp == NULL) diff --git a/drivers/ata/pata_ninja32.c b/drivers/ata/pata_ninja32.c index cc50bd09aa26..e277a142138c 100644 --- a/drivers/ata/pata_ninja32.c +++ b/drivers/ata/pata_ninja32.c @@ -165,7 +165,7 @@ static int ninja32_reinit_one(struct pci_dev *pdev) return rc; ninja32_program(host->iomap[0]); ata_host_resume(host); - return 0; + return 0; } #endif diff --git a/drivers/ata/pata_octeon_cf.c b/drivers/ata/pata_octeon_cf.c index fa1b95a9a7ff..220ddc90608f 100644 --- a/drivers/ata/pata_octeon_cf.c +++ b/drivers/ata/pata_octeon_cf.c @@ -848,8 +848,7 @@ static int __devinit octeon_cf_probe(struct platform_device *pdev) cf_port->ap = ap; ap->ops = &octeon_cf_ops; ap->pio_mask = ATA_PIO6; - ap->flags |= ATA_FLAG_MMIO | ATA_FLAG_NO_LEGACY - | ATA_FLAG_NO_ATAPI | ATA_FLAG_PIO_POLLING; + ap->flags |= ATA_FLAG_NO_ATAPI | ATA_FLAG_PIO_POLLING; base = cs0 + ocd->base_region_bias; if (!ocd->is16bit) { diff --git a/drivers/ata/pata_palmld.c b/drivers/ata/pata_palmld.c index 11fb4ccc74b4..a2a73d953840 100644 --- a/drivers/ata/pata_palmld.c +++ b/drivers/ata/pata_palmld.c @@ -85,7 +85,7 @@ static __devinit int palmld_pata_probe(struct platform_device *pdev) ap = host->ports[0]; ap->ops = &palmld_port_ops; ap->pio_mask = ATA_PIO4; - ap->flags |= ATA_FLAG_MMIO | ATA_FLAG_NO_LEGACY | ATA_FLAG_PIO_POLLING; + ap->flags |= ATA_FLAG_PIO_POLLING; /* memory mapping voodoo */ ap->ioaddr.cmd_addr = mem + 0x10; diff --git a/drivers/ata/pata_pcmcia.c b/drivers/ata/pata_pcmcia.c index 806292160b3f..29af660d968b 100644 --- a/drivers/ata/pata_pcmcia.c +++ b/drivers/ata/pata_pcmcia.c @@ -124,7 +124,7 @@ static unsigned int ata_data_xfer_8bit(struct ata_device *dev, * reset will recover the device. * */ - + static void pcmcia_8bit_drain_fifo(struct ata_queued_cmd *qc) { int count; diff --git a/drivers/ata/pata_pdc2027x.c b/drivers/ata/pata_pdc2027x.c index b18351122525..9765ace16921 100644 --- a/drivers/ata/pata_pdc2027x.c +++ b/drivers/ata/pata_pdc2027x.c @@ -150,8 +150,7 @@ static struct ata_port_operations pdc2027x_pata133_ops = { static struct ata_port_info pdc2027x_port_info[] = { /* PDC_UDMA_100 */ { - .flags = ATA_FLAG_NO_LEGACY | ATA_FLAG_SLAVE_POSS | - ATA_FLAG_MMIO, + .flags = ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA5, @@ -159,8 +158,7 @@ static struct ata_port_info pdc2027x_port_info[] = { }, /* PDC_UDMA_133 */ { - .flags = ATA_FLAG_NO_LEGACY | ATA_FLAG_SLAVE_POSS | - ATA_FLAG_MMIO, + .flags = ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, diff --git a/drivers/ata/pata_pxa.c b/drivers/ata/pata_pxa.c index 1898c6ed4b4e..b4ede40f8ae1 100644 --- a/drivers/ata/pata_pxa.c +++ b/drivers/ata/pata_pxa.c @@ -292,7 +292,6 @@ static int __devinit pxa_ata_probe(struct platform_device *pdev) ap->ops = &pxa_ata_port_ops; ap->pio_mask = ATA_PIO4; ap->mwdma_mask = ATA_MWDMA2; - ap->flags = ATA_FLAG_MMIO; ap->ioaddr.cmd_addr = devm_ioremap(&pdev->dev, cmd_res->start, resource_size(cmd_res)); diff --git a/drivers/ata/pata_rb532_cf.c b/drivers/ata/pata_rb532_cf.c index 0ffd631000b7..baeaf938d55b 100644 --- a/drivers/ata/pata_rb532_cf.c +++ b/drivers/ata/pata_rb532_cf.c @@ -91,7 +91,6 @@ static void rb532_pata_setup_ports(struct ata_host *ah) ap->ops = &rb532_pata_port_ops; ap->pio_mask = ATA_PIO4; - ap->flags = ATA_FLAG_NO_LEGACY | ATA_FLAG_MMIO; ap->ioaddr.cmd_addr = info->iobase + RB500_CF_REG_BASE; ap->ioaddr.ctl_addr = info->iobase + RB500_CF_REG_CTRL; diff --git a/drivers/ata/pata_samsung_cf.c b/drivers/ata/pata_samsung_cf.c index 8a51d673e5b2..c446ae6055a3 100644 --- a/drivers/ata/pata_samsung_cf.c +++ b/drivers/ata/pata_samsung_cf.c @@ -531,7 +531,6 @@ static int __init pata_s3c_probe(struct platform_device *pdev) } ap = host->ports[0]; - ap->flags |= ATA_FLAG_MMIO; ap->pio_mask = ATA_PIO4; if (cpu_type == TYPE_S3C64XX) { diff --git a/drivers/ata/pata_scc.c b/drivers/ata/pata_scc.c index 093715c3273a..88ea9b677b47 100644 --- a/drivers/ata/pata_scc.c +++ b/drivers/ata/pata_scc.c @@ -959,7 +959,7 @@ static struct ata_port_operations scc_pata_ops = { static struct ata_port_info scc_port_info[] = { { - .flags = ATA_FLAG_SLAVE_POSS | ATA_FLAG_MMIO | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, /* No MWDMA */ .udma_mask = ATA_UDMA6, diff --git a/drivers/ata/pata_sis.c b/drivers/ata/pata_sis.c index 60cea13cccce..c04abc393fc5 100644 --- a/drivers/ata/pata_sis.c +++ b/drivers/ata/pata_sis.c @@ -593,7 +593,7 @@ static const struct ata_port_info sis_info133 = { .port_ops = &sis_133_ops, }; const struct ata_port_info sis_info133_for_sata = { - .flags = ATA_FLAG_SLAVE_POSS | ATA_FLAG_SRST, + .flags = ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, /* No MWDMA */ .udma_mask = ATA_UDMA6, diff --git a/drivers/ata/pdc_adma.c b/drivers/ata/pdc_adma.c index adbe0426c8f0..1111712b3d7d 100644 --- a/drivers/ata/pdc_adma.c +++ b/drivers/ata/pdc_adma.c @@ -166,9 +166,7 @@ static struct ata_port_operations adma_ata_ops = { static struct ata_port_info adma_port_info[] = { /* board_1841_idx */ { - .flags = ATA_FLAG_SLAVE_POSS | - ATA_FLAG_NO_LEGACY | ATA_FLAG_MMIO | - ATA_FLAG_PIO_POLLING, + .flags = ATA_FLAG_SLAVE_POSS | ATA_FLAG_PIO_POLLING, .pio_mask = ATA_PIO4_ONLY, .udma_mask = ATA_UDMA4, .port_ops = &adma_ata_ops, diff --git a/drivers/ata/sata_dwc_460ex.c b/drivers/ata/sata_dwc_460ex.c index 6cf57c5c2b5f..712ab5a4922e 100644 --- a/drivers/ata/sata_dwc_460ex.c +++ b/drivers/ata/sata_dwc_460ex.c @@ -40,8 +40,11 @@ #include <scsi/scsi_host.h> #include <scsi/scsi_cmnd.h> +/* These two are defined in "libata.h" */ +#undef DRV_NAME +#undef DRV_VERSION #define DRV_NAME "sata-dwc" -#define DRV_VERSION "1.0" +#define DRV_VERSION "1.3" /* SATA DMA driver Globals */ #define DMA_NUM_CHANS 1 @@ -333,11 +336,47 @@ static int dma_dwc_xfer_setup(struct scatterlist *sg, int num_elems, void __iomem *addr, int dir); static void dma_dwc_xfer_start(int dma_ch); +static const char *get_prot_descript(u8 protocol) +{ + switch ((enum ata_tf_protocols)protocol) { + case ATA_PROT_NODATA: + return "ATA no data"; + case ATA_PROT_PIO: + return "ATA PIO"; + case ATA_PROT_DMA: + return "ATA DMA"; + case ATA_PROT_NCQ: + return "ATA NCQ"; + case ATAPI_PROT_NODATA: + return "ATAPI no data"; + case ATAPI_PROT_PIO: + return "ATAPI PIO"; + case ATAPI_PROT_DMA: + return "ATAPI DMA"; + default: + return "unknown"; + } +} + +static const char *get_dma_dir_descript(int dma_dir) +{ + switch ((enum dma_data_direction)dma_dir) { + case DMA_BIDIRECTIONAL: + return "bidirectional"; + case DMA_TO_DEVICE: + return "to device"; + case DMA_FROM_DEVICE: + return "from device"; + default: + return "none"; + } +} + static void sata_dwc_tf_dump(struct ata_taskfile *tf) { dev_vdbg(host_pvt.dwc_dev, "taskfile cmd: 0x%02x protocol: %s flags:" - "0x%lx device: %x\n", tf->command, ata_get_cmd_descript\ - (tf->protocol), tf->flags, tf->device); + "0x%lx device: %x\n", tf->command, + get_prot_descript(tf->protocol), tf->flags, tf->device); dev_vdbg(host_pvt.dwc_dev, "feature: 0x%02x nsect: 0x%x lbal: 0x%x " "lbam: 0x%x lbah: 0x%x\n", tf->feature, tf->nsect, tf->lbal, tf->lbam, tf->lbah); @@ -715,7 +754,7 @@ static int dma_dwc_xfer_setup(struct scatterlist *sg, int num_elems, /* Program the CTL register with src enable / dst enable */ out_le32(&(host_pvt.sata_dma_regs->chan_regs[dma_ch].ctl.low), DMA_CTL_LLP_SRCEN | DMA_CTL_LLP_DSTEN); - return 0; + return dma_ch; } /* @@ -967,7 +1006,7 @@ static irqreturn_t sata_dwc_isr(int irq, void *dev_instance) } dev_dbg(ap->dev, "%s non-NCQ cmd interrupt, protocol: %s\n", - __func__, ata_get_cmd_descript(qc->tf.protocol)); + __func__, get_prot_descript(qc->tf.protocol)); DRVSTILLBUSY: if (ata_is_dma(qc->tf.protocol)) { /* @@ -1057,7 +1096,7 @@ DRVSTILLBUSY: /* Process completed command */ dev_dbg(ap->dev, "%s NCQ command, protocol: %s\n", __func__, - ata_get_cmd_descript(qc->tf.protocol)); + get_prot_descript(qc->tf.protocol)); if (ata_is_dma(qc->tf.protocol)) { host_pvt.dma_interrupt_count++; if (hsdevp->dma_pending[tag] == \ @@ -1142,8 +1181,8 @@ static void sata_dwc_dma_xfer_complete(struct ata_port *ap, u32 check_status) if (tag > 0) { dev_info(ap->dev, "%s tag=%u cmd=0x%02x dma dir=%s proto=%s " "dmacr=0x%08x\n", __func__, qc->tag, qc->tf.command, - ata_get_cmd_descript(qc->dma_dir), - ata_get_cmd_descript(qc->tf.protocol), + get_dma_dir_descript(qc->dma_dir), + get_prot_descript(qc->tf.protocol), in_le32(&(hsdev->sata_dwc_regs->dmacr))); } #endif @@ -1354,7 +1393,7 @@ static void sata_dwc_exec_command_by_tag(struct ata_port *ap, struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap); dev_dbg(ap->dev, "%s cmd(0x%02x): %s tag=%d\n", __func__, tf->command, - ata_get_cmd_descript(tf), tag); + ata_get_cmd_descript(tf->command), tag); spin_lock_irqsave(&ap->host->lock, flags); hsdevp->cmd_issued[tag] = cmd_issued; @@ -1413,7 +1452,7 @@ static void sata_dwc_bmdma_start_by_tag(struct ata_queued_cmd *qc, u8 tag) dev_dbg(ap->dev, "%s qc=%p tag: %x cmd: 0x%02x dma_dir: %s " "start_dma? %x\n", __func__, qc, tag, qc->tf.command, - ata_get_cmd_descript(qc->dma_dir), start_dma); + get_dma_dir_descript(qc->dma_dir), start_dma); sata_dwc_tf_dump(&(qc->tf)); if (start_dma) { @@ -1462,10 +1501,9 @@ static void sata_dwc_qc_prep_by_tag(struct ata_queued_cmd *qc, u8 tag) int dma_chan; struct sata_dwc_device *hsdev = HSDEV_FROM_AP(ap); struct sata_dwc_device_port *hsdevp = HSDEVP_FROM_AP(ap); - int err; dev_dbg(ap->dev, "%s: port=%d dma dir=%s n_elem=%d\n", - __func__, ap->port_no, ata_get_cmd_descript(qc->dma_dir), + __func__, ap->port_no, get_dma_dir_descript(qc->dma_dir), qc->n_elem); dma_chan = dma_dwc_xfer_setup(sg, qc->n_elem, hsdevp->llit[tag], @@ -1474,7 +1512,7 @@ static void sata_dwc_qc_prep_by_tag(struct ata_queued_cmd *qc, u8 tag) dmadr), qc->dma_dir); if (dma_chan < 0) { dev_err(ap->dev, "%s: dma_dwc_xfer_setup returns err %d\n", - __func__, err); + __func__, dma_chan); return; } hsdevp->dma_chan[tag] = dma_chan; @@ -1491,8 +1529,8 @@ static unsigned int sata_dwc_qc_issue(struct ata_queued_cmd *qc) dev_info(ap->dev, "%s ap id=%d cmd(0x%02x)=%s qc tag=%d " "prot=%s ap active_tag=0x%08x ap sactive=0x%08x\n", __func__, ap->print_id, qc->tf.command, - ata_get_cmd_descript(&qc->tf), - qc->tag, ata_get_cmd_descript(qc->tf.protocol), + ata_get_cmd_descript(qc->tf.command), + qc->tag, get_prot_descript(qc->tf.protocol), ap->link.active_tag, ap->link.sactive); #endif @@ -1533,7 +1571,7 @@ static void sata_dwc_qc_prep(struct ata_queued_cmd *qc) #ifdef DEBUG_NCQ if (qc->tag > 0) dev_info(qc->ap->dev, "%s: qc->tag=%d ap->active_tag=0x%08x\n", - __func__, tag, qc->ap->link.active_tag); + __func__, qc->tag, qc->ap->link.active_tag); return ; #endif @@ -1580,9 +1618,8 @@ static struct ata_port_operations sata_dwc_ops = { static const struct ata_port_info sata_dwc_port_info[] = { { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_NCQ, - .pio_mask = 0x1f, /* pio 0-4 */ + .flags = ATA_FLAG_SATA | ATA_FLAG_NCQ, + .pio_mask = ATA_PIO4, .udma_mask = ATA_UDMA6, .port_ops = &sata_dwc_ops, }, diff --git a/drivers/ata/sata_fsl.c b/drivers/ata/sata_fsl.c index b0214d00d50b..beef37134a04 100644 --- a/drivers/ata/sata_fsl.c +++ b/drivers/ata/sata_fsl.c @@ -33,8 +33,7 @@ enum { SATA_FSL_MAX_PRD_USABLE = SATA_FSL_MAX_PRD - 1, SATA_FSL_MAX_PRD_DIRECT = 16, /* Direct PRDT entries */ - SATA_FSL_HOST_FLAGS = (ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | + SATA_FSL_HOST_FLAGS = (ATA_FLAG_SATA | ATA_FLAG_PIO_DMA | ATA_FLAG_PMP | ATA_FLAG_NCQ | ATA_FLAG_AN), SATA_FSL_MAX_CMDS = SATA_FSL_QUEUE_DEPTH, diff --git a/drivers/ata/sata_mv.c b/drivers/ata/sata_mv.c index bf74a36d3cc3..cd40651e9b72 100644 --- a/drivers/ata/sata_mv.c +++ b/drivers/ata/sata_mv.c @@ -160,8 +160,7 @@ enum { /* Host Flags */ MV_FLAG_DUAL_HC = (1 << 30), /* two SATA Host Controllers */ - MV_COMMON_FLAGS = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_PIO_POLLING, + MV_COMMON_FLAGS = ATA_FLAG_SATA | ATA_FLAG_PIO_POLLING, MV_GEN_I_FLAGS = MV_COMMON_FLAGS | ATA_FLAG_NO_ATAPI, diff --git a/drivers/ata/sata_nv.c b/drivers/ata/sata_nv.c index 7254e255fd78..42344e3c686d 100644 --- a/drivers/ata/sata_nv.c +++ b/drivers/ata/sata_nv.c @@ -539,7 +539,7 @@ struct nv_pi_priv { static const struct ata_port_info nv_port_info[] = { /* generic */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA, .pio_mask = NV_PIO_MASK, .mwdma_mask = NV_MWDMA_MASK, .udma_mask = NV_UDMA_MASK, @@ -548,7 +548,7 @@ static const struct ata_port_info nv_port_info[] = { }, /* nforce2/3 */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA, .pio_mask = NV_PIO_MASK, .mwdma_mask = NV_MWDMA_MASK, .udma_mask = NV_UDMA_MASK, @@ -557,7 +557,7 @@ static const struct ata_port_info nv_port_info[] = { }, /* ck804 */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA, .pio_mask = NV_PIO_MASK, .mwdma_mask = NV_MWDMA_MASK, .udma_mask = NV_UDMA_MASK, @@ -566,8 +566,7 @@ static const struct ata_port_info nv_port_info[] = { }, /* ADMA */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_NCQ, + .flags = ATA_FLAG_SATA | ATA_FLAG_NCQ, .pio_mask = NV_PIO_MASK, .mwdma_mask = NV_MWDMA_MASK, .udma_mask = NV_UDMA_MASK, @@ -576,7 +575,7 @@ static const struct ata_port_info nv_port_info[] = { }, /* MCP5x */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA, .pio_mask = NV_PIO_MASK, .mwdma_mask = NV_MWDMA_MASK, .udma_mask = NV_UDMA_MASK, @@ -585,8 +584,7 @@ static const struct ata_port_info nv_port_info[] = { }, /* SWNCQ */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_NCQ, + .flags = ATA_FLAG_SATA | ATA_FLAG_NCQ, .pio_mask = NV_PIO_MASK, .mwdma_mask = NV_MWDMA_MASK, .udma_mask = NV_UDMA_MASK, diff --git a/drivers/ata/sata_promise.c b/drivers/ata/sata_promise.c index f03ad48273ff..a004b1e0ea6d 100644 --- a/drivers/ata/sata_promise.c +++ b/drivers/ata/sata_promise.c @@ -134,9 +134,7 @@ enum { PDC_IRQ_DISABLE = (1 << 10), PDC_RESET = (1 << 11), /* HDMA reset */ - PDC_COMMON_FLAGS = ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | - ATA_FLAG_PIO_POLLING, + PDC_COMMON_FLAGS = ATA_FLAG_PIO_POLLING, /* ap->flags bits */ PDC_FLAG_GEN_II = (1 << 24), diff --git a/drivers/ata/sata_qstor.c b/drivers/ata/sata_qstor.c index daeebf19a6a9..c5603265fa58 100644 --- a/drivers/ata/sata_qstor.c +++ b/drivers/ata/sata_qstor.c @@ -155,8 +155,7 @@ static struct ata_port_operations qs_ata_ops = { static const struct ata_port_info qs_port_info[] = { /* board_2068_idx */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_PIO_POLLING, + .flags = ATA_FLAG_SATA | ATA_FLAG_PIO_POLLING, .pio_mask = ATA_PIO4_ONLY, .udma_mask = ATA_UDMA6, .port_ops = &qs_ata_ops, diff --git a/drivers/ata/sata_sil.c b/drivers/ata/sata_sil.c index 3a4f84219719..b42edaaf3a53 100644 --- a/drivers/ata/sata_sil.c +++ b/drivers/ata/sata_sil.c @@ -61,8 +61,7 @@ enum { SIL_FLAG_RERR_ON_DMA_ACT = (1 << 29), SIL_FLAG_MOD15WRITE = (1 << 30), - SIL_DFL_PORT_FLAGS = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO, + SIL_DFL_PORT_FLAGS = ATA_FLAG_SATA, /* * Controller IDs diff --git a/drivers/ata/sata_sil24.c b/drivers/ata/sata_sil24.c index af41c6fd1254..06c564e55051 100644 --- a/drivers/ata/sata_sil24.c +++ b/drivers/ata/sata_sil24.c @@ -244,8 +244,7 @@ enum { BID_SIL3131 = 2, /* host flags */ - SIL24_COMMON_FLAGS = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | + SIL24_COMMON_FLAGS = ATA_FLAG_SATA | ATA_FLAG_PIO_DMA | ATA_FLAG_NCQ | ATA_FLAG_ACPI_SATA | ATA_FLAG_AN | ATA_FLAG_PMP, SIL24_FLAG_PCIX_IRQ_WOC = (1 << 24), /* IRQ loss errata on PCI-X */ diff --git a/drivers/ata/sata_sis.c b/drivers/ata/sata_sis.c index 2bfe3ae03976..cdcc13e9cf51 100644 --- a/drivers/ata/sata_sis.c +++ b/drivers/ata/sata_sis.c @@ -96,7 +96,7 @@ static struct ata_port_operations sis_ops = { }; static const struct ata_port_info sis_port_info = { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, diff --git a/drivers/ata/sata_svw.c b/drivers/ata/sata_svw.c index 7d9db4aaf07e..35eabcf34568 100644 --- a/drivers/ata/sata_svw.c +++ b/drivers/ata/sata_svw.c @@ -359,8 +359,7 @@ static struct ata_port_operations k2_sata_ops = { static const struct ata_port_info k2_port_info[] = { /* chip_svw4 */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | K2_FLAG_NO_ATAPI_DMA, + .flags = ATA_FLAG_SATA | K2_FLAG_NO_ATAPI_DMA, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, @@ -368,8 +367,7 @@ static const struct ata_port_info k2_port_info[] = { }, /* chip_svw8 */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | K2_FLAG_NO_ATAPI_DMA | + .flags = ATA_FLAG_SATA | K2_FLAG_NO_ATAPI_DMA | K2_FLAG_SATA_8_PORTS, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, @@ -378,8 +376,7 @@ static const struct ata_port_info k2_port_info[] = { }, /* chip_svw42 */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO | K2_FLAG_BAR_POS_3, + .flags = ATA_FLAG_SATA | K2_FLAG_BAR_POS_3, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, @@ -387,8 +384,7 @@ static const struct ata_port_info k2_port_info[] = { }, /* chip_svw43 */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO, + .flags = ATA_FLAG_SATA, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, diff --git a/drivers/ata/sata_sx4.c b/drivers/ata/sata_sx4.c index bedd5188e5b0..8fd3b7252bda 100644 --- a/drivers/ata/sata_sx4.c +++ b/drivers/ata/sata_sx4.c @@ -273,9 +273,8 @@ static struct ata_port_operations pdc_20621_ops = { static const struct ata_port_info pdc_port_info[] = { /* board_20621 */ { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_SRST | ATA_FLAG_MMIO | - ATA_FLAG_NO_ATAPI | ATA_FLAG_PIO_POLLING, + .flags = ATA_FLAG_SATA | ATA_FLAG_NO_ATAPI | + ATA_FLAG_PIO_POLLING, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, diff --git a/drivers/ata/sata_uli.c b/drivers/ata/sata_uli.c index b8578c32d344..235be717a713 100644 --- a/drivers/ata/sata_uli.c +++ b/drivers/ata/sata_uli.c @@ -88,8 +88,7 @@ static struct ata_port_operations uli_ops = { }; static const struct ata_port_info uli_port_info = { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_IGN_SIMPLEX, + .flags = ATA_FLAG_SATA | ATA_FLAG_IGN_SIMPLEX, .pio_mask = ATA_PIO4, .udma_mask = ATA_UDMA6, .port_ops = &uli_ops, diff --git a/drivers/ata/sata_via.c b/drivers/ata/sata_via.c index 8b677bbf2d37..21242c5709a0 100644 --- a/drivers/ata/sata_via.c +++ b/drivers/ata/sata_via.c @@ -148,7 +148,7 @@ static struct ata_port_operations vt8251_ops = { }; static const struct ata_port_info vt6420_port_info = { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, @@ -156,7 +156,7 @@ static const struct ata_port_info vt6420_port_info = { }; static struct ata_port_info vt6421_sport_info = { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, @@ -164,7 +164,7 @@ static struct ata_port_info vt6421_sport_info = { }; static struct ata_port_info vt6421_pport_info = { - .flags = ATA_FLAG_SLAVE_POSS | ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, /* No MWDMA */ .udma_mask = ATA_UDMA6, @@ -172,8 +172,7 @@ static struct ata_port_info vt6421_pport_info = { }; static struct ata_port_info vt8251_port_info = { - .flags = ATA_FLAG_SATA | ATA_FLAG_SLAVE_POSS | - ATA_FLAG_NO_LEGACY, + .flags = ATA_FLAG_SATA | ATA_FLAG_SLAVE_POSS, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, diff --git a/drivers/ata/sata_vsc.c b/drivers/ata/sata_vsc.c index e079cf29ed5d..7c987371136e 100644 --- a/drivers/ata/sata_vsc.c +++ b/drivers/ata/sata_vsc.c @@ -340,8 +340,7 @@ static int __devinit vsc_sata_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) { static const struct ata_port_info pi = { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | - ATA_FLAG_MMIO, + .flags = ATA_FLAG_SATA, .pio_mask = ATA_PIO4, .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, diff --git a/drivers/block/floppy.c b/drivers/block/floppy.c index b9ba04fc2b34..77fc76f8aea9 100644 --- a/drivers/block/floppy.c +++ b/drivers/block/floppy.c @@ -3281,7 +3281,7 @@ static int set_geometry(unsigned int cmd, struct floppy_struct *g, struct block_device *bdev = opened_bdev[cnt]; if (!bdev || ITYPE(drive_state[cnt].fd_device) != type) continue; - __invalidate_device(bdev); + __invalidate_device(bdev, true); } mutex_unlock(&open_lock); } else { diff --git a/drivers/bluetooth/btusb.c b/drivers/bluetooth/btusb.c index 411ae9c9b384..7e0ebd4a1a74 100644 --- a/drivers/bluetooth/btusb.c +++ b/drivers/bluetooth/btusb.c @@ -1043,8 +1043,6 @@ static int btusb_probe(struct usb_interface *intf, usb_set_intfdata(intf, data); - usb_enable_autosuspend(interface_to_usbdev(intf)); - return 0; } diff --git a/drivers/char/agp/amd64-agp.c b/drivers/char/agp/amd64-agp.c index 9252e85706ef..780498d76581 100644 --- a/drivers/char/agp/amd64-agp.c +++ b/drivers/char/agp/amd64-agp.c @@ -773,18 +773,23 @@ int __init agp_amd64_init(void) #else printk(KERN_INFO PFX "You can boot with agp=try_unsupported\n"); #endif + pci_unregister_driver(&agp_amd64_pci_driver); return -ENODEV; } /* First check that we have at least one AMD64 NB */ - if (!pci_dev_present(amd_nb_misc_ids)) + if (!pci_dev_present(amd_nb_misc_ids)) { + pci_unregister_driver(&agp_amd64_pci_driver); return -ENODEV; + } /* Look for any AGP bridge */ agp_amd64_pci_driver.id_table = agp_amd64_pci_promisc_table; err = driver_attach(&agp_amd64_pci_driver.driver); - if (err == 0 && agp_bridges_found == 0) + if (err == 0 && agp_bridges_found == 0) { + pci_unregister_driver(&agp_amd64_pci_driver); err = -ENODEV; + } } return err; } diff --git a/drivers/char/agp/intel-agp.h b/drivers/char/agp/intel-agp.h index c195bfeade11..5feebe2800e9 100644 --- a/drivers/char/agp/intel-agp.h +++ b/drivers/char/agp/intel-agp.h @@ -130,6 +130,7 @@ #define INTEL_GMCH_GMS_STOLEN_352M (0xd << 4) #define I915_IFPADDR 0x60 +#define I830_HIC 0x70 /* Intel 965G registers */ #define I965_MSAC 0x62 diff --git a/drivers/char/agp/intel-gtt.c b/drivers/char/agp/intel-gtt.c index fab3d3265adb..0d09b537bb9a 100644 --- a/drivers/char/agp/intel-gtt.c +++ b/drivers/char/agp/intel-gtt.c @@ -21,6 +21,7 @@ #include <linux/kernel.h> #include <linux/pagemap.h> #include <linux/agp_backend.h> +#include <linux/delay.h> #include <asm/smp.h> #include "agp.h" #include "intel-agp.h" @@ -70,12 +71,8 @@ static struct _intel_private { u32 __iomem *gtt; /* I915G */ bool clear_fake_agp; /* on first access via agp, fill with scratch */ int num_dcache_entries; - union { - void __iomem *i9xx_flush_page; - void *i8xx_flush_page; - }; + void __iomem *i9xx_flush_page; char *i81x_gtt_table; - struct page *i8xx_page; struct resource ifp_resource; int resource_valid; struct page *scratch_page; @@ -722,28 +719,6 @@ static int intel_fake_agp_fetch_size(void) static void i830_cleanup(void) { - if (intel_private.i8xx_flush_page) { - kunmap(intel_private.i8xx_flush_page); - intel_private.i8xx_flush_page = NULL; - } - - __free_page(intel_private.i8xx_page); - intel_private.i8xx_page = NULL; -} - -static void intel_i830_setup_flush(void) -{ - /* return if we've already set the flush mechanism up */ - if (intel_private.i8xx_page) - return; - - intel_private.i8xx_page = alloc_page(GFP_KERNEL); - if (!intel_private.i8xx_page) - return; - - intel_private.i8xx_flush_page = kmap(intel_private.i8xx_page); - if (!intel_private.i8xx_flush_page) - i830_cleanup(); } /* The chipset_flush interface needs to get data that has already been @@ -758,14 +733,27 @@ static void intel_i830_setup_flush(void) */ static void i830_chipset_flush(void) { - unsigned int *pg = intel_private.i8xx_flush_page; + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + /* Forcibly evict everything from the CPU write buffers. + * clflush appears to be insufficient. + */ + wbinvd_on_all_cpus(); + + /* Now we've only seen documents for this magic bit on 855GM, + * we hope it exists for the other gen2 chipsets... + * + * Also works as advertised on my 845G. + */ + writel(readl(intel_private.registers+I830_HIC) | (1<<31), + intel_private.registers+I830_HIC); - memset(pg, 0, 1024); + while (readl(intel_private.registers+I830_HIC) & (1<<31)) { + if (time_after(jiffies, timeout)) + break; - if (cpu_has_clflush) - clflush_cache_range(pg, 1024); - else if (wbinvd_on_all_cpus() != 0) - printk(KERN_ERR "Timed out waiting for cache flush.\n"); + udelay(50); + } } static void i830_write_entry(dma_addr_t addr, unsigned int entry, @@ -849,8 +837,6 @@ static int i830_setup(void) intel_private.gtt_bus_addr = reg_addr + I810_PTE_BASE; - intel_i830_setup_flush(); - return 0; } diff --git a/drivers/char/hw_random/nomadik-rng.c b/drivers/char/hw_random/nomadik-rng.c index a348c7e9aa0b..dd1d143eb8ea 100644 --- a/drivers/char/hw_random/nomadik-rng.c +++ b/drivers/char/hw_random/nomadik-rng.c @@ -39,7 +39,7 @@ static struct hwrng nmk_rng = { .read = nmk_rng_read, }; -static int nmk_rng_probe(struct amba_device *dev, struct amba_id *id) +static int nmk_rng_probe(struct amba_device *dev, const struct amba_id *id) { void __iomem *base; int ret; diff --git a/drivers/char/pcmcia/cm4000_cs.c b/drivers/char/pcmcia/cm4000_cs.c index 777181a2e603..bcbbc71febb7 100644 --- a/drivers/char/pcmcia/cm4000_cs.c +++ b/drivers/char/pcmcia/cm4000_cs.c @@ -830,8 +830,7 @@ static void monitor_card(unsigned long p) test_bit(IS_ANY_T1, &dev->flags))) { DEBUGP(4, dev, "Perform AUTOPPS\n"); set_bit(IS_AUTOPPS_ACT, &dev->flags); - ptsreq.protocol = ptsreq.protocol = - (0x01 << dev->proto); + ptsreq.protocol = (0x01 << dev->proto); ptsreq.flags = 0x01; ptsreq.pts1 = 0x00; ptsreq.pts2 = 0x00; diff --git a/drivers/char/pcmcia/ipwireless/main.c b/drivers/char/pcmcia/ipwireless/main.c index 94b8eb4d691d..444155a305ae 100644 --- a/drivers/char/pcmcia/ipwireless/main.c +++ b/drivers/char/pcmcia/ipwireless/main.c @@ -78,7 +78,6 @@ static void signalled_reboot_callback(void *callback_data) static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data) { struct ipw_dev *ipw = priv_data; - struct resource *io_resource; int ret; p_dev->resource[0]->flags &= ~IO_DATA_PATH_WIDTH; @@ -92,9 +91,12 @@ static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data) if (ret) return ret; - io_resource = request_region(p_dev->resource[0]->start, - resource_size(p_dev->resource[0]), - IPWIRELESS_PCCARD_NAME); + if (!request_region(p_dev->resource[0]->start, + resource_size(p_dev->resource[0]), + IPWIRELESS_PCCARD_NAME)) { + ret = -EBUSY; + goto exit; + } p_dev->resource[2]->flags |= WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_CM | WIN_ENABLE; @@ -105,22 +107,25 @@ static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data) ret = pcmcia_map_mem_page(p_dev, p_dev->resource[2], p_dev->card_addr); if (ret != 0) - goto exit2; + goto exit1; ipw->is_v2_card = resource_size(p_dev->resource[2]) == 0x100; - ipw->attr_memory = ioremap(p_dev->resource[2]->start, + ipw->common_memory = ioremap(p_dev->resource[2]->start, resource_size(p_dev->resource[2])); - request_mem_region(p_dev->resource[2]->start, - resource_size(p_dev->resource[2]), - IPWIRELESS_PCCARD_NAME); + if (!request_mem_region(p_dev->resource[2]->start, + resource_size(p_dev->resource[2]), + IPWIRELESS_PCCARD_NAME)) { + ret = -EBUSY; + goto exit2; + } p_dev->resource[3]->flags |= WIN_DATA_WIDTH_16 | WIN_MEMORY_TYPE_AM | WIN_ENABLE; p_dev->resource[3]->end = 0; /* this used to be 0x1000 */ ret = pcmcia_request_window(p_dev, p_dev->resource[3], 0); if (ret != 0) - goto exit2; + goto exit3; ret = pcmcia_map_mem_page(p_dev, p_dev->resource[3], 0); if (ret != 0) @@ -128,23 +133,28 @@ static int ipwireless_probe(struct pcmcia_device *p_dev, void *priv_data) ipw->attr_memory = ioremap(p_dev->resource[3]->start, resource_size(p_dev->resource[3])); - request_mem_region(p_dev->resource[3]->start, - resource_size(p_dev->resource[3]), - IPWIRELESS_PCCARD_NAME); + if (!request_mem_region(p_dev->resource[3]->start, + resource_size(p_dev->resource[3]), + IPWIRELESS_PCCARD_NAME)) { + ret = -EBUSY; + goto exit4; + } return 0; +exit4: + iounmap(ipw->attr_memory); exit3: + release_mem_region(p_dev->resource[2]->start, + resource_size(p_dev->resource[2])); exit2: - if (ipw->common_memory) { - release_mem_region(p_dev->resource[2]->start, - resource_size(p_dev->resource[2])); - iounmap(ipw->common_memory); - } + iounmap(ipw->common_memory); exit1: - release_resource(io_resource); + release_region(p_dev->resource[0]->start, + resource_size(p_dev->resource[0])); +exit: pcmcia_disable_device(p_dev); - return -1; + return ret; } static int config_ipwireless(struct ipw_dev *ipw) @@ -219,6 +229,8 @@ exit: static void release_ipwireless(struct ipw_dev *ipw) { + release_region(ipw->link->resource[0]->start, + resource_size(ipw->link->resource[0])); if (ipw->common_memory) { release_mem_region(ipw->link->resource[2]->start, resource_size(ipw->link->resource[2])); diff --git a/drivers/char/tpm/tpm.c b/drivers/char/tpm/tpm.c index faf5a2c65926..36e0fa161c2b 100644 --- a/drivers/char/tpm/tpm.c +++ b/drivers/char/tpm/tpm.c @@ -577,11 +577,9 @@ duration: if (rc) return; - if (be32_to_cpu(tpm_cmd.header.out.return_code) != 0 || - be32_to_cpu(tpm_cmd.header.out.length) - != sizeof(tpm_cmd.header.out) + sizeof(u32) + 3 * sizeof(u32)) + if (be32_to_cpu(tpm_cmd.header.out.return_code) + != 3 * sizeof(u32)) return; - duration_cap = &tpm_cmd.params.getcap_out.cap.duration; chip->vendor.duration[TPM_SHORT] = usecs_to_jiffies(be32_to_cpu(duration_cap->tpm_short)); @@ -941,18 +939,6 @@ ssize_t tpm_show_caps_1_2(struct device * dev, } EXPORT_SYMBOL_GPL(tpm_show_caps_1_2); -ssize_t tpm_show_timeouts(struct device *dev, struct device_attribute *attr, - char *buf) -{ - struct tpm_chip *chip = dev_get_drvdata(dev); - - return sprintf(buf, "%d %d %d\n", - jiffies_to_usecs(chip->vendor.duration[TPM_SHORT]), - jiffies_to_usecs(chip->vendor.duration[TPM_MEDIUM]), - jiffies_to_usecs(chip->vendor.duration[TPM_LONG])); -} -EXPORT_SYMBOL_GPL(tpm_show_timeouts); - ssize_t tpm_store_cancel(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index d84ff772c26f..72ddb031b69a 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -56,8 +56,6 @@ extern ssize_t tpm_show_owned(struct device *, struct device_attribute *attr, char *); extern ssize_t tpm_show_temp_deactivated(struct device *, struct device_attribute *attr, char *); -extern ssize_t tpm_show_timeouts(struct device *, - struct device_attribute *attr, char *); struct tpm_chip; diff --git a/drivers/char/tpm/tpm_tis.c b/drivers/char/tpm/tpm_tis.c index 0d1d38e5f266..dd21df55689d 100644 --- a/drivers/char/tpm/tpm_tis.c +++ b/drivers/char/tpm/tpm_tis.c @@ -376,7 +376,6 @@ static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated, NULL); static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps_1_2, NULL); static DEVICE_ATTR(cancel, S_IWUSR | S_IWGRP, NULL, tpm_store_cancel); -static DEVICE_ATTR(timeouts, S_IRUGO, tpm_show_timeouts, NULL); static struct attribute *tis_attrs[] = { &dev_attr_pubek.attr, @@ -386,8 +385,7 @@ static struct attribute *tis_attrs[] = { &dev_attr_owned.attr, &dev_attr_temp_deactivated.attr, &dev_attr_caps.attr, - &dev_attr_cancel.attr, - &dev_attr_timeouts.attr, NULL, + &dev_attr_cancel.attr, NULL, }; static struct attribute_group tis_attr_grp = { diff --git a/drivers/dma/amba-pl08x.c b/drivers/dma/amba-pl08x.c index 07bca4970e50..e6d7228b1479 100644 --- a/drivers/dma/amba-pl08x.c +++ b/drivers/dma/amba-pl08x.c @@ -1845,7 +1845,7 @@ static inline void init_pl08x_debugfs(struct pl08x_driver_data *pl08x) } #endif -static int pl08x_probe(struct amba_device *adev, struct amba_id *id) +static int pl08x_probe(struct amba_device *adev, const struct amba_id *id) { struct pl08x_driver_data *pl08x; const struct vendor_data *vd = id->data; diff --git a/drivers/dma/dw_dmac.c b/drivers/dma/dw_dmac.c index a3991ab0d67e..08dab3badad2 100644 --- a/drivers/dma/dw_dmac.c +++ b/drivers/dma/dw_dmac.c @@ -32,15 +32,18 @@ * which does not support descriptor writeback. */ -/* NOTE: DMS+SMS is system-specific. We should get this information - * from the platform code somehow. - */ -#define DWC_DEFAULT_CTLLO (DWC_CTLL_DST_MSIZE(0) \ - | DWC_CTLL_SRC_MSIZE(0) \ - | DWC_CTLL_DMS(0) \ - | DWC_CTLL_SMS(1) \ - | DWC_CTLL_LLP_D_EN \ - | DWC_CTLL_LLP_S_EN) +#define DWC_DEFAULT_CTLLO(private) ({ \ + struct dw_dma_slave *__slave = (private); \ + int dms = __slave ? __slave->dst_master : 0; \ + int sms = __slave ? __slave->src_master : 1; \ + \ + (DWC_CTLL_DST_MSIZE(0) \ + | DWC_CTLL_SRC_MSIZE(0) \ + | DWC_CTLL_LLP_D_EN \ + | DWC_CTLL_LLP_S_EN \ + | DWC_CTLL_DMS(dms) \ + | DWC_CTLL_SMS(sms)); \ + }) /* * This is configuration-dependent and usually a funny size like 4095. @@ -291,6 +294,9 @@ static void dwc_scan_descriptors(struct dw_dma *dw, struct dw_dma_chan *dwc) return; } + if (list_empty(&dwc->active_list)) + return; + dev_vdbg(chan2dev(&dwc->chan), "scan_descriptors: llp=0x%x\n", llp); list_for_each_entry_safe(desc, _desc, &dwc->active_list, desc_node) { @@ -588,7 +594,7 @@ dwc_prep_dma_memcpy(struct dma_chan *chan, dma_addr_t dest, dma_addr_t src, else src_width = dst_width = 0; - ctllo = DWC_DEFAULT_CTLLO + ctllo = DWC_DEFAULT_CTLLO(chan->private) | DWC_CTLL_DST_WIDTH(dst_width) | DWC_CTLL_SRC_WIDTH(src_width) | DWC_CTLL_DST_INC @@ -669,7 +675,7 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, switch (direction) { case DMA_TO_DEVICE: - ctllo = (DWC_DEFAULT_CTLLO + ctllo = (DWC_DEFAULT_CTLLO(chan->private) | DWC_CTLL_DST_WIDTH(reg_width) | DWC_CTLL_DST_FIX | DWC_CTLL_SRC_INC @@ -714,7 +720,7 @@ dwc_prep_slave_sg(struct dma_chan *chan, struct scatterlist *sgl, } break; case DMA_FROM_DEVICE: - ctllo = (DWC_DEFAULT_CTLLO + ctllo = (DWC_DEFAULT_CTLLO(chan->private) | DWC_CTLL_SRC_WIDTH(reg_width) | DWC_CTLL_DST_INC | DWC_CTLL_SRC_FIX @@ -1126,7 +1132,7 @@ struct dw_cyclic_desc *dw_dma_cyclic_prep(struct dma_chan *chan, case DMA_TO_DEVICE: desc->lli.dar = dws->tx_reg; desc->lli.sar = buf_addr + (period_len * i); - desc->lli.ctllo = (DWC_DEFAULT_CTLLO + desc->lli.ctllo = (DWC_DEFAULT_CTLLO(chan->private) | DWC_CTLL_DST_WIDTH(reg_width) | DWC_CTLL_SRC_WIDTH(reg_width) | DWC_CTLL_DST_FIX @@ -1137,7 +1143,7 @@ struct dw_cyclic_desc *dw_dma_cyclic_prep(struct dma_chan *chan, case DMA_FROM_DEVICE: desc->lli.dar = buf_addr + (period_len * i); desc->lli.sar = dws->rx_reg; - desc->lli.ctllo = (DWC_DEFAULT_CTLLO + desc->lli.ctllo = (DWC_DEFAULT_CTLLO(chan->private) | DWC_CTLL_SRC_WIDTH(reg_width) | DWC_CTLL_DST_WIDTH(reg_width) | DWC_CTLL_DST_INC @@ -1335,6 +1341,8 @@ static int __init dw_probe(struct platform_device *pdev) dma_cap_set(DMA_MEMCPY, dw->dma.cap_mask); dma_cap_set(DMA_SLAVE, dw->dma.cap_mask); + if (pdata->is_private) + dma_cap_set(DMA_PRIVATE, dw->dma.cap_mask); dw->dma.dev = &pdev->dev; dw->dma.device_alloc_chan_resources = dwc_alloc_chan_resources; dw->dma.device_free_chan_resources = dwc_free_chan_resources; diff --git a/drivers/dma/pl330.c b/drivers/dma/pl330.c index 7c50f6dfd3f4..6abe1ec1f2ce 100644 --- a/drivers/dma/pl330.c +++ b/drivers/dma/pl330.c @@ -657,7 +657,7 @@ static irqreturn_t pl330_irq_handler(int irq, void *data) } static int __devinit -pl330_probe(struct amba_device *adev, struct amba_id *id) +pl330_probe(struct amba_device *adev, const struct amba_id *id) { struct dma_pl330_platdata *pdat; struct dma_pl330_dmac *pdmac; diff --git a/drivers/dma/ste_dma40.c b/drivers/dma/ste_dma40.c index 6e1d46a65d0e..af955de035f4 100644 --- a/drivers/dma/ste_dma40.c +++ b/drivers/dma/ste_dma40.c @@ -68,6 +68,7 @@ enum d40_command { * @base: Pointer to memory area when the pre_alloc_lli's are not large * enough, IE bigger than the most common case, 1 dst and 1 src. NULL if * pre_alloc_lli is used. + * @dma_addr: DMA address, if mapped * @size: The size in bytes of the memory at base or the size of pre_alloc_lli. * @pre_alloc_lli: Pre allocated area for the most common case of transfers, * one buffer to one buffer. @@ -75,6 +76,7 @@ enum d40_command { struct d40_lli_pool { void *base; int size; + dma_addr_t dma_addr; /* Space for dst and src, plus an extra for padding */ u8 pre_alloc_lli[3 * sizeof(struct d40_phy_lli)]; }; @@ -94,7 +96,6 @@ struct d40_lli_pool { * during a transfer. * @node: List entry. * @is_in_client_list: true if the client owns this descriptor. - * @is_hw_linked: true if this job will automatically be continued for * the previous one. * * This descriptor is used for both logical and physical transfers. @@ -114,7 +115,7 @@ struct d40_desc { struct list_head node; bool is_in_client_list; - bool is_hw_linked; + bool cyclic; }; /** @@ -130,6 +131,7 @@ struct d40_desc { */ struct d40_lcla_pool { void *base; + dma_addr_t dma_addr; void *base_unaligned; int pages; spinlock_t lock; @@ -303,9 +305,37 @@ struct d40_reg_val { unsigned int val; }; -static int d40_pool_lli_alloc(struct d40_desc *d40d, - int lli_len, bool is_log) +static struct device *chan2dev(struct d40_chan *d40c) { + return &d40c->chan.dev->device; +} + +static bool chan_is_physical(struct d40_chan *chan) +{ + return chan->log_num == D40_PHY_CHAN; +} + +static bool chan_is_logical(struct d40_chan *chan) +{ + return !chan_is_physical(chan); +} + +static void __iomem *chan_base(struct d40_chan *chan) +{ + return chan->base->virtbase + D40_DREG_PCBASE + + chan->phy_chan->num * D40_DREG_PCDELTA; +} + +#define d40_err(dev, format, arg...) \ + dev_err(dev, "[%s] " format, __func__, ## arg) + +#define chan_err(d40c, format, arg...) \ + d40_err(chan2dev(d40c), format, ## arg) + +static int d40_pool_lli_alloc(struct d40_chan *d40c, struct d40_desc *d40d, + int lli_len) +{ + bool is_log = chan_is_logical(d40c); u32 align; void *base; @@ -319,7 +349,7 @@ static int d40_pool_lli_alloc(struct d40_desc *d40d, d40d->lli_pool.size = sizeof(d40d->lli_pool.pre_alloc_lli); d40d->lli_pool.base = NULL; } else { - d40d->lli_pool.size = ALIGN(lli_len * 2 * align, align); + d40d->lli_pool.size = lli_len * 2 * align; base = kmalloc(d40d->lli_pool.size + align, GFP_NOWAIT); d40d->lli_pool.base = base; @@ -329,22 +359,37 @@ static int d40_pool_lli_alloc(struct d40_desc *d40d, } if (is_log) { - d40d->lli_log.src = PTR_ALIGN((struct d40_log_lli *) base, - align); - d40d->lli_log.dst = PTR_ALIGN(d40d->lli_log.src + lli_len, - align); + d40d->lli_log.src = PTR_ALIGN(base, align); + d40d->lli_log.dst = d40d->lli_log.src + lli_len; + + d40d->lli_pool.dma_addr = 0; } else { - d40d->lli_phy.src = PTR_ALIGN((struct d40_phy_lli *)base, - align); - d40d->lli_phy.dst = PTR_ALIGN(d40d->lli_phy.src + lli_len, - align); + d40d->lli_phy.src = PTR_ALIGN(base, align); + d40d->lli_phy.dst = d40d->lli_phy.src + lli_len; + + d40d->lli_pool.dma_addr = dma_map_single(d40c->base->dev, + d40d->lli_phy.src, + d40d->lli_pool.size, + DMA_TO_DEVICE); + + if (dma_mapping_error(d40c->base->dev, + d40d->lli_pool.dma_addr)) { + kfree(d40d->lli_pool.base); + d40d->lli_pool.base = NULL; + d40d->lli_pool.dma_addr = 0; + return -ENOMEM; + } } return 0; } -static void d40_pool_lli_free(struct d40_desc *d40d) +static void d40_pool_lli_free(struct d40_chan *d40c, struct d40_desc *d40d) { + if (d40d->lli_pool.dma_addr) + dma_unmap_single(d40c->base->dev, d40d->lli_pool.dma_addr, + d40d->lli_pool.size, DMA_TO_DEVICE); + kfree(d40d->lli_pool.base); d40d->lli_pool.base = NULL; d40d->lli_pool.size = 0; @@ -391,7 +436,7 @@ static int d40_lcla_free_all(struct d40_chan *d40c, int i; int ret = -EINVAL; - if (d40c->log_num == D40_PHY_CHAN) + if (chan_is_physical(d40c)) return 0; spin_lock_irqsave(&d40c->base->lcla_pool.lock, flags); @@ -430,7 +475,7 @@ static struct d40_desc *d40_desc_get(struct d40_chan *d40c) list_for_each_entry_safe(d, _d, &d40c->client, node) if (async_tx_test_ack(&d->txd)) { - d40_pool_lli_free(d); + d40_pool_lli_free(d40c, d); d40_desc_remove(d); desc = d; memset(desc, 0, sizeof(*desc)); @@ -450,6 +495,7 @@ static struct d40_desc *d40_desc_get(struct d40_chan *d40c) static void d40_desc_free(struct d40_chan *d40c, struct d40_desc *d40d) { + d40_pool_lli_free(d40c, d40d); d40_lcla_free_all(d40c, d40d); kmem_cache_free(d40c->base->desc_slab, d40d); } @@ -459,57 +505,128 @@ static void d40_desc_submit(struct d40_chan *d40c, struct d40_desc *desc) list_add_tail(&desc->node, &d40c->active); } -static void d40_desc_load(struct d40_chan *d40c, struct d40_desc *d40d) +static void d40_phy_lli_load(struct d40_chan *chan, struct d40_desc *desc) { - int curr_lcla = -EINVAL, next_lcla; + struct d40_phy_lli *lli_dst = desc->lli_phy.dst; + struct d40_phy_lli *lli_src = desc->lli_phy.src; + void __iomem *base = chan_base(chan); + + writel(lli_src->reg_cfg, base + D40_CHAN_REG_SSCFG); + writel(lli_src->reg_elt, base + D40_CHAN_REG_SSELT); + writel(lli_src->reg_ptr, base + D40_CHAN_REG_SSPTR); + writel(lli_src->reg_lnk, base + D40_CHAN_REG_SSLNK); + + writel(lli_dst->reg_cfg, base + D40_CHAN_REG_SDCFG); + writel(lli_dst->reg_elt, base + D40_CHAN_REG_SDELT); + writel(lli_dst->reg_ptr, base + D40_CHAN_REG_SDPTR); + writel(lli_dst->reg_lnk, base + D40_CHAN_REG_SDLNK); +} - if (d40c->log_num == D40_PHY_CHAN) { - d40_phy_lli_write(d40c->base->virtbase, - d40c->phy_chan->num, - d40d->lli_phy.dst, - d40d->lli_phy.src); - d40d->lli_current = d40d->lli_len; - } else { +static void d40_log_lli_to_lcxa(struct d40_chan *chan, struct d40_desc *desc) +{ + struct d40_lcla_pool *pool = &chan->base->lcla_pool; + struct d40_log_lli_bidir *lli = &desc->lli_log; + int lli_current = desc->lli_current; + int lli_len = desc->lli_len; + bool cyclic = desc->cyclic; + int curr_lcla = -EINVAL; + int first_lcla = 0; + bool linkback; - if ((d40d->lli_len - d40d->lli_current) > 1) - curr_lcla = d40_lcla_alloc_one(d40c, d40d); + /* + * We may have partially running cyclic transfers, in case we did't get + * enough LCLA entries. + */ + linkback = cyclic && lli_current == 0; - d40_log_lli_lcpa_write(d40c->lcpa, - &d40d->lli_log.dst[d40d->lli_current], - &d40d->lli_log.src[d40d->lli_current], - curr_lcla); + /* + * For linkback, we need one LCLA even with only one link, because we + * can't link back to the one in LCPA space + */ + if (linkback || (lli_len - lli_current > 1)) { + curr_lcla = d40_lcla_alloc_one(chan, desc); + first_lcla = curr_lcla; + } - d40d->lli_current++; - for (; d40d->lli_current < d40d->lli_len; d40d->lli_current++) { - struct d40_log_lli *lcla; + /* + * For linkback, we normally load the LCPA in the loop since we need to + * link it to the second LCLA and not the first. However, if we + * couldn't even get a first LCLA, then we have to run in LCPA and + * reload manually. + */ + if (!linkback || curr_lcla == -EINVAL) { + unsigned int flags = 0; - if (d40d->lli_current + 1 < d40d->lli_len) - next_lcla = d40_lcla_alloc_one(d40c, d40d); - else - next_lcla = -EINVAL; + if (curr_lcla == -EINVAL) + flags |= LLI_TERM_INT; - lcla = d40c->base->lcla_pool.base + - d40c->phy_chan->num * 1024 + - 8 * curr_lcla * 2; + d40_log_lli_lcpa_write(chan->lcpa, + &lli->dst[lli_current], + &lli->src[lli_current], + curr_lcla, + flags); + lli_current++; + } - d40_log_lli_lcla_write(lcla, - &d40d->lli_log.dst[d40d->lli_current], - &d40d->lli_log.src[d40d->lli_current], - next_lcla); + if (curr_lcla < 0) + goto out; - (void) dma_map_single(d40c->base->dev, lcla, - 2 * sizeof(struct d40_log_lli), - DMA_TO_DEVICE); + for (; lli_current < lli_len; lli_current++) { + unsigned int lcla_offset = chan->phy_chan->num * 1024 + + 8 * curr_lcla * 2; + struct d40_log_lli *lcla = pool->base + lcla_offset; + unsigned int flags = 0; + int next_lcla; - curr_lcla = next_lcla; + if (lli_current + 1 < lli_len) + next_lcla = d40_lcla_alloc_one(chan, desc); + else + next_lcla = linkback ? first_lcla : -EINVAL; - if (curr_lcla == -EINVAL) { - d40d->lli_current++; - break; - } + if (cyclic || next_lcla == -EINVAL) + flags |= LLI_TERM_INT; + + if (linkback && curr_lcla == first_lcla) { + /* First link goes in both LCPA and LCLA */ + d40_log_lli_lcpa_write(chan->lcpa, + &lli->dst[lli_current], + &lli->src[lli_current], + next_lcla, flags); + } + + /* + * One unused LCLA in the cyclic case if the very first + * next_lcla fails... + */ + d40_log_lli_lcla_write(lcla, + &lli->dst[lli_current], + &lli->src[lli_current], + next_lcla, flags); + + dma_sync_single_range_for_device(chan->base->dev, + pool->dma_addr, lcla_offset, + 2 * sizeof(struct d40_log_lli), + DMA_TO_DEVICE); + curr_lcla = next_lcla; + + if (curr_lcla == -EINVAL || curr_lcla == first_lcla) { + lli_current++; + break; } } + +out: + desc->lli_current = lli_current; +} + +static void d40_desc_load(struct d40_chan *d40c, struct d40_desc *d40d) +{ + if (chan_is_physical(d40c)) { + d40_phy_lli_load(d40c, d40d); + d40d->lli_current = d40d->lli_len; + } else + d40_log_lli_to_lcxa(d40c, d40d); } static struct d40_desc *d40_first_active_get(struct d40_chan *d40c) @@ -543,18 +660,6 @@ static struct d40_desc *d40_first_queued(struct d40_chan *d40c) return d; } -static struct d40_desc *d40_last_queued(struct d40_chan *d40c) -{ - struct d40_desc *d; - - if (list_empty(&d40c->queue)) - return NULL; - list_for_each_entry(d, &d40c->queue, node) - if (list_is_last(&d->node, &d40c->queue)) - break; - return d; -} - static int d40_psize_2_burst_size(bool is_log, int psize) { if (is_log) { @@ -666,9 +771,9 @@ static int d40_channel_execute_command(struct d40_chan *d40c, } if (i == D40_SUSPEND_MAX_IT) { - dev_err(&d40c->chan.dev->device, - "[%s]: unable to suspend the chl %d (log: %d) status %x\n", - __func__, d40c->phy_chan->num, d40c->log_num, + chan_err(d40c, + "unable to suspend the chl %d (log: %d) status %x\n", + d40c->phy_chan->num, d40c->log_num, status); dump_stack(); ret = -EBUSY; @@ -701,17 +806,45 @@ static void d40_term_all(struct d40_chan *d40c) d40c->busy = false; } +static void __d40_config_set_event(struct d40_chan *d40c, bool enable, + u32 event, int reg) +{ + void __iomem *addr = chan_base(d40c) + reg; + int tries; + + if (!enable) { + writel((D40_DEACTIVATE_EVENTLINE << D40_EVENTLINE_POS(event)) + | ~D40_EVENTLINE_MASK(event), addr); + return; + } + + /* + * The hardware sometimes doesn't register the enable when src and dst + * event lines are active on the same logical channel. Retry to ensure + * it does. Usually only one retry is sufficient. + */ + tries = 100; + while (--tries) { + writel((D40_ACTIVATE_EVENTLINE << D40_EVENTLINE_POS(event)) + | ~D40_EVENTLINE_MASK(event), addr); + + if (readl(addr) & D40_EVENTLINE_MASK(event)) + break; + } + + if (tries != 99) + dev_dbg(chan2dev(d40c), + "[%s] workaround enable S%cLNK (%d tries)\n", + __func__, reg == D40_CHAN_REG_SSLNK ? 'S' : 'D', + 100 - tries); + + WARN_ON(!tries); +} + static void d40_config_set_event(struct d40_chan *d40c, bool do_enable) { - u32 val; unsigned long flags; - /* Notice, that disable requires the physical channel to be stopped */ - if (do_enable) - val = D40_ACTIVATE_EVENTLINE; - else - val = D40_DEACTIVATE_EVENTLINE; - spin_lock_irqsave(&d40c->phy_chan->lock, flags); /* Enable event line connected to device (or memcpy) */ @@ -719,20 +852,15 @@ static void d40_config_set_event(struct d40_chan *d40c, bool do_enable) (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_PERIPH)) { u32 event = D40_TYPE_TO_EVENT(d40c->dma_cfg.src_dev_type); - writel((val << D40_EVENTLINE_POS(event)) | - ~D40_EVENTLINE_MASK(event), - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSLNK); + __d40_config_set_event(d40c, do_enable, event, + D40_CHAN_REG_SSLNK); } + if (d40c->dma_cfg.dir != STEDMA40_PERIPH_TO_MEM) { u32 event = D40_TYPE_TO_EVENT(d40c->dma_cfg.dst_dev_type); - writel((val << D40_EVENTLINE_POS(event)) | - ~D40_EVENTLINE_MASK(event), - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDLNK); + __d40_config_set_event(d40c, do_enable, event, + D40_CHAN_REG_SDLNK); } spin_unlock_irqrestore(&d40c->phy_chan->lock, flags); @@ -740,15 +868,12 @@ static void d40_config_set_event(struct d40_chan *d40c, bool do_enable) static u32 d40_chan_has_events(struct d40_chan *d40c) { + void __iomem *chanbase = chan_base(d40c); u32 val; - val = readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSLNK); + val = readl(chanbase + D40_CHAN_REG_SSLNK); + val |= readl(chanbase + D40_CHAN_REG_SDLNK); - val |= readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDLNK); return val; } @@ -771,7 +896,7 @@ static u32 d40_get_prmo(struct d40_chan *d40c) = D40_DREG_PRMO_LCHAN_SRC_LOG_DST_LOG, }; - if (d40c->log_num == D40_PHY_CHAN) + if (chan_is_physical(d40c)) return phy_map[d40c->dma_cfg.mode_opt]; else return log_map[d40c->dma_cfg.mode_opt]; @@ -785,7 +910,7 @@ static void d40_config_write(struct d40_chan *d40c) /* Odd addresses are even addresses + 4 */ addr_base = (d40c->phy_chan->num % 2) * 4; /* Setup channel mode to logical or physical */ - var = ((u32)(d40c->log_num != D40_PHY_CHAN) + 1) << + var = ((u32)(chan_is_logical(d40c)) + 1) << D40_CHAN_POS(d40c->phy_chan->num); writel(var, d40c->base->virtbase + D40_DREG_PRMSE + addr_base); @@ -794,30 +919,18 @@ static void d40_config_write(struct d40_chan *d40c) writel(var, d40c->base->virtbase + D40_DREG_PRMOE + addr_base); - if (d40c->log_num != D40_PHY_CHAN) { + if (chan_is_logical(d40c)) { + int lidx = (d40c->phy_chan->num << D40_SREG_ELEM_LOG_LIDX_POS) + & D40_SREG_ELEM_LOG_LIDX_MASK; + void __iomem *chanbase = chan_base(d40c); + /* Set default config for CFG reg */ - writel(d40c->src_def_cfg, - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSCFG); - writel(d40c->dst_def_cfg, - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDCFG); + writel(d40c->src_def_cfg, chanbase + D40_CHAN_REG_SSCFG); + writel(d40c->dst_def_cfg, chanbase + D40_CHAN_REG_SDCFG); /* Set LIDX for lcla */ - writel((d40c->phy_chan->num << D40_SREG_ELEM_LOG_LIDX_POS) & - D40_SREG_ELEM_LOG_LIDX_MASK, - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDELT); - - writel((d40c->phy_chan->num << D40_SREG_ELEM_LOG_LIDX_POS) & - D40_SREG_ELEM_LOG_LIDX_MASK, - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSELT); - + writel(lidx, chanbase + D40_CHAN_REG_SSELT); + writel(lidx, chanbase + D40_CHAN_REG_SDELT); } } @@ -825,15 +938,15 @@ static u32 d40_residue(struct d40_chan *d40c) { u32 num_elt; - if (d40c->log_num != D40_PHY_CHAN) + if (chan_is_logical(d40c)) num_elt = (readl(&d40c->lcpa->lcsp2) & D40_MEM_LCSP2_ECNT_MASK) >> D40_MEM_LCSP2_ECNT_POS; - else - num_elt = (readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDELT) & - D40_SREG_ELEM_PHY_ECNT_MASK) >> - D40_SREG_ELEM_PHY_ECNT_POS; + else { + u32 val = readl(chan_base(d40c) + D40_CHAN_REG_SDELT); + num_elt = (val & D40_SREG_ELEM_PHY_ECNT_MASK) + >> D40_SREG_ELEM_PHY_ECNT_POS; + } + return num_elt * (1 << d40c->dma_cfg.dst_info.data_width); } @@ -841,20 +954,17 @@ static bool d40_tx_is_linked(struct d40_chan *d40c) { bool is_link; - if (d40c->log_num != D40_PHY_CHAN) + if (chan_is_logical(d40c)) is_link = readl(&d40c->lcpa->lcsp3) & D40_MEM_LCSP3_DLOS_MASK; else - is_link = readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDLNK) & - D40_SREG_LNK_PHYS_LNK_MASK; + is_link = readl(chan_base(d40c) + D40_CHAN_REG_SDLNK) + & D40_SREG_LNK_PHYS_LNK_MASK; + return is_link; } -static int d40_pause(struct dma_chan *chan) +static int d40_pause(struct d40_chan *d40c) { - struct d40_chan *d40c = - container_of(chan, struct d40_chan, chan); int res = 0; unsigned long flags; @@ -865,7 +975,7 @@ static int d40_pause(struct dma_chan *chan) res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ); if (res == 0) { - if (d40c->log_num != D40_PHY_CHAN) { + if (chan_is_logical(d40c)) { d40_config_set_event(d40c, false); /* Resume the other logical channels if any */ if (d40_chan_has_events(d40c)) @@ -878,10 +988,8 @@ static int d40_pause(struct dma_chan *chan) return res; } -static int d40_resume(struct dma_chan *chan) +static int d40_resume(struct d40_chan *d40c) { - struct d40_chan *d40c = - container_of(chan, struct d40_chan, chan); int res = 0; unsigned long flags; @@ -891,7 +999,7 @@ static int d40_resume(struct dma_chan *chan) spin_lock_irqsave(&d40c->lock, flags); if (d40c->base->rev == 0) - if (d40c->log_num != D40_PHY_CHAN) { + if (chan_is_logical(d40c)) { res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ); goto no_suspend; @@ -900,7 +1008,7 @@ static int d40_resume(struct dma_chan *chan) /* If bytes left to transfer or linked tx resume job */ if (d40_residue(d40c) || d40_tx_is_linked(d40c)) { - if (d40c->log_num != D40_PHY_CHAN) + if (chan_is_logical(d40c)) d40_config_set_event(d40c, true); res = d40_channel_execute_command(d40c, D40_DMA_RUN); @@ -911,75 +1019,20 @@ no_suspend: return res; } -static void d40_tx_submit_log(struct d40_chan *d40c, struct d40_desc *d40d) +static int d40_terminate_all(struct d40_chan *chan) { - /* TODO: Write */ -} - -static void d40_tx_submit_phy(struct d40_chan *d40c, struct d40_desc *d40d) -{ - struct d40_desc *d40d_prev = NULL; - int i; - u32 val; - - if (!list_empty(&d40c->queue)) - d40d_prev = d40_last_queued(d40c); - else if (!list_empty(&d40c->active)) - d40d_prev = d40_first_active_get(d40c); - - if (!d40d_prev) - return; - - /* Here we try to join this job with previous jobs */ - val = readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSLNK); - - /* Figure out which link we're currently transmitting */ - for (i = 0; i < d40d_prev->lli_len; i++) - if (val == d40d_prev->lli_phy.src[i].reg_lnk) - break; - - val = readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSELT) >> D40_SREG_ELEM_LOG_ECNT_POS; - - if (i == (d40d_prev->lli_len - 1) && val > 0) { - /* Change the current one */ - writel(virt_to_phys(d40d->lli_phy.src), - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSLNK); - writel(virt_to_phys(d40d->lli_phy.dst), - d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDLNK); - - d40d->is_hw_linked = true; - - } else if (i < d40d_prev->lli_len) { - (void) dma_unmap_single(d40c->base->dev, - virt_to_phys(d40d_prev->lli_phy.src), - d40d_prev->lli_pool.size, - DMA_TO_DEVICE); + unsigned long flags; + int ret = 0; - /* Keep the settings */ - val = d40d_prev->lli_phy.src[d40d_prev->lli_len - 1].reg_lnk & - ~D40_SREG_LNK_PHYS_LNK_MASK; - d40d_prev->lli_phy.src[d40d_prev->lli_len - 1].reg_lnk = - val | virt_to_phys(d40d->lli_phy.src); + ret = d40_pause(chan); + if (!ret && chan_is_physical(chan)) + ret = d40_channel_execute_command(chan, D40_DMA_STOP); - val = d40d_prev->lli_phy.dst[d40d_prev->lli_len - 1].reg_lnk & - ~D40_SREG_LNK_PHYS_LNK_MASK; - d40d_prev->lli_phy.dst[d40d_prev->lli_len - 1].reg_lnk = - val | virt_to_phys(d40d->lli_phy.dst); + spin_lock_irqsave(&chan->lock, flags); + d40_term_all(chan); + spin_unlock_irqrestore(&chan->lock, flags); - (void) dma_map_single(d40c->base->dev, - d40d_prev->lli_phy.src, - d40d_prev->lli_pool.size, - DMA_TO_DEVICE); - d40d->is_hw_linked = true; - } + return ret; } static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) @@ -990,8 +1043,6 @@ static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) struct d40_desc *d40d = container_of(tx, struct d40_desc, txd); unsigned long flags; - (void) d40_pause(&d40c->chan); - spin_lock_irqsave(&d40c->lock, flags); d40c->chan.cookie++; @@ -1001,17 +1052,10 @@ static dma_cookie_t d40_tx_submit(struct dma_async_tx_descriptor *tx) d40d->txd.cookie = d40c->chan.cookie; - if (d40c->log_num == D40_PHY_CHAN) - d40_tx_submit_phy(d40c, d40d); - else - d40_tx_submit_log(d40c, d40d); - d40_desc_queue(d40c, d40d); spin_unlock_irqrestore(&d40c->lock, flags); - (void) d40_resume(&d40c->chan); - return tx->cookie; } @@ -1020,7 +1064,7 @@ static int d40_start(struct d40_chan *d40c) if (d40c->base->rev == 0) { int err; - if (d40c->log_num != D40_PHY_CHAN) { + if (chan_is_logical(d40c)) { err = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ); if (err) @@ -1028,7 +1072,7 @@ static int d40_start(struct d40_chan *d40c) } } - if (d40c->log_num != D40_PHY_CHAN) + if (chan_is_logical(d40c)) d40_config_set_event(d40c, true); return d40_channel_execute_command(d40c, D40_DMA_RUN); @@ -1051,21 +1095,14 @@ static struct d40_desc *d40_queue_start(struct d40_chan *d40c) /* Add to active queue */ d40_desc_submit(d40c, d40d); - /* - * If this job is already linked in hw, - * do not submit it. - */ - - if (!d40d->is_hw_linked) { - /* Initiate DMA job */ - d40_desc_load(d40c, d40d); + /* Initiate DMA job */ + d40_desc_load(d40c, d40d); - /* Start dma job */ - err = d40_start(d40c); + /* Start dma job */ + err = d40_start(d40c); - if (err) - return NULL; - } + if (err) + return NULL; } return d40d; @@ -1082,17 +1119,36 @@ static void dma_tc_handle(struct d40_chan *d40c) if (d40d == NULL) return; - d40_lcla_free_all(d40c, d40d); + if (d40d->cyclic) { + /* + * If this was a paritially loaded list, we need to reloaded + * it, and only when the list is completed. We need to check + * for done because the interrupt will hit for every link, and + * not just the last one. + */ + if (d40d->lli_current < d40d->lli_len + && !d40_tx_is_linked(d40c) + && !d40_residue(d40c)) { + d40_lcla_free_all(d40c, d40d); + d40_desc_load(d40c, d40d); + (void) d40_start(d40c); - if (d40d->lli_current < d40d->lli_len) { - d40_desc_load(d40c, d40d); - /* Start dma job */ - (void) d40_start(d40c); - return; - } + if (d40d->lli_current == d40d->lli_len) + d40d->lli_current = 0; + } + } else { + d40_lcla_free_all(d40c, d40d); - if (d40_queue_start(d40c) == NULL) - d40c->busy = false; + if (d40d->lli_current < d40d->lli_len) { + d40_desc_load(d40c, d40d); + /* Start dma job */ + (void) d40_start(d40c); + return; + } + + if (d40_queue_start(d40c) == NULL) + d40c->busy = false; + } d40c->pending_tx++; tasklet_schedule(&d40c->tasklet); @@ -1111,11 +1167,11 @@ static void dma_tasklet(unsigned long data) /* Get first active entry from list */ d40d = d40_first_active_get(d40c); - if (d40d == NULL) goto err; - d40c->completed = d40d->txd.cookie; + if (!d40d->cyclic) + d40c->completed = d40d->txd.cookie; /* * If terminating a channel pending_tx is set to zero. @@ -1130,16 +1186,18 @@ static void dma_tasklet(unsigned long data) callback = d40d->txd.callback; callback_param = d40d->txd.callback_param; - if (async_tx_test_ack(&d40d->txd)) { - d40_pool_lli_free(d40d); - d40_desc_remove(d40d); - d40_desc_free(d40c, d40d); - } else { - if (!d40d->is_in_client_list) { + if (!d40d->cyclic) { + if (async_tx_test_ack(&d40d->txd)) { + d40_pool_lli_free(d40c, d40d); d40_desc_remove(d40d); - d40_lcla_free_all(d40c, d40d); - list_add_tail(&d40d->node, &d40c->client); - d40d->is_in_client_list = true; + d40_desc_free(d40c, d40d); + } else { + if (!d40d->is_in_client_list) { + d40_desc_remove(d40d); + d40_lcla_free_all(d40c, d40d); + list_add_tail(&d40d->node, &d40c->client); + d40d->is_in_client_list = true; + } } } @@ -1216,9 +1274,8 @@ static irqreturn_t d40_handle_interrupt(int irq, void *data) if (!il[row].is_error) dma_tc_handle(d40c); else - dev_err(base->dev, - "[%s] IRQ chan: %ld offset %d idx %d\n", - __func__, chan, il[row].offset, idx); + d40_err(base->dev, "IRQ chan: %ld offset %d idx %d\n", + chan, il[row].offset, idx); spin_unlock(&d40c->lock); } @@ -1237,8 +1294,7 @@ static int d40_validate_conf(struct d40_chan *d40c, bool is_log = conf->mode == STEDMA40_MODE_LOGICAL; if (!conf->dir) { - dev_err(&d40c->chan.dev->device, "[%s] Invalid direction.\n", - __func__); + chan_err(d40c, "Invalid direction.\n"); res = -EINVAL; } @@ -1246,46 +1302,40 @@ static int d40_validate_conf(struct d40_chan *d40c, d40c->base->plat_data->dev_tx[conf->dst_dev_type] == 0 && d40c->runtime_addr == 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Invalid TX channel address (%d)\n", - __func__, conf->dst_dev_type); + chan_err(d40c, "Invalid TX channel address (%d)\n", + conf->dst_dev_type); res = -EINVAL; } if (conf->src_dev_type != STEDMA40_DEV_SRC_MEMORY && d40c->base->plat_data->dev_rx[conf->src_dev_type] == 0 && d40c->runtime_addr == 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Invalid RX channel address (%d)\n", - __func__, conf->src_dev_type); + chan_err(d40c, "Invalid RX channel address (%d)\n", + conf->src_dev_type); res = -EINVAL; } if (conf->dir == STEDMA40_MEM_TO_PERIPH && dst_event_group == STEDMA40_DEV_DST_MEMORY) { - dev_err(&d40c->chan.dev->device, "[%s] Invalid dst\n", - __func__); + chan_err(d40c, "Invalid dst\n"); res = -EINVAL; } if (conf->dir == STEDMA40_PERIPH_TO_MEM && src_event_group == STEDMA40_DEV_SRC_MEMORY) { - dev_err(&d40c->chan.dev->device, "[%s] Invalid src\n", - __func__); + chan_err(d40c, "Invalid src\n"); res = -EINVAL; } if (src_event_group == STEDMA40_DEV_SRC_MEMORY && dst_event_group == STEDMA40_DEV_DST_MEMORY && is_log) { - dev_err(&d40c->chan.dev->device, - "[%s] No event line\n", __func__); + chan_err(d40c, "No event line\n"); res = -EINVAL; } if (conf->dir == STEDMA40_PERIPH_TO_PERIPH && (src_event_group != dst_event_group)) { - dev_err(&d40c->chan.dev->device, - "[%s] Invalid event group\n", __func__); + chan_err(d40c, "Invalid event group\n"); res = -EINVAL; } @@ -1294,9 +1344,7 @@ static int d40_validate_conf(struct d40_chan *d40c, * DMAC HW supports it. Will be added to this driver, * in case any dma client requires it. */ - dev_err(&d40c->chan.dev->device, - "[%s] periph to periph not supported\n", - __func__); + chan_err(d40c, "periph to periph not supported\n"); res = -EINVAL; } @@ -1309,9 +1357,7 @@ static int d40_validate_conf(struct d40_chan *d40c, * src (burst x width) == dst (burst x width) */ - dev_err(&d40c->chan.dev->device, - "[%s] src (burst x width) != dst (burst x width)\n", - __func__); + chan_err(d40c, "src (burst x width) != dst (burst x width)\n"); res = -EINVAL; } @@ -1514,8 +1560,7 @@ static int d40_config_memcpy(struct d40_chan *d40c) dma_has_cap(DMA_SLAVE, cap)) { d40c->dma_cfg = *d40c->base->plat_data->memcpy_conf_phy; } else { - dev_err(&d40c->chan.dev->device, "[%s] No memcpy\n", - __func__); + chan_err(d40c, "No memcpy\n"); return -EINVAL; } @@ -1540,21 +1585,19 @@ static int d40_free_dma(struct d40_chan *d40c) /* Release client owned descriptors */ if (!list_empty(&d40c->client)) list_for_each_entry_safe(d, _d, &d40c->client, node) { - d40_pool_lli_free(d); + d40_pool_lli_free(d40c, d); d40_desc_remove(d); d40_desc_free(d40c, d); } if (phy == NULL) { - dev_err(&d40c->chan.dev->device, "[%s] phy == null\n", - __func__); + chan_err(d40c, "phy == null\n"); return -EINVAL; } if (phy->allocated_src == D40_ALLOC_FREE && phy->allocated_dst == D40_ALLOC_FREE) { - dev_err(&d40c->chan.dev->device, "[%s] channel already free\n", - __func__); + chan_err(d40c, "channel already free\n"); return -EINVAL; } @@ -1566,19 +1609,17 @@ static int d40_free_dma(struct d40_chan *d40c) event = D40_TYPE_TO_EVENT(d40c->dma_cfg.src_dev_type); is_src = true; } else { - dev_err(&d40c->chan.dev->device, - "[%s] Unknown direction\n", __func__); + chan_err(d40c, "Unknown direction\n"); return -EINVAL; } res = d40_channel_execute_command(d40c, D40_DMA_SUSPEND_REQ); if (res) { - dev_err(&d40c->chan.dev->device, "[%s] suspend failed\n", - __func__); + chan_err(d40c, "suspend failed\n"); return res; } - if (d40c->log_num != D40_PHY_CHAN) { + if (chan_is_logical(d40c)) { /* Release logical channel, deactivate the event line */ d40_config_set_event(d40c, false); @@ -1594,9 +1635,8 @@ static int d40_free_dma(struct d40_chan *d40c) res = d40_channel_execute_command(d40c, D40_DMA_RUN); if (res) { - dev_err(&d40c->chan.dev->device, - "[%s] Executing RUN command\n", - __func__); + chan_err(d40c, + "Executing RUN command\n"); return res; } } @@ -1609,8 +1649,7 @@ static int d40_free_dma(struct d40_chan *d40c) /* Release physical channel */ res = d40_channel_execute_command(d40c, D40_DMA_STOP); if (res) { - dev_err(&d40c->chan.dev->device, - "[%s] Failed to stop channel\n", __func__); + chan_err(d40c, "Failed to stop channel\n"); return res; } d40c->phy_chan = NULL; @@ -1622,6 +1661,7 @@ static int d40_free_dma(struct d40_chan *d40c) static bool d40_is_paused(struct d40_chan *d40c) { + void __iomem *chanbase = chan_base(d40c); bool is_paused = false; unsigned long flags; void __iomem *active_reg; @@ -1630,7 +1670,7 @@ static bool d40_is_paused(struct d40_chan *d40c) spin_lock_irqsave(&d40c->lock, flags); - if (d40c->log_num == D40_PHY_CHAN) { + if (chan_is_physical(d40c)) { if (d40c->phy_chan->num % 2 == 0) active_reg = d40c->base->virtbase + D40_DREG_ACTIVE; else @@ -1648,17 +1688,12 @@ static bool d40_is_paused(struct d40_chan *d40c) if (d40c->dma_cfg.dir == STEDMA40_MEM_TO_PERIPH || d40c->dma_cfg.dir == STEDMA40_MEM_TO_MEM) { event = D40_TYPE_TO_EVENT(d40c->dma_cfg.dst_dev_type); - status = readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SDLNK); + status = readl(chanbase + D40_CHAN_REG_SDLNK); } else if (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) { event = D40_TYPE_TO_EVENT(d40c->dma_cfg.src_dev_type); - status = readl(d40c->base->virtbase + D40_DREG_PCBASE + - d40c->phy_chan->num * D40_DREG_PCDELTA + - D40_CHAN_REG_SSLNK); + status = readl(chanbase + D40_CHAN_REG_SSLNK); } else { - dev_err(&d40c->chan.dev->device, - "[%s] Unknown direction\n", __func__); + chan_err(d40c, "Unknown direction\n"); goto _exit; } @@ -1688,114 +1723,184 @@ static u32 stedma40_residue(struct dma_chan *chan) return bytes_left; } -struct dma_async_tx_descriptor *stedma40_memcpy_sg(struct dma_chan *chan, - struct scatterlist *sgl_dst, - struct scatterlist *sgl_src, - unsigned int sgl_len, - unsigned long dma_flags) +static int +d40_prep_sg_log(struct d40_chan *chan, struct d40_desc *desc, + struct scatterlist *sg_src, struct scatterlist *sg_dst, + unsigned int sg_len, dma_addr_t src_dev_addr, + dma_addr_t dst_dev_addr) { - int res; - struct d40_desc *d40d; - struct d40_chan *d40c = container_of(chan, struct d40_chan, - chan); - unsigned long flags; + struct stedma40_chan_cfg *cfg = &chan->dma_cfg; + struct stedma40_half_channel_info *src_info = &cfg->src_info; + struct stedma40_half_channel_info *dst_info = &cfg->dst_info; + int ret; - if (d40c->phy_chan == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Unallocated channel.\n", __func__); - return ERR_PTR(-EINVAL); - } + ret = d40_log_sg_to_lli(sg_src, sg_len, + src_dev_addr, + desc->lli_log.src, + chan->log_def.lcsp1, + src_info->data_width, + dst_info->data_width); - spin_lock_irqsave(&d40c->lock, flags); - d40d = d40_desc_get(d40c); + ret = d40_log_sg_to_lli(sg_dst, sg_len, + dst_dev_addr, + desc->lli_log.dst, + chan->log_def.lcsp3, + dst_info->data_width, + src_info->data_width); - if (d40d == NULL) + return ret < 0 ? ret : 0; +} + +static int +d40_prep_sg_phy(struct d40_chan *chan, struct d40_desc *desc, + struct scatterlist *sg_src, struct scatterlist *sg_dst, + unsigned int sg_len, dma_addr_t src_dev_addr, + dma_addr_t dst_dev_addr) +{ + struct stedma40_chan_cfg *cfg = &chan->dma_cfg; + struct stedma40_half_channel_info *src_info = &cfg->src_info; + struct stedma40_half_channel_info *dst_info = &cfg->dst_info; + unsigned long flags = 0; + int ret; + + if (desc->cyclic) + flags |= LLI_CYCLIC | LLI_TERM_INT; + + ret = d40_phy_sg_to_lli(sg_src, sg_len, src_dev_addr, + desc->lli_phy.src, + virt_to_phys(desc->lli_phy.src), + chan->src_def_cfg, + src_info, dst_info, flags); + + ret = d40_phy_sg_to_lli(sg_dst, sg_len, dst_dev_addr, + desc->lli_phy.dst, + virt_to_phys(desc->lli_phy.dst), + chan->dst_def_cfg, + dst_info, src_info, flags); + + dma_sync_single_for_device(chan->base->dev, desc->lli_pool.dma_addr, + desc->lli_pool.size, DMA_TO_DEVICE); + + return ret < 0 ? ret : 0; +} + + +static struct d40_desc * +d40_prep_desc(struct d40_chan *chan, struct scatterlist *sg, + unsigned int sg_len, unsigned long dma_flags) +{ + struct stedma40_chan_cfg *cfg = &chan->dma_cfg; + struct d40_desc *desc; + int ret; + + desc = d40_desc_get(chan); + if (!desc) + return NULL; + + desc->lli_len = d40_sg_2_dmalen(sg, sg_len, cfg->src_info.data_width, + cfg->dst_info.data_width); + if (desc->lli_len < 0) { + chan_err(chan, "Unaligned size\n"); goto err; + } - d40d->lli_len = d40_sg_2_dmalen(sgl_dst, sgl_len, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width); - if (d40d->lli_len < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Unaligned size\n", __func__); + ret = d40_pool_lli_alloc(chan, desc, desc->lli_len); + if (ret < 0) { + chan_err(chan, "Could not allocate lli\n"); goto err; } - d40d->lli_current = 0; - d40d->txd.flags = dma_flags; - if (d40c->log_num != D40_PHY_CHAN) { + desc->lli_current = 0; + desc->txd.flags = dma_flags; + desc->txd.tx_submit = d40_tx_submit; - if (d40_pool_lli_alloc(d40d, d40d->lli_len, true) < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Out of memory\n", __func__); - goto err; - } + dma_async_tx_descriptor_init(&desc->txd, &chan->chan); - (void) d40_log_sg_to_lli(sgl_src, - sgl_len, - d40d->lli_log.src, - d40c->log_def.lcsp1, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width); - - (void) d40_log_sg_to_lli(sgl_dst, - sgl_len, - d40d->lli_log.dst, - d40c->log_def.lcsp3, - d40c->dma_cfg.dst_info.data_width, - d40c->dma_cfg.src_info.data_width); - } else { - if (d40_pool_lli_alloc(d40d, d40d->lli_len, false) < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Out of memory\n", __func__); - goto err; - } + return desc; + +err: + d40_desc_free(chan, desc); + return NULL; +} + +static dma_addr_t +d40_get_dev_addr(struct d40_chan *chan, enum dma_data_direction direction) +{ + struct stedma40_platform_data *plat = chan->base->plat_data; + struct stedma40_chan_cfg *cfg = &chan->dma_cfg; + dma_addr_t addr; - res = d40_phy_sg_to_lli(sgl_src, - sgl_len, - 0, - d40d->lli_phy.src, - virt_to_phys(d40d->lli_phy.src), - d40c->src_def_cfg, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width, - d40c->dma_cfg.src_info.psize); + if (chan->runtime_addr) + return chan->runtime_addr; - if (res < 0) - goto err; + if (direction == DMA_FROM_DEVICE) + addr = plat->dev_rx[cfg->src_dev_type]; + else if (direction == DMA_TO_DEVICE) + addr = plat->dev_tx[cfg->dst_dev_type]; - res = d40_phy_sg_to_lli(sgl_dst, - sgl_len, - 0, - d40d->lli_phy.dst, - virt_to_phys(d40d->lli_phy.dst), - d40c->dst_def_cfg, - d40c->dma_cfg.dst_info.data_width, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.psize); + return addr; +} - if (res < 0) - goto err; +static struct dma_async_tx_descriptor * +d40_prep_sg(struct dma_chan *dchan, struct scatterlist *sg_src, + struct scatterlist *sg_dst, unsigned int sg_len, + enum dma_data_direction direction, unsigned long dma_flags) +{ + struct d40_chan *chan = container_of(dchan, struct d40_chan, chan); + dma_addr_t src_dev_addr = 0; + dma_addr_t dst_dev_addr = 0; + struct d40_desc *desc; + unsigned long flags; + int ret; - (void) dma_map_single(d40c->base->dev, d40d->lli_phy.src, - d40d->lli_pool.size, DMA_TO_DEVICE); + if (!chan->phy_chan) { + chan_err(chan, "Cannot prepare unallocated channel\n"); + return NULL; } - dma_async_tx_descriptor_init(&d40d->txd, chan); - d40d->txd.tx_submit = d40_tx_submit; + spin_lock_irqsave(&chan->lock, flags); - spin_unlock_irqrestore(&d40c->lock, flags); + desc = d40_prep_desc(chan, sg_src, sg_len, dma_flags); + if (desc == NULL) + goto err; + + if (sg_next(&sg_src[sg_len - 1]) == sg_src) + desc->cyclic = true; + + if (direction != DMA_NONE) { + dma_addr_t dev_addr = d40_get_dev_addr(chan, direction); + + if (direction == DMA_FROM_DEVICE) + src_dev_addr = dev_addr; + else if (direction == DMA_TO_DEVICE) + dst_dev_addr = dev_addr; + } + + if (chan_is_logical(chan)) + ret = d40_prep_sg_log(chan, desc, sg_src, sg_dst, + sg_len, src_dev_addr, dst_dev_addr); + else + ret = d40_prep_sg_phy(chan, desc, sg_src, sg_dst, + sg_len, src_dev_addr, dst_dev_addr); + + if (ret) { + chan_err(chan, "Failed to prepare %s sg job: %d\n", + chan_is_logical(chan) ? "log" : "phy", ret); + goto err; + } + + spin_unlock_irqrestore(&chan->lock, flags); + + return &desc->txd; - return &d40d->txd; err: - if (d40d) - d40_desc_free(d40c, d40d); - spin_unlock_irqrestore(&d40c->lock, flags); + if (desc) + d40_desc_free(chan, desc); + spin_unlock_irqrestore(&chan->lock, flags); return NULL; } -EXPORT_SYMBOL(stedma40_memcpy_sg); bool stedma40_filter(struct dma_chan *chan, void *data) { @@ -1818,6 +1923,38 @@ bool stedma40_filter(struct dma_chan *chan, void *data) } EXPORT_SYMBOL(stedma40_filter); +static void __d40_set_prio_rt(struct d40_chan *d40c, int dev_type, bool src) +{ + bool realtime = d40c->dma_cfg.realtime; + bool highprio = d40c->dma_cfg.high_priority; + u32 prioreg = highprio ? D40_DREG_PSEG1 : D40_DREG_PCEG1; + u32 rtreg = realtime ? D40_DREG_RSEG1 : D40_DREG_RCEG1; + u32 event = D40_TYPE_TO_EVENT(dev_type); + u32 group = D40_TYPE_TO_GROUP(dev_type); + u32 bit = 1 << event; + + /* Destination event lines are stored in the upper halfword */ + if (!src) + bit <<= 16; + + writel(bit, d40c->base->virtbase + prioreg + group * 4); + writel(bit, d40c->base->virtbase + rtreg + group * 4); +} + +static void d40_set_prio_realtime(struct d40_chan *d40c) +{ + if (d40c->base->rev < 3) + return; + + if ((d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_MEM) || + (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_PERIPH)) + __d40_set_prio_rt(d40c, d40c->dma_cfg.src_dev_type, true); + + if ((d40c->dma_cfg.dir == STEDMA40_MEM_TO_PERIPH) || + (d40c->dma_cfg.dir == STEDMA40_PERIPH_TO_PERIPH)) + __d40_set_prio_rt(d40c, d40c->dma_cfg.dst_dev_type, false); +} + /* DMA ENGINE functions */ static int d40_alloc_chan_resources(struct dma_chan *chan) { @@ -1834,9 +1971,7 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) if (!d40c->configured) { err = d40_config_memcpy(d40c); if (err) { - dev_err(&d40c->chan.dev->device, - "[%s] Failed to configure memcpy channel\n", - __func__); + chan_err(d40c, "Failed to configure memcpy channel\n"); goto fail; } } @@ -1844,16 +1979,17 @@ static int d40_alloc_chan_resources(struct dma_chan *chan) err = d40_allocate_channel(d40c); if (err) { - dev_err(&d40c->chan.dev->device, - "[%s] Failed to allocate channel\n", __func__); + chan_err(d40c, "Failed to allocate channel\n"); goto fail; } /* Fill in basic CFG register values */ d40_phy_cfg(&d40c->dma_cfg, &d40c->src_def_cfg, - &d40c->dst_def_cfg, d40c->log_num != D40_PHY_CHAN); + &d40c->dst_def_cfg, chan_is_logical(d40c)); - if (d40c->log_num != D40_PHY_CHAN) { + d40_set_prio_realtime(d40c); + + if (chan_is_logical(d40c)) { d40_log_cfg(&d40c->dma_cfg, &d40c->log_def.lcsp1, &d40c->log_def.lcsp3); @@ -1886,8 +2022,7 @@ static void d40_free_chan_resources(struct dma_chan *chan) unsigned long flags; if (d40c->phy_chan == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Cannot free unallocated channel\n", __func__); + chan_err(d40c, "Cannot free unallocated channel\n"); return; } @@ -1897,8 +2032,7 @@ static void d40_free_chan_resources(struct dma_chan *chan) err = d40_free_dma(d40c); if (err) - dev_err(&d40c->chan.dev->device, - "[%s] Failed to free channel\n", __func__); + chan_err(d40c, "Failed to free channel\n"); spin_unlock_irqrestore(&d40c->lock, flags); } @@ -1908,251 +2042,31 @@ static struct dma_async_tx_descriptor *d40_prep_memcpy(struct dma_chan *chan, size_t size, unsigned long dma_flags) { - struct d40_desc *d40d; - struct d40_chan *d40c = container_of(chan, struct d40_chan, - chan); - unsigned long flags; - - if (d40c->phy_chan == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Channel is not allocated.\n", __func__); - return ERR_PTR(-EINVAL); - } - - spin_lock_irqsave(&d40c->lock, flags); - d40d = d40_desc_get(d40c); - - if (d40d == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Descriptor is NULL\n", __func__); - goto err; - } + struct scatterlist dst_sg; + struct scatterlist src_sg; - d40d->txd.flags = dma_flags; - d40d->lli_len = d40_size_2_dmalen(size, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width); - if (d40d->lli_len < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Unaligned size\n", __func__); - goto err; - } + sg_init_table(&dst_sg, 1); + sg_init_table(&src_sg, 1); + sg_dma_address(&dst_sg) = dst; + sg_dma_address(&src_sg) = src; - dma_async_tx_descriptor_init(&d40d->txd, chan); + sg_dma_len(&dst_sg) = size; + sg_dma_len(&src_sg) = size; - d40d->txd.tx_submit = d40_tx_submit; - - if (d40c->log_num != D40_PHY_CHAN) { - - if (d40_pool_lli_alloc(d40d, d40d->lli_len, true) < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Out of memory\n", __func__); - goto err; - } - d40d->lli_current = 0; - - if (d40_log_buf_to_lli(d40d->lli_log.src, - src, - size, - d40c->log_def.lcsp1, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width, - true) == NULL) - goto err; - - if (d40_log_buf_to_lli(d40d->lli_log.dst, - dst, - size, - d40c->log_def.lcsp3, - d40c->dma_cfg.dst_info.data_width, - d40c->dma_cfg.src_info.data_width, - true) == NULL) - goto err; - - } else { - - if (d40_pool_lli_alloc(d40d, d40d->lli_len, false) < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Out of memory\n", __func__); - goto err; - } - - if (d40_phy_buf_to_lli(d40d->lli_phy.src, - src, - size, - d40c->dma_cfg.src_info.psize, - 0, - d40c->src_def_cfg, - true, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width, - false) == NULL) - goto err; - - if (d40_phy_buf_to_lli(d40d->lli_phy.dst, - dst, - size, - d40c->dma_cfg.dst_info.psize, - 0, - d40c->dst_def_cfg, - true, - d40c->dma_cfg.dst_info.data_width, - d40c->dma_cfg.src_info.data_width, - false) == NULL) - goto err; - - (void) dma_map_single(d40c->base->dev, d40d->lli_phy.src, - d40d->lli_pool.size, DMA_TO_DEVICE); - } - - spin_unlock_irqrestore(&d40c->lock, flags); - return &d40d->txd; - -err: - if (d40d) - d40_desc_free(d40c, d40d); - spin_unlock_irqrestore(&d40c->lock, flags); - return NULL; + return d40_prep_sg(chan, &src_sg, &dst_sg, 1, DMA_NONE, dma_flags); } static struct dma_async_tx_descriptor * -d40_prep_sg(struct dma_chan *chan, - struct scatterlist *dst_sg, unsigned int dst_nents, - struct scatterlist *src_sg, unsigned int src_nents, - unsigned long dma_flags) +d40_prep_memcpy_sg(struct dma_chan *chan, + struct scatterlist *dst_sg, unsigned int dst_nents, + struct scatterlist *src_sg, unsigned int src_nents, + unsigned long dma_flags) { if (dst_nents != src_nents) return NULL; - return stedma40_memcpy_sg(chan, dst_sg, src_sg, dst_nents, dma_flags); -} - -static int d40_prep_slave_sg_log(struct d40_desc *d40d, - struct d40_chan *d40c, - struct scatterlist *sgl, - unsigned int sg_len, - enum dma_data_direction direction, - unsigned long dma_flags) -{ - dma_addr_t dev_addr = 0; - int total_size; - - d40d->lli_len = d40_sg_2_dmalen(sgl, sg_len, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width); - if (d40d->lli_len < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Unaligned size\n", __func__); - return -EINVAL; - } - - if (d40_pool_lli_alloc(d40d, d40d->lli_len, true) < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Out of memory\n", __func__); - return -ENOMEM; - } - - d40d->lli_current = 0; - - if (direction == DMA_FROM_DEVICE) - if (d40c->runtime_addr) - dev_addr = d40c->runtime_addr; - else - dev_addr = d40c->base->plat_data->dev_rx[d40c->dma_cfg.src_dev_type]; - else if (direction == DMA_TO_DEVICE) - if (d40c->runtime_addr) - dev_addr = d40c->runtime_addr; - else - dev_addr = d40c->base->plat_data->dev_tx[d40c->dma_cfg.dst_dev_type]; - - else - return -EINVAL; - - total_size = d40_log_sg_to_dev(sgl, sg_len, - &d40d->lli_log, - &d40c->log_def, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width, - direction, - dev_addr); - - if (total_size < 0) - return -EINVAL; - - return 0; -} - -static int d40_prep_slave_sg_phy(struct d40_desc *d40d, - struct d40_chan *d40c, - struct scatterlist *sgl, - unsigned int sgl_len, - enum dma_data_direction direction, - unsigned long dma_flags) -{ - dma_addr_t src_dev_addr; - dma_addr_t dst_dev_addr; - int res; - - d40d->lli_len = d40_sg_2_dmalen(sgl, sgl_len, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width); - if (d40d->lli_len < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Unaligned size\n", __func__); - return -EINVAL; - } - - if (d40_pool_lli_alloc(d40d, d40d->lli_len, false) < 0) { - dev_err(&d40c->chan.dev->device, - "[%s] Out of memory\n", __func__); - return -ENOMEM; - } - - d40d->lli_current = 0; - - if (direction == DMA_FROM_DEVICE) { - dst_dev_addr = 0; - if (d40c->runtime_addr) - src_dev_addr = d40c->runtime_addr; - else - src_dev_addr = d40c->base->plat_data->dev_rx[d40c->dma_cfg.src_dev_type]; - } else if (direction == DMA_TO_DEVICE) { - if (d40c->runtime_addr) - dst_dev_addr = d40c->runtime_addr; - else - dst_dev_addr = d40c->base->plat_data->dev_tx[d40c->dma_cfg.dst_dev_type]; - src_dev_addr = 0; - } else - return -EINVAL; - - res = d40_phy_sg_to_lli(sgl, - sgl_len, - src_dev_addr, - d40d->lli_phy.src, - virt_to_phys(d40d->lli_phy.src), - d40c->src_def_cfg, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.data_width, - d40c->dma_cfg.src_info.psize); - if (res < 0) - return res; - - res = d40_phy_sg_to_lli(sgl, - sgl_len, - dst_dev_addr, - d40d->lli_phy.dst, - virt_to_phys(d40d->lli_phy.dst), - d40c->dst_def_cfg, - d40c->dma_cfg.dst_info.data_width, - d40c->dma_cfg.src_info.data_width, - d40c->dma_cfg.dst_info.psize); - if (res < 0) - return res; - - (void) dma_map_single(d40c->base->dev, d40d->lli_phy.src, - d40d->lli_pool.size, DMA_TO_DEVICE); - return 0; + return d40_prep_sg(chan, src_sg, dst_sg, src_nents, DMA_NONE, dma_flags); } static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan, @@ -2161,52 +2075,40 @@ static struct dma_async_tx_descriptor *d40_prep_slave_sg(struct dma_chan *chan, enum dma_data_direction direction, unsigned long dma_flags) { - struct d40_desc *d40d; - struct d40_chan *d40c = container_of(chan, struct d40_chan, - chan); - unsigned long flags; - int err; - - if (d40c->phy_chan == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Cannot prepare unallocated channel\n", __func__); - return ERR_PTR(-EINVAL); - } + if (direction != DMA_FROM_DEVICE && direction != DMA_TO_DEVICE) + return NULL; - spin_lock_irqsave(&d40c->lock, flags); - d40d = d40_desc_get(d40c); + return d40_prep_sg(chan, sgl, sgl, sg_len, direction, dma_flags); +} - if (d40d == NULL) - goto err; +static struct dma_async_tx_descriptor * +dma40_prep_dma_cyclic(struct dma_chan *chan, dma_addr_t dma_addr, + size_t buf_len, size_t period_len, + enum dma_data_direction direction) +{ + unsigned int periods = buf_len / period_len; + struct dma_async_tx_descriptor *txd; + struct scatterlist *sg; + int i; - if (d40c->log_num != D40_PHY_CHAN) - err = d40_prep_slave_sg_log(d40d, d40c, sgl, sg_len, - direction, dma_flags); - else - err = d40_prep_slave_sg_phy(d40d, d40c, sgl, sg_len, - direction, dma_flags); - if (err) { - dev_err(&d40c->chan.dev->device, - "[%s] Failed to prepare %s slave sg job: %d\n", - __func__, - d40c->log_num != D40_PHY_CHAN ? "log" : "phy", err); - goto err; + sg = kcalloc(periods + 1, sizeof(struct scatterlist), GFP_KERNEL); + for (i = 0; i < periods; i++) { + sg_dma_address(&sg[i]) = dma_addr; + sg_dma_len(&sg[i]) = period_len; + dma_addr += period_len; } - d40d->txd.flags = dma_flags; + sg[periods].offset = 0; + sg[periods].length = 0; + sg[periods].page_link = + ((unsigned long)sg | 0x01) & ~0x02; - dma_async_tx_descriptor_init(&d40d->txd, chan); + txd = d40_prep_sg(chan, sg, sg, periods, direction, + DMA_PREP_INTERRUPT); - d40d->txd.tx_submit = d40_tx_submit; + kfree(sg); - spin_unlock_irqrestore(&d40c->lock, flags); - return &d40d->txd; - -err: - if (d40d) - d40_desc_free(d40c, d40d); - spin_unlock_irqrestore(&d40c->lock, flags); - return NULL; + return txd; } static enum dma_status d40_tx_status(struct dma_chan *chan, @@ -2219,9 +2121,7 @@ static enum dma_status d40_tx_status(struct dma_chan *chan, int ret; if (d40c->phy_chan == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Cannot read status of unallocated channel\n", - __func__); + chan_err(d40c, "Cannot read status of unallocated channel\n"); return -EINVAL; } @@ -2245,8 +2145,7 @@ static void d40_issue_pending(struct dma_chan *chan) unsigned long flags; if (d40c->phy_chan == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Channel is not allocated!\n", __func__); + chan_err(d40c, "Channel is not allocated!\n"); return; } @@ -2339,7 +2238,7 @@ static void d40_set_runtime_config(struct dma_chan *chan, return; } - if (d40c->log_num != D40_PHY_CHAN) { + if (chan_is_logical(d40c)) { if (config_maxburst >= 16) psize = STEDMA40_PSIZE_LOG_16; else if (config_maxburst >= 8) @@ -2372,7 +2271,7 @@ static void d40_set_runtime_config(struct dma_chan *chan, cfg->dst_info.flow_ctrl = STEDMA40_NO_FLOW_CTRL; /* Fill in register values */ - if (d40c->log_num != D40_PHY_CHAN) + if (chan_is_logical(d40c)) d40_log_cfg(cfg, &d40c->log_def.lcsp1, &d40c->log_def.lcsp3); else d40_phy_cfg(cfg, &d40c->src_def_cfg, @@ -2393,25 +2292,20 @@ static void d40_set_runtime_config(struct dma_chan *chan, static int d40_control(struct dma_chan *chan, enum dma_ctrl_cmd cmd, unsigned long arg) { - unsigned long flags; struct d40_chan *d40c = container_of(chan, struct d40_chan, chan); if (d40c->phy_chan == NULL) { - dev_err(&d40c->chan.dev->device, - "[%s] Channel is not allocated!\n", __func__); + chan_err(d40c, "Channel is not allocated!\n"); return -EINVAL; } switch (cmd) { case DMA_TERMINATE_ALL: - spin_lock_irqsave(&d40c->lock, flags); - d40_term_all(d40c); - spin_unlock_irqrestore(&d40c->lock, flags); - return 0; + return d40_terminate_all(d40c); case DMA_PAUSE: - return d40_pause(chan); + return d40_pause(d40c); case DMA_RESUME: - return d40_resume(chan); + return d40_resume(d40c); case DMA_SLAVE_CONFIG: d40_set_runtime_config(chan, (struct dma_slave_config *) arg); @@ -2456,6 +2350,35 @@ static void __init d40_chan_init(struct d40_base *base, struct dma_device *dma, } } +static void d40_ops_init(struct d40_base *base, struct dma_device *dev) +{ + if (dma_has_cap(DMA_SLAVE, dev->cap_mask)) + dev->device_prep_slave_sg = d40_prep_slave_sg; + + if (dma_has_cap(DMA_MEMCPY, dev->cap_mask)) { + dev->device_prep_dma_memcpy = d40_prep_memcpy; + + /* + * This controller can only access address at even + * 32bit boundaries, i.e. 2^2 + */ + dev->copy_align = 2; + } + + if (dma_has_cap(DMA_SG, dev->cap_mask)) + dev->device_prep_dma_sg = d40_prep_memcpy_sg; + + if (dma_has_cap(DMA_CYCLIC, dev->cap_mask)) + dev->device_prep_dma_cyclic = dma40_prep_dma_cyclic; + + dev->device_alloc_chan_resources = d40_alloc_chan_resources; + dev->device_free_chan_resources = d40_free_chan_resources; + dev->device_issue_pending = d40_issue_pending; + dev->device_tx_status = d40_tx_status; + dev->device_control = d40_control; + dev->dev = base->dev; +} + static int __init d40_dmaengine_init(struct d40_base *base, int num_reserved_chans) { @@ -2466,23 +2389,14 @@ static int __init d40_dmaengine_init(struct d40_base *base, dma_cap_zero(base->dma_slave.cap_mask); dma_cap_set(DMA_SLAVE, base->dma_slave.cap_mask); + dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask); - base->dma_slave.device_alloc_chan_resources = d40_alloc_chan_resources; - base->dma_slave.device_free_chan_resources = d40_free_chan_resources; - base->dma_slave.device_prep_dma_memcpy = d40_prep_memcpy; - base->dma_slave.device_prep_dma_sg = d40_prep_sg; - base->dma_slave.device_prep_slave_sg = d40_prep_slave_sg; - base->dma_slave.device_tx_status = d40_tx_status; - base->dma_slave.device_issue_pending = d40_issue_pending; - base->dma_slave.device_control = d40_control; - base->dma_slave.dev = base->dev; + d40_ops_init(base, &base->dma_slave); err = dma_async_device_register(&base->dma_slave); if (err) { - dev_err(base->dev, - "[%s] Failed to register slave channels\n", - __func__); + d40_err(base->dev, "Failed to register slave channels\n"); goto failure1; } @@ -2491,29 +2405,15 @@ static int __init d40_dmaengine_init(struct d40_base *base, dma_cap_zero(base->dma_memcpy.cap_mask); dma_cap_set(DMA_MEMCPY, base->dma_memcpy.cap_mask); - dma_cap_set(DMA_SG, base->dma_slave.cap_mask); - - base->dma_memcpy.device_alloc_chan_resources = d40_alloc_chan_resources; - base->dma_memcpy.device_free_chan_resources = d40_free_chan_resources; - base->dma_memcpy.device_prep_dma_memcpy = d40_prep_memcpy; - base->dma_slave.device_prep_dma_sg = d40_prep_sg; - base->dma_memcpy.device_prep_slave_sg = d40_prep_slave_sg; - base->dma_memcpy.device_tx_status = d40_tx_status; - base->dma_memcpy.device_issue_pending = d40_issue_pending; - base->dma_memcpy.device_control = d40_control; - base->dma_memcpy.dev = base->dev; - /* - * This controller can only access address at even - * 32bit boundaries, i.e. 2^2 - */ - base->dma_memcpy.copy_align = 2; + dma_cap_set(DMA_SG, base->dma_memcpy.cap_mask); + + d40_ops_init(base, &base->dma_memcpy); err = dma_async_device_register(&base->dma_memcpy); if (err) { - dev_err(base->dev, - "[%s] Failed to regsiter memcpy only channels\n", - __func__); + d40_err(base->dev, + "Failed to regsiter memcpy only channels\n"); goto failure2; } @@ -2523,24 +2423,15 @@ static int __init d40_dmaengine_init(struct d40_base *base, dma_cap_zero(base->dma_both.cap_mask); dma_cap_set(DMA_SLAVE, base->dma_both.cap_mask); dma_cap_set(DMA_MEMCPY, base->dma_both.cap_mask); - dma_cap_set(DMA_SG, base->dma_slave.cap_mask); - - base->dma_both.device_alloc_chan_resources = d40_alloc_chan_resources; - base->dma_both.device_free_chan_resources = d40_free_chan_resources; - base->dma_both.device_prep_dma_memcpy = d40_prep_memcpy; - base->dma_slave.device_prep_dma_sg = d40_prep_sg; - base->dma_both.device_prep_slave_sg = d40_prep_slave_sg; - base->dma_both.device_tx_status = d40_tx_status; - base->dma_both.device_issue_pending = d40_issue_pending; - base->dma_both.device_control = d40_control; - base->dma_both.dev = base->dev; - base->dma_both.copy_align = 2; + dma_cap_set(DMA_SG, base->dma_both.cap_mask); + dma_cap_set(DMA_CYCLIC, base->dma_slave.cap_mask); + + d40_ops_init(base, &base->dma_both); err = dma_async_device_register(&base->dma_both); if (err) { - dev_err(base->dev, - "[%s] Failed to register logical and physical capable channels\n", - __func__); + d40_err(base->dev, + "Failed to register logical and physical capable channels\n"); goto failure3; } return 0; @@ -2616,9 +2507,10 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) { .reg = D40_DREG_PERIPHID1, .val = 0x0000}, /* * D40_DREG_PERIPHID2 Depends on HW revision: - * MOP500/HREF ED has 0x0008, + * DB8500ed has 0x0008, * ? has 0x0018, - * HREF V1 has 0x0028 + * DB8500v1 has 0x0028 + * DB8500v2 has 0x0038 */ { .reg = D40_DREG_PERIPHID3, .val = 0x0000}, @@ -2642,8 +2534,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) clk = clk_get(&pdev->dev, NULL); if (IS_ERR(clk)) { - dev_err(&pdev->dev, "[%s] No matching clock found\n", - __func__); + d40_err(&pdev->dev, "No matching clock found\n"); goto failure; } @@ -2666,9 +2557,8 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) for (i = 0; i < ARRAY_SIZE(dma_id_regs); i++) { if (dma_id_regs[i].val != readl(virtbase + dma_id_regs[i].reg)) { - dev_err(&pdev->dev, - "[%s] Unknown hardware! Expected 0x%x at 0x%x but got 0x%x\n", - __func__, + d40_err(&pdev->dev, + "Unknown hardware! Expected 0x%x at 0x%x but got 0x%x\n", dma_id_regs[i].val, dma_id_regs[i].reg, readl(virtbase + dma_id_regs[i].reg)); @@ -2681,9 +2571,8 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) if ((val & D40_DREG_PERIPHID2_DESIGNER_MASK) != D40_HW_DESIGNER) { - dev_err(&pdev->dev, - "[%s] Unknown designer! Got %x wanted %x\n", - __func__, val & D40_DREG_PERIPHID2_DESIGNER_MASK, + d40_err(&pdev->dev, "Unknown designer! Got %x wanted %x\n", + val & D40_DREG_PERIPHID2_DESIGNER_MASK, D40_HW_DESIGNER); goto failure; } @@ -2713,7 +2602,7 @@ static struct d40_base * __init d40_hw_detect_init(struct platform_device *pdev) sizeof(struct d40_chan), GFP_KERNEL); if (base == NULL) { - dev_err(&pdev->dev, "[%s] Out of memory\n", __func__); + d40_err(&pdev->dev, "Out of memory\n"); goto failure; } @@ -2860,6 +2749,7 @@ static void __init d40_hw_init(struct d40_base *base) static int __init d40_lcla_allocate(struct d40_base *base) { + struct d40_lcla_pool *pool = &base->lcla_pool; unsigned long *page_list; int i, j; int ret = 0; @@ -2885,9 +2775,8 @@ static int __init d40_lcla_allocate(struct d40_base *base) base->lcla_pool.pages); if (!page_list[i]) { - dev_err(base->dev, - "[%s] Failed to allocate %d pages.\n", - __func__, base->lcla_pool.pages); + d40_err(base->dev, "Failed to allocate %d pages.\n", + base->lcla_pool.pages); for (j = 0; j < i; j++) free_pages(page_list[j], base->lcla_pool.pages); @@ -2925,6 +2814,15 @@ static int __init d40_lcla_allocate(struct d40_base *base) LCLA_ALIGNMENT); } + pool->dma_addr = dma_map_single(base->dev, pool->base, + SZ_1K * base->num_phy_chans, + DMA_TO_DEVICE); + if (dma_mapping_error(base->dev, pool->dma_addr)) { + pool->dma_addr = 0; + ret = -ENOMEM; + goto failure; + } + writel(virt_to_phys(base->lcla_pool.base), base->virtbase + D40_DREG_LCLA); failure: @@ -2957,9 +2855,7 @@ static int __init d40_probe(struct platform_device *pdev) res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "lcpa"); if (!res) { ret = -ENOENT; - dev_err(&pdev->dev, - "[%s] No \"lcpa\" memory resource\n", - __func__); + d40_err(&pdev->dev, "No \"lcpa\" memory resource\n"); goto failure; } base->lcpa_size = resource_size(res); @@ -2968,9 +2864,9 @@ static int __init d40_probe(struct platform_device *pdev) if (request_mem_region(res->start, resource_size(res), D40_NAME " I/O lcpa") == NULL) { ret = -EBUSY; - dev_err(&pdev->dev, - "[%s] Failed to request LCPA region 0x%x-0x%x\n", - __func__, res->start, res->end); + d40_err(&pdev->dev, + "Failed to request LCPA region 0x%x-0x%x\n", + res->start, res->end); goto failure; } @@ -2986,16 +2882,13 @@ static int __init d40_probe(struct platform_device *pdev) base->lcpa_base = ioremap(res->start, resource_size(res)); if (!base->lcpa_base) { ret = -ENOMEM; - dev_err(&pdev->dev, - "[%s] Failed to ioremap LCPA region\n", - __func__); + d40_err(&pdev->dev, "Failed to ioremap LCPA region\n"); goto failure; } ret = d40_lcla_allocate(base); if (ret) { - dev_err(&pdev->dev, "[%s] Failed to allocate LCLA area\n", - __func__); + d40_err(&pdev->dev, "Failed to allocate LCLA area\n"); goto failure; } @@ -3004,9 +2897,8 @@ static int __init d40_probe(struct platform_device *pdev) base->irq = platform_get_irq(pdev, 0); ret = request_irq(base->irq, d40_handle_interrupt, 0, D40_NAME, base); - if (ret) { - dev_err(&pdev->dev, "[%s] No IRQ defined\n", __func__); + d40_err(&pdev->dev, "No IRQ defined\n"); goto failure; } @@ -3025,6 +2917,12 @@ failure: kmem_cache_destroy(base->desc_slab); if (base->virtbase) iounmap(base->virtbase); + + if (base->lcla_pool.dma_addr) + dma_unmap_single(base->dev, base->lcla_pool.dma_addr, + SZ_1K * base->num_phy_chans, + DMA_TO_DEVICE); + if (!base->lcla_pool.base_unaligned && base->lcla_pool.base) free_pages((unsigned long)base->lcla_pool.base, base->lcla_pool.pages); @@ -3049,7 +2947,7 @@ failure: kfree(base); } - dev_err(&pdev->dev, "[%s] probe failed\n", __func__); + d40_err(&pdev->dev, "probe failed\n"); return ret; } @@ -3060,7 +2958,7 @@ static struct platform_driver d40_driver = { }, }; -int __init stedma40_init(void) +static int __init stedma40_init(void) { return platform_driver_probe(&d40_driver, d40_probe); } diff --git a/drivers/dma/ste_dma40_ll.c b/drivers/dma/ste_dma40_ll.c index 0b096a38322d..cad9e1daedff 100644 --- a/drivers/dma/ste_dma40_ll.c +++ b/drivers/dma/ste_dma40_ll.c @@ -125,13 +125,15 @@ void d40_phy_cfg(struct stedma40_chan_cfg *cfg, static int d40_phy_fill_lli(struct d40_phy_lli *lli, dma_addr_t data, u32 data_size, - int psize, dma_addr_t next_lli, u32 reg_cfg, - bool term_int, - u32 data_width, - bool is_device) + struct stedma40_half_channel_info *info, + unsigned int flags) { + bool addr_inc = flags & LLI_ADDR_INC; + bool term_int = flags & LLI_TERM_INT; + unsigned int data_width = info->data_width; + int psize = info->psize; int num_elems; if (psize == STEDMA40_PSIZE_PHY_1) @@ -154,7 +156,7 @@ static int d40_phy_fill_lli(struct d40_phy_lli *lli, * Distance to next element sized entry. * Usually the size of the element unless you want gaps. */ - if (!is_device) + if (addr_inc) lli->reg_elt |= (0x1 << data_width) << D40_SREG_ELEM_PHY_EIDX_POS; @@ -198,47 +200,51 @@ static int d40_seg_size(int size, int data_width1, int data_width2) return seg_max; } -struct d40_phy_lli *d40_phy_buf_to_lli(struct d40_phy_lli *lli, - dma_addr_t addr, - u32 size, - int psize, - dma_addr_t lli_phys, - u32 reg_cfg, - bool term_int, - u32 data_width1, - u32 data_width2, - bool is_device) +static struct d40_phy_lli * +d40_phy_buf_to_lli(struct d40_phy_lli *lli, dma_addr_t addr, u32 size, + dma_addr_t lli_phys, dma_addr_t first_phys, u32 reg_cfg, + struct stedma40_half_channel_info *info, + struct stedma40_half_channel_info *otherinfo, + unsigned long flags) { + bool lastlink = flags & LLI_LAST_LINK; + bool addr_inc = flags & LLI_ADDR_INC; + bool term_int = flags & LLI_TERM_INT; + bool cyclic = flags & LLI_CYCLIC; int err; dma_addr_t next = lli_phys; int size_rest = size; int size_seg = 0; + /* + * This piece may be split up based on d40_seg_size(); we only want the + * term int on the last part. + */ + if (term_int) + flags &= ~LLI_TERM_INT; + do { - size_seg = d40_seg_size(size_rest, data_width1, data_width2); + size_seg = d40_seg_size(size_rest, info->data_width, + otherinfo->data_width); size_rest -= size_seg; - if (term_int && size_rest == 0) - next = 0; + if (size_rest == 0 && term_int) + flags |= LLI_TERM_INT; + + if (size_rest == 0 && lastlink) + next = cyclic ? first_phys : 0; else next = ALIGN(next + sizeof(struct d40_phy_lli), D40_LLI_ALIGN); - err = d40_phy_fill_lli(lli, - addr, - size_seg, - psize, - next, - reg_cfg, - !next, - data_width1, - is_device); + err = d40_phy_fill_lli(lli, addr, size_seg, next, + reg_cfg, info, flags); if (err) goto err; lli++; - if (!is_device) + if (addr_inc) addr += size_seg; } while (size_rest); @@ -254,39 +260,35 @@ int d40_phy_sg_to_lli(struct scatterlist *sg, struct d40_phy_lli *lli_sg, dma_addr_t lli_phys, u32 reg_cfg, - u32 data_width1, - u32 data_width2, - int psize) + struct stedma40_half_channel_info *info, + struct stedma40_half_channel_info *otherinfo, + unsigned long flags) { int total_size = 0; int i; struct scatterlist *current_sg = sg; - dma_addr_t dst; struct d40_phy_lli *lli = lli_sg; dma_addr_t l_phys = lli_phys; + if (!target) + flags |= LLI_ADDR_INC; + for_each_sg(sg, current_sg, sg_len, i) { + dma_addr_t sg_addr = sg_dma_address(current_sg); + unsigned int len = sg_dma_len(current_sg); + dma_addr_t dst = target ?: sg_addr; total_size += sg_dma_len(current_sg); - if (target) - dst = target; - else - dst = sg_phys(current_sg); + if (i == sg_len - 1) + flags |= LLI_TERM_INT | LLI_LAST_LINK; l_phys = ALIGN(lli_phys + (lli - lli_sg) * sizeof(struct d40_phy_lli), D40_LLI_ALIGN); - lli = d40_phy_buf_to_lli(lli, - dst, - sg_dma_len(current_sg), - psize, - l_phys, - reg_cfg, - sg_len - 1 == i, - data_width1, - data_width2, - target == dst); + lli = d40_phy_buf_to_lli(lli, dst, len, l_phys, lli_phys, + reg_cfg, info, otherinfo, flags); + if (lli == NULL) return -EINVAL; } @@ -295,45 +297,22 @@ int d40_phy_sg_to_lli(struct scatterlist *sg, } -void d40_phy_lli_write(void __iomem *virtbase, - u32 phy_chan_num, - struct d40_phy_lli *lli_dst, - struct d40_phy_lli *lli_src) -{ - - writel(lli_src->reg_cfg, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSCFG); - writel(lli_src->reg_elt, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSELT); - writel(lli_src->reg_ptr, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSPTR); - writel(lli_src->reg_lnk, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SSLNK); - - writel(lli_dst->reg_cfg, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDCFG); - writel(lli_dst->reg_elt, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDELT); - writel(lli_dst->reg_ptr, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDPTR); - writel(lli_dst->reg_lnk, virtbase + D40_DREG_PCBASE + - phy_chan_num * D40_DREG_PCDELTA + D40_CHAN_REG_SDLNK); - -} - /* DMA logical lli operations */ static void d40_log_lli_link(struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next) + int next, unsigned int flags) { + bool interrupt = flags & LLI_TERM_INT; u32 slos = 0; u32 dlos = 0; if (next != -EINVAL) { slos = next * 2; dlos = next * 2 + 1; - } else { + } + + if (interrupt) { lli_dst->lcsp13 |= D40_MEM_LCSP1_SCFG_TIM_MASK; lli_dst->lcsp13 |= D40_MEM_LCSP3_DTCP_MASK; } @@ -348,9 +327,9 @@ static void d40_log_lli_link(struct d40_log_lli *lli_dst, void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next) + int next, unsigned int flags) { - d40_log_lli_link(lli_dst, lli_src, next); + d40_log_lli_link(lli_dst, lli_src, next, flags); writel(lli_src->lcsp02, &lcpa[0].lcsp0); writel(lli_src->lcsp13, &lcpa[0].lcsp1); @@ -361,9 +340,9 @@ void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, void d40_log_lli_lcla_write(struct d40_log_lli *lcla, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next) + int next, unsigned int flags) { - d40_log_lli_link(lli_dst, lli_src, next); + d40_log_lli_link(lli_dst, lli_src, next, flags); writel(lli_src->lcsp02, &lcla[0].lcsp02); writel(lli_src->lcsp13, &lcla[0].lcsp13); @@ -375,8 +354,10 @@ static void d40_log_fill_lli(struct d40_log_lli *lli, dma_addr_t data, u32 data_size, u32 reg_cfg, u32 data_width, - bool addr_inc) + unsigned int flags) { + bool addr_inc = flags & LLI_ADDR_INC; + lli->lcsp13 = reg_cfg; /* The number of elements to transfer */ @@ -395,67 +376,15 @@ static void d40_log_fill_lli(struct d40_log_lli *lli, } -int d40_log_sg_to_dev(struct scatterlist *sg, - int sg_len, - struct d40_log_lli_bidir *lli, - struct d40_def_lcsp *lcsp, - u32 src_data_width, - u32 dst_data_width, - enum dma_data_direction direction, - dma_addr_t dev_addr) -{ - int total_size = 0; - struct scatterlist *current_sg = sg; - int i; - struct d40_log_lli *lli_src = lli->src; - struct d40_log_lli *lli_dst = lli->dst; - - for_each_sg(sg, current_sg, sg_len, i) { - total_size += sg_dma_len(current_sg); - - if (direction == DMA_TO_DEVICE) { - lli_src = - d40_log_buf_to_lli(lli_src, - sg_phys(current_sg), - sg_dma_len(current_sg), - lcsp->lcsp1, src_data_width, - dst_data_width, - true); - lli_dst = - d40_log_buf_to_lli(lli_dst, - dev_addr, - sg_dma_len(current_sg), - lcsp->lcsp3, dst_data_width, - src_data_width, - false); - } else { - lli_dst = - d40_log_buf_to_lli(lli_dst, - sg_phys(current_sg), - sg_dma_len(current_sg), - lcsp->lcsp3, dst_data_width, - src_data_width, - true); - lli_src = - d40_log_buf_to_lli(lli_src, - dev_addr, - sg_dma_len(current_sg), - lcsp->lcsp1, src_data_width, - dst_data_width, - false); - } - } - return total_size; -} - -struct d40_log_lli *d40_log_buf_to_lli(struct d40_log_lli *lli_sg, +static struct d40_log_lli *d40_log_buf_to_lli(struct d40_log_lli *lli_sg, dma_addr_t addr, int size, u32 lcsp13, /* src or dst*/ u32 data_width1, u32 data_width2, - bool addr_inc) + unsigned int flags) { + bool addr_inc = flags & LLI_ADDR_INC; struct d40_log_lli *lli = lli_sg; int size_rest = size; int size_seg = 0; @@ -468,7 +397,7 @@ struct d40_log_lli *d40_log_buf_to_lli(struct d40_log_lli *lli_sg, addr, size_seg, lcsp13, data_width1, - addr_inc); + flags); if (addr_inc) addr += size_seg; lli++; @@ -479,6 +408,7 @@ struct d40_log_lli *d40_log_buf_to_lli(struct d40_log_lli *lli_sg, int d40_log_sg_to_lli(struct scatterlist *sg, int sg_len, + dma_addr_t dev_addr, struct d40_log_lli *lli_sg, u32 lcsp13, /* src or dst*/ u32 data_width1, u32 data_width2) @@ -487,14 +417,24 @@ int d40_log_sg_to_lli(struct scatterlist *sg, struct scatterlist *current_sg = sg; int i; struct d40_log_lli *lli = lli_sg; + unsigned long flags = 0; + + if (!dev_addr) + flags |= LLI_ADDR_INC; for_each_sg(sg, current_sg, sg_len, i) { + dma_addr_t sg_addr = sg_dma_address(current_sg); + unsigned int len = sg_dma_len(current_sg); + dma_addr_t addr = dev_addr ?: sg_addr; + total_size += sg_dma_len(current_sg); - lli = d40_log_buf_to_lli(lli, - sg_phys(current_sg), - sg_dma_len(current_sg), + + lli = d40_log_buf_to_lli(lli, addr, len, lcsp13, - data_width1, data_width2, true); + data_width1, + data_width2, + flags); } + return total_size; } diff --git a/drivers/dma/ste_dma40_ll.h b/drivers/dma/ste_dma40_ll.h index 9cc43495bea2..195ee65ee7f3 100644 --- a/drivers/dma/ste_dma40_ll.h +++ b/drivers/dma/ste_dma40_ll.h @@ -163,6 +163,22 @@ #define D40_DREG_LCEIS1 0x0B4 #define D40_DREG_LCEIS2 0x0B8 #define D40_DREG_LCEIS3 0x0BC +#define D40_DREG_PSEG1 0x110 +#define D40_DREG_PSEG2 0x114 +#define D40_DREG_PSEG3 0x118 +#define D40_DREG_PSEG4 0x11C +#define D40_DREG_PCEG1 0x120 +#define D40_DREG_PCEG2 0x124 +#define D40_DREG_PCEG3 0x128 +#define D40_DREG_PCEG4 0x12C +#define D40_DREG_RSEG1 0x130 +#define D40_DREG_RSEG2 0x134 +#define D40_DREG_RSEG3 0x138 +#define D40_DREG_RSEG4 0x13C +#define D40_DREG_RCEG1 0x140 +#define D40_DREG_RCEG2 0x144 +#define D40_DREG_RCEG3 0x148 +#define D40_DREG_RCEG4 0x14C #define D40_DREG_STFU 0xFC8 #define D40_DREG_ICFG 0xFCC #define D40_DREG_PERIPHID0 0xFE0 @@ -277,6 +293,13 @@ struct d40_def_lcsp { /* Physical channels */ +enum d40_lli_flags { + LLI_ADDR_INC = 1 << 0, + LLI_TERM_INT = 1 << 1, + LLI_CYCLIC = 1 << 2, + LLI_LAST_LINK = 1 << 3, +}; + void d40_phy_cfg(struct stedma40_chan_cfg *cfg, u32 *src_cfg, u32 *dst_cfg, @@ -292,46 +315,15 @@ int d40_phy_sg_to_lli(struct scatterlist *sg, struct d40_phy_lli *lli, dma_addr_t lli_phys, u32 reg_cfg, - u32 data_width1, - u32 data_width2, - int psize); - -struct d40_phy_lli *d40_phy_buf_to_lli(struct d40_phy_lli *lli, - dma_addr_t data, - u32 data_size, - int psize, - dma_addr_t next_lli, - u32 reg_cfg, - bool term_int, - u32 data_width1, - u32 data_width2, - bool is_device); - -void d40_phy_lli_write(void __iomem *virtbase, - u32 phy_chan_num, - struct d40_phy_lli *lli_dst, - struct d40_phy_lli *lli_src); + struct stedma40_half_channel_info *info, + struct stedma40_half_channel_info *otherinfo, + unsigned long flags); /* Logical channels */ -struct d40_log_lli *d40_log_buf_to_lli(struct d40_log_lli *lli_sg, - dma_addr_t addr, - int size, - u32 lcsp13, /* src or dst*/ - u32 data_width1, u32 data_width2, - bool addr_inc); - -int d40_log_sg_to_dev(struct scatterlist *sg, - int sg_len, - struct d40_log_lli_bidir *lli, - struct d40_def_lcsp *lcsp, - u32 src_data_width, - u32 dst_data_width, - enum dma_data_direction direction, - dma_addr_t dev_addr); - int d40_log_sg_to_lli(struct scatterlist *sg, int sg_len, + dma_addr_t dev_addr, struct d40_log_lli *lli_sg, u32 lcsp13, /* src or dst*/ u32 data_width1, u32 data_width2); @@ -339,11 +331,11 @@ int d40_log_sg_to_lli(struct scatterlist *sg, void d40_log_lli_lcpa_write(struct d40_log_lli_full *lcpa, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next); + int next, unsigned int flags); void d40_log_lli_lcla_write(struct d40_log_lli *lcla, struct d40_log_lli *lli_dst, struct d40_log_lli *lli_src, - int next); + int next, unsigned int flags); #endif /* STE_DMA40_LLI_H */ diff --git a/drivers/firewire/Kconfig b/drivers/firewire/Kconfig index 0c56989cd907..2be6f4520772 100644 --- a/drivers/firewire/Kconfig +++ b/drivers/firewire/Kconfig @@ -75,7 +75,8 @@ config FIREWIRE_NOSY The following cards are known to be based on PCILynx or PCILynx-2: IOI IOI-1394TT (PCI card), Unibrain Fireboard 400 PCI Lynx-2 (PCI card), Newer Technology FireWire 2 Go (CardBus card), - Apple Power Mac G3 blue & white (onboard controller). + Apple Power Mac G3 blue & white and G4 with PCI graphics + (onboard controller). To compile this driver as a module, say M here: The module will be called nosy. Source code of a userspace interface to nosy, called diff --git a/drivers/firewire/core-card.c b/drivers/firewire/core-card.c index 24ff35511e2b..3241dc4e1fc9 100644 --- a/drivers/firewire/core-card.c +++ b/drivers/firewire/core-card.c @@ -233,7 +233,7 @@ static void br_work(struct work_struct *work) /* Delay for 2s after last reset per IEEE 1394 clause 8.2.1. */ if (card->reset_jiffies != 0 && - time_is_after_jiffies(card->reset_jiffies + 2 * HZ)) { + time_before64(get_jiffies_64(), card->reset_jiffies + 2 * HZ)) { if (!schedule_delayed_work(&card->br_work, 2 * HZ)) fw_card_put(card); return; @@ -316,7 +316,8 @@ static void bm_work(struct work_struct *work) irm_id = card->irm_node->node_id; local_id = card->local_node->node_id; - grace = time_after(jiffies, card->reset_jiffies + DIV_ROUND_UP(HZ, 8)); + grace = time_after64(get_jiffies_64(), + card->reset_jiffies + DIV_ROUND_UP(HZ, 8)); if ((is_next_generation(generation, card->bm_generation) && !card->bm_abdicate) || diff --git a/drivers/firewire/core-cdev.c b/drivers/firewire/core-cdev.c index 48ae712e2101..62ac111af243 100644 --- a/drivers/firewire/core-cdev.c +++ b/drivers/firewire/core-cdev.c @@ -64,6 +64,7 @@ struct client { struct idr resource_idr; struct list_head event_list; wait_queue_head_t wait; + wait_queue_head_t tx_flush_wait; u64 bus_reset_closure; struct fw_iso_context *iso_context; @@ -251,6 +252,7 @@ static int fw_device_op_open(struct inode *inode, struct file *file) idr_init(&client->resource_idr); INIT_LIST_HEAD(&client->event_list); init_waitqueue_head(&client->wait); + init_waitqueue_head(&client->tx_flush_wait); INIT_LIST_HEAD(&client->phy_receiver_link); kref_init(&client->kref); @@ -520,10 +522,6 @@ static int release_client_resource(struct client *client, u32 handle, static void release_transaction(struct client *client, struct client_resource *resource) { - struct outbound_transaction_resource *r = container_of(resource, - struct outbound_transaction_resource, resource); - - fw_cancel_transaction(client->device->card, &r->transaction); } static void complete_transaction(struct fw_card *card, int rcode, @@ -540,22 +538,9 @@ static void complete_transaction(struct fw_card *card, int rcode, memcpy(rsp->data, payload, rsp->length); spin_lock_irqsave(&client->lock, flags); - /* - * 1. If called while in shutdown, the idr tree must be left untouched. - * The idr handle will be removed and the client reference will be - * dropped later. - * 2. If the call chain was release_client_resource -> - * release_transaction -> complete_transaction (instead of a normal - * conclusion of the transaction), i.e. if this resource was already - * unregistered from the idr, the client reference will be dropped - * by release_client_resource and we must not drop it here. - */ - if (!client->in_shutdown && - idr_find(&client->resource_idr, e->r.resource.handle)) { - idr_remove(&client->resource_idr, e->r.resource.handle); - /* Drop the idr's reference */ - client_put(client); - } + idr_remove(&client->resource_idr, e->r.resource.handle); + if (client->in_shutdown) + wake_up(&client->tx_flush_wait); spin_unlock_irqrestore(&client->lock, flags); rsp->type = FW_CDEV_EVENT_RESPONSE; @@ -575,7 +560,7 @@ static void complete_transaction(struct fw_card *card, int rcode, queue_event(client, &e->event, rsp, sizeof(*rsp) + rsp->length, NULL, 0); - /* Drop the transaction callback's reference */ + /* Drop the idr's reference */ client_put(client); } @@ -614,9 +599,6 @@ static int init_request(struct client *client, if (ret < 0) goto failed; - /* Get a reference for the transaction callback */ - client_get(client); - fw_send_request(client->device->card, &e->r.transaction, request->tcode, destination_id, request->generation, speed, request->offset, e->response.data, @@ -1223,7 +1205,8 @@ static void iso_resource_work(struct work_struct *work) todo = r->todo; /* Allow 1000ms grace period for other reallocations. */ if (todo == ISO_RES_ALLOC && - time_is_after_jiffies(client->device->card->reset_jiffies + HZ)) { + time_before64(get_jiffies_64(), + client->device->card->reset_jiffies + HZ)) { schedule_iso_resource(r, DIV_ROUND_UP(HZ, 3)); skip = true; } else { @@ -1678,6 +1661,25 @@ static int fw_device_op_mmap(struct file *file, struct vm_area_struct *vma) return ret; } +static int is_outbound_transaction_resource(int id, void *p, void *data) +{ + struct client_resource *resource = p; + + return resource->release == release_transaction; +} + +static int has_outbound_transactions(struct client *client) +{ + int ret; + + spin_lock_irq(&client->lock); + ret = idr_for_each(&client->resource_idr, + is_outbound_transaction_resource, NULL); + spin_unlock_irq(&client->lock); + + return ret; +} + static int shutdown_resource(int id, void *p, void *data) { struct client_resource *resource = p; @@ -1713,6 +1715,8 @@ static int fw_device_op_release(struct inode *inode, struct file *file) client->in_shutdown = true; spin_unlock_irq(&client->lock); + wait_event(client->tx_flush_wait, !has_outbound_transactions(client)); + idr_for_each(&client->resource_idr, shutdown_resource, client); idr_remove_all(&client->resource_idr); idr_destroy(&client->resource_idr); diff --git a/drivers/firewire/core-device.c b/drivers/firewire/core-device.c index 6113b896e790..57461923bacf 100644 --- a/drivers/firewire/core-device.c +++ b/drivers/firewire/core-device.c @@ -747,7 +747,8 @@ static void fw_device_shutdown(struct work_struct *work) container_of(work, struct fw_device, work.work); int minor = MINOR(device->device.devt); - if (time_is_after_jiffies(device->card->reset_jiffies + SHUTDOWN_DELAY) + if (time_before64(get_jiffies_64(), + device->card->reset_jiffies + SHUTDOWN_DELAY) && !list_empty(&device->card->link)) { schedule_delayed_work(&device->work, SHUTDOWN_DELAY); return; diff --git a/drivers/firewire/core-iso.c b/drivers/firewire/core-iso.c index c003fa4e2db1..95bc01a02a88 100644 --- a/drivers/firewire/core-iso.c +++ b/drivers/firewire/core-iso.c @@ -235,45 +235,45 @@ static int manage_bandwidth(struct fw_card *card, int irm_id, int generation, static int manage_channel(struct fw_card *card, int irm_id, int generation, u32 channels_mask, u64 offset, bool allocate, __be32 data[2]) { - __be32 c, all, old; - int i, ret = -EIO, retry = 5; + __be32 bit, all, old; + int channel, ret = -EIO, retry = 5; old = all = allocate ? cpu_to_be32(~0) : 0; - for (i = 0; i < 32; i++) { - if (!(channels_mask & 1 << i)) + for (channel = 0; channel < 32; channel++) { + if (!(channels_mask & 1 << channel)) continue; ret = -EBUSY; - c = cpu_to_be32(1 << (31 - i)); - if ((old & c) != (all & c)) + bit = cpu_to_be32(1 << (31 - channel)); + if ((old & bit) != (all & bit)) continue; data[0] = old; - data[1] = old ^ c; + data[1] = old ^ bit; switch (fw_run_transaction(card, TCODE_LOCK_COMPARE_SWAP, irm_id, generation, SCODE_100, offset, data, 8)) { case RCODE_GENERATION: /* A generation change frees all channels. */ - return allocate ? -EAGAIN : i; + return allocate ? -EAGAIN : channel; case RCODE_COMPLETE: if (data[0] == old) - return i; + return channel; old = data[0]; /* Is the IRM 1394a-2000 compliant? */ - if ((data[0] & c) == (data[1] & c)) + if ((data[0] & bit) == (data[1] & bit)) continue; /* 1394-1995 IRM, fall through to retry. */ default: if (retry) { retry--; - i--; + channel--; } else { ret = -EIO; } diff --git a/drivers/firewire/core-topology.c b/drivers/firewire/core-topology.c index 09be1a635505..193ed9233144 100644 --- a/drivers/firewire/core-topology.c +++ b/drivers/firewire/core-topology.c @@ -545,7 +545,7 @@ void fw_core_handle_bus_reset(struct fw_card *card, int node_id, int generation, */ smp_wmb(); card->generation = generation; - card->reset_jiffies = jiffies; + card->reset_jiffies = get_jiffies_64(); card->bm_node_id = 0xffff; card->bm_abdicate = bm_abdicate; fw_schedule_bm_work(card, 0); diff --git a/drivers/firewire/ohci.c b/drivers/firewire/ohci.c index bd3c61b6dd8d..c0572283b93e 100644 --- a/drivers/firewire/ohci.c +++ b/drivers/firewire/ohci.c @@ -208,9 +208,11 @@ struct fw_ohci { struct context at_request_ctx; struct context at_response_ctx; + u32 it_context_support; u32 it_context_mask; /* unoccupied IT contexts */ struct iso_context *it_context_list; u64 ir_context_channels; /* unoccupied channels */ + u32 ir_context_support; u32 ir_context_mask; /* unoccupied IR contexts */ struct iso_context *ir_context_list; u64 mc_channels; /* channels in use by the multichannel IR context */ @@ -338,7 +340,7 @@ static void log_irqs(u32 evt) !(evt & OHCI1394_busReset)) return; - fw_notify("IRQ %08x%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", evt, + fw_notify("IRQ %08x%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n", evt, evt & OHCI1394_selfIDComplete ? " selfID" : "", evt & OHCI1394_RQPkt ? " AR_req" : "", evt & OHCI1394_RSPkt ? " AR_resp" : "", @@ -351,6 +353,7 @@ static void log_irqs(u32 evt) evt & OHCI1394_cycle64Seconds ? " cycle64Seconds" : "", evt & OHCI1394_cycleInconsistent ? " cycleInconsistent" : "", evt & OHCI1394_regAccessFail ? " regAccessFail" : "", + evt & OHCI1394_unrecoverableError ? " unrecoverableError" : "", evt & OHCI1394_busReset ? " busReset" : "", evt & ~(OHCI1394_selfIDComplete | OHCI1394_RQPkt | OHCI1394_RSPkt | OHCI1394_reqTxComplete | @@ -1590,6 +1593,47 @@ static void at_context_transmit(struct context *ctx, struct fw_packet *packet) } +static void detect_dead_context(struct fw_ohci *ohci, + const char *name, unsigned int regs) +{ + u32 ctl; + + ctl = reg_read(ohci, CONTROL_SET(regs)); + if (ctl & CONTEXT_DEAD) { +#ifdef CONFIG_FIREWIRE_OHCI_DEBUG + fw_error("DMA context %s has stopped, error code: %s\n", + name, evts[ctl & 0x1f]); +#else + fw_error("DMA context %s has stopped, error code: %#x\n", + name, ctl & 0x1f); +#endif + } +} + +static void handle_dead_contexts(struct fw_ohci *ohci) +{ + unsigned int i; + char name[8]; + + detect_dead_context(ohci, "ATReq", OHCI1394_AsReqTrContextBase); + detect_dead_context(ohci, "ATRsp", OHCI1394_AsRspTrContextBase); + detect_dead_context(ohci, "ARReq", OHCI1394_AsReqRcvContextBase); + detect_dead_context(ohci, "ARRsp", OHCI1394_AsRspRcvContextBase); + for (i = 0; i < 32; ++i) { + if (!(ohci->it_context_support & (1 << i))) + continue; + sprintf(name, "IT%u", i); + detect_dead_context(ohci, name, OHCI1394_IsoXmitContextBase(i)); + } + for (i = 0; i < 32; ++i) { + if (!(ohci->ir_context_support & (1 << i))) + continue; + sprintf(name, "IR%u", i); + detect_dead_context(ohci, name, OHCI1394_IsoRcvContextBase(i)); + } + /* TODO: maybe try to flush and restart the dead contexts */ +} + static u32 cycle_timer_ticks(u32 cycle_timer) { u32 ticks; @@ -1904,6 +1948,9 @@ static irqreturn_t irq_handler(int irq, void *data) fw_notify("isochronous cycle inconsistent\n"); } + if (unlikely(event & OHCI1394_unrecoverableError)) + handle_dead_contexts(ohci); + if (event & OHCI1394_cycle64Seconds) { spin_lock(&ohci->lock); update_bus_time(ohci); @@ -2141,7 +2188,9 @@ static int ohci_enable(struct fw_card *card, OHCI1394_selfIDComplete | OHCI1394_regAccessFail | OHCI1394_cycle64Seconds | - OHCI1394_cycleInconsistent | OHCI1394_cycleTooLong | + OHCI1394_cycleInconsistent | + OHCI1394_unrecoverableError | + OHCI1394_cycleTooLong | OHCI1394_masterIntEnable; if (param_debug & OHCI_PARAM_DEBUG_BUSRESETS) irqs |= OHCI1394_busReset; @@ -2657,6 +2706,10 @@ static int ohci_start_iso(struct fw_iso_context *base, u32 control = IR_CONTEXT_ISOCH_HEADER, match; int index; + /* the controller cannot start without any queued packets */ + if (ctx->context.last->branch_address == 0) + return -ENODATA; + switch (ctx->base.type) { case FW_ISO_CONTEXT_TRANSMIT: index = ctx - ohci->it_context_list; @@ -2715,6 +2768,7 @@ static int ohci_stop_iso(struct fw_iso_context *base) } flush_writes(ohci); context_stop(&ctx->context); + tasklet_kill(&ctx->context.tasklet); return 0; } @@ -3207,15 +3261,17 @@ static int __devinit pci_probe(struct pci_dev *dev, reg_write(ohci, OHCI1394_IsoRecvIntMaskSet, ~0); ohci->ir_context_channels = ~0ULL; - ohci->ir_context_mask = reg_read(ohci, OHCI1394_IsoRecvIntMaskSet); + ohci->ir_context_support = reg_read(ohci, OHCI1394_IsoRecvIntMaskSet); reg_write(ohci, OHCI1394_IsoRecvIntMaskClear, ~0); + ohci->ir_context_mask = ohci->ir_context_support; ohci->n_ir = hweight32(ohci->ir_context_mask); size = sizeof(struct iso_context) * ohci->n_ir; ohci->ir_context_list = kzalloc(size, GFP_KERNEL); reg_write(ohci, OHCI1394_IsoXmitIntMaskSet, ~0); - ohci->it_context_mask = reg_read(ohci, OHCI1394_IsoXmitIntMaskSet); + ohci->it_context_support = reg_read(ohci, OHCI1394_IsoXmitIntMaskSet); reg_write(ohci, OHCI1394_IsoXmitIntMaskClear, ~0); + ohci->it_context_mask = ohci->it_context_support; ohci->n_it = hweight32(ohci->it_context_mask); size = sizeof(struct iso_context) * ohci->n_it; ohci->it_context_list = kzalloc(size, GFP_KERNEL); diff --git a/drivers/gpio/pl061.c b/drivers/gpio/pl061.c index 2975d22daffe..838ddbdf90cc 100644 --- a/drivers/gpio/pl061.c +++ b/drivers/gpio/pl061.c @@ -232,7 +232,7 @@ static void pl061_irq_handler(unsigned irq, struct irq_desc *desc) desc->irq_data.chip->irq_unmask(&desc->irq_data); } -static int pl061_probe(struct amba_device *dev, struct amba_id *id) +static int pl061_probe(struct amba_device *dev, const struct amba_id *id) { struct pl061_platform_data *pdata; struct pl061_gpio *chip; diff --git a/drivers/gpu/drm/drm_irq.c b/drivers/gpu/drm/drm_irq.c index 3dadfa2a8528..53120a72a48c 100644 --- a/drivers/gpu/drm/drm_irq.c +++ b/drivers/gpu/drm/drm_irq.c @@ -164,8 +164,10 @@ static void vblank_disable_and_save(struct drm_device *dev, int crtc) * available. In that case we can't account for this and just * hope for the best. */ - if ((vblrc > 0) && (abs(diff_ns) > 1000000)) + if ((vblrc > 0) && (abs64(diff_ns) > 1000000)) { atomic_inc(&dev->_vblank_count[crtc]); + smp_mb__after_atomic_inc(); + } /* Invalidate all timestamps while vblank irq's are off. */ clear_vblank_timestamps(dev, crtc); @@ -491,6 +493,12 @@ void drm_calc_timestamping_constants(struct drm_crtc *crtc) /* Dot clock in Hz: */ dotclock = (u64) crtc->hwmode.clock * 1000; + /* Fields of interlaced scanout modes are only halve a frame duration. + * Double the dotclock to get halve the frame-/line-/pixelduration. + */ + if (crtc->hwmode.flags & DRM_MODE_FLAG_INTERLACE) + dotclock *= 2; + /* Valid dotclock? */ if (dotclock > 0) { /* Convert scanline length in pixels and video dot clock to @@ -603,14 +611,6 @@ int drm_calc_vbltimestamp_from_scanoutpos(struct drm_device *dev, int crtc, return -EAGAIN; } - /* Don't know yet how to handle interlaced or - * double scan modes. Just no-op for now. - */ - if (mode->flags & (DRM_MODE_FLAG_INTERLACE | DRM_MODE_FLAG_DBLSCAN)) { - DRM_DEBUG("crtc %d: Noop due to unsupported mode.\n", crtc); - return -ENOTSUPP; - } - /* Get current scanout position with system timestamp. * Repeat query up to DRM_TIMESTAMP_MAXRETRIES times * if single query takes longer than max_error nanoseconds. @@ -858,10 +858,11 @@ static void drm_update_vblank_count(struct drm_device *dev, int crtc) if (rc) { tslot = atomic_read(&dev->_vblank_count[crtc]) + diff; vblanktimestamp(dev, crtc, tslot) = t_vblank; - smp_wmb(); } + smp_mb__before_atomic_inc(); atomic_add(diff, &dev->_vblank_count[crtc]); + smp_mb__after_atomic_inc(); } /** @@ -1293,15 +1294,16 @@ bool drm_handle_vblank(struct drm_device *dev, int crtc) * e.g., due to spurious vblank interrupts. We need to * ignore those for accounting. */ - if (abs(diff_ns) > DRM_REDUNDANT_VBLIRQ_THRESH_NS) { + if (abs64(diff_ns) > DRM_REDUNDANT_VBLIRQ_THRESH_NS) { /* Store new timestamp in ringbuffer. */ vblanktimestamp(dev, crtc, vblcount + 1) = tvblank; - smp_wmb(); /* Increment cooked vblank count. This also atomically commits * the timestamp computed above. */ + smp_mb__before_atomic_inc(); atomic_inc(&dev->_vblank_count[crtc]); + smp_mb__after_atomic_inc(); } else { DRM_DEBUG("crtc %d: Redundant vblirq ignored. diff_ns = %d\n", crtc, (int) diff_ns); diff --git a/drivers/gpu/drm/i915/i915_gem_tiling.c b/drivers/gpu/drm/i915/i915_gem_tiling.c index 22a32b9932c5..79a04fde69b5 100644 --- a/drivers/gpu/drm/i915/i915_gem_tiling.c +++ b/drivers/gpu/drm/i915/i915_gem_tiling.c @@ -184,7 +184,7 @@ i915_gem_detect_bit_6_swizzle(struct drm_device *dev) static bool i915_tiling_ok(struct drm_device *dev, int stride, int size, int tiling_mode) { - int tile_width; + int tile_width, tile_height; /* Linear is always fine */ if (tiling_mode == I915_TILING_NONE) @@ -215,6 +215,20 @@ i915_tiling_ok(struct drm_device *dev, int stride, int size, int tiling_mode) } } + if (IS_GEN2(dev) || + (tiling_mode == I915_TILING_Y && HAS_128_BYTE_Y_TILING(dev))) + tile_height = 32; + else + tile_height = 8; + /* i8xx is strange: It has 2 interleaved rows of tiles, so needs an even + * number of tile rows. */ + if (IS_GEN2(dev)) + tile_height *= 2; + + /* Size needs to be aligned to a full tile row */ + if (size & (tile_height * stride - 1)) + return false; + /* 965+ just needs multiples of tile width */ if (INTEL_INFO(dev)->gen >= 4) { if (stride & (tile_width - 1)) diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c index 97f946dcc1aa..8a9e08bf1cf7 100644 --- a/drivers/gpu/drm/i915/i915_irq.c +++ b/drivers/gpu/drm/i915/i915_irq.c @@ -316,6 +316,8 @@ static void i915_hotplug_work_func(struct work_struct *work) struct drm_mode_config *mode_config = &dev->mode_config; struct intel_encoder *encoder; + DRM_DEBUG_KMS("running encoder hotplug functions\n"); + list_for_each_entry(encoder, &mode_config->encoder_list, base.head) if (encoder->hot_plug) encoder->hot_plug(encoder); @@ -1649,9 +1651,7 @@ static int ironlake_irq_postinstall(struct drm_device *dev) } else { hotplug_mask = SDE_CRT_HOTPLUG | SDE_PORTB_HOTPLUG | SDE_PORTC_HOTPLUG | SDE_PORTD_HOTPLUG; - hotplug_mask |= SDE_AUX_MASK | SDE_FDI_MASK | SDE_TRANS_MASK; - I915_WRITE(FDI_RXA_IMR, 0); - I915_WRITE(FDI_RXB_IMR, 0); + hotplug_mask |= SDE_AUX_MASK; } dev_priv->pch_irq_mask = ~hotplug_mask; diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 15d94c63918c..729d4233b763 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -1553,17 +1553,7 @@ /* Backlight control */ #define BLC_PWM_CTL 0x61254 -#define BACKLIGHT_MODULATION_FREQ_SHIFT (17) #define BLC_PWM_CTL2 0x61250 /* 965+ only */ -#define BLM_COMBINATION_MODE (1 << 30) -/* - * This is the most significant 15 bits of the number of backlight cycles in a - * complete cycle of the modulated backlight control. - * - * The actual value is this field multiplied by two. - */ -#define BACKLIGHT_MODULATION_FREQ_MASK (0x7fff << 17) -#define BLM_LEGACY_MODE (1 << 16) /* * This is the number of cycles out of the backlight modulation cycle for which * the backlight is on. diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index 3b006536b3d2..e79b25bbee6c 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -1630,19 +1630,19 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, struct drm_i915_gem_object *obj = to_intel_framebuffer(old_fb)->obj; wait_event(dev_priv->pending_flip_queue, + atomic_read(&dev_priv->mm.wedged) || atomic_read(&obj->pending_flip) == 0); /* Big Hammer, we also need to ensure that any pending * MI_WAIT_FOR_EVENT inside a user batch buffer on the * current scanout is retired before unpinning the old * framebuffer. + * + * This should only fail upon a hung GPU, in which case we + * can safely continue. */ ret = i915_gem_object_flush_gpu(obj, false); - if (ret) { - i915_gem_object_unpin(to_intel_framebuffer(crtc->fb)->obj); - mutex_unlock(&dev->struct_mutex); - return ret; - } + (void) ret; } ret = intel_pipe_set_base_atomic(crtc, crtc->fb, x, y, @@ -2045,6 +2045,31 @@ static void intel_crtc_wait_for_pending_flips(struct drm_crtc *crtc) atomic_read(&obj->pending_flip) == 0); } +static bool intel_crtc_driving_pch(struct drm_crtc *crtc) +{ + struct drm_device *dev = crtc->dev; + struct drm_mode_config *mode_config = &dev->mode_config; + struct intel_encoder *encoder; + + /* + * If there's a non-PCH eDP on this crtc, it must be DP_A, and that + * must be driven by its own crtc; no sharing is possible. + */ + list_for_each_entry(encoder, &mode_config->encoder_list, base.head) { + if (encoder->base.crtc != crtc) + continue; + + switch (encoder->type) { + case INTEL_OUTPUT_EDP: + if (!intel_encoder_is_pch_edp(&encoder->base)) + return false; + continue; + } + } + + return true; +} + static void ironlake_crtc_enable(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; @@ -2053,6 +2078,7 @@ static void ironlake_crtc_enable(struct drm_crtc *crtc) int pipe = intel_crtc->pipe; int plane = intel_crtc->plane; u32 reg, temp; + bool is_pch_port = false; if (intel_crtc->active) return; @@ -2066,7 +2092,56 @@ static void ironlake_crtc_enable(struct drm_crtc *crtc) I915_WRITE(PCH_LVDS, temp | LVDS_PORT_EN); } - ironlake_fdi_enable(crtc); + is_pch_port = intel_crtc_driving_pch(crtc); + + if (is_pch_port) + ironlake_fdi_enable(crtc); + else { + /* disable CPU FDI tx and PCH FDI rx */ + reg = FDI_TX_CTL(pipe); + temp = I915_READ(reg); + I915_WRITE(reg, temp & ~FDI_TX_ENABLE); + POSTING_READ(reg); + + reg = FDI_RX_CTL(pipe); + temp = I915_READ(reg); + temp &= ~(0x7 << 16); + temp |= (I915_READ(PIPECONF(pipe)) & PIPE_BPC_MASK) << 11; + I915_WRITE(reg, temp & ~FDI_RX_ENABLE); + + POSTING_READ(reg); + udelay(100); + + /* Ironlake workaround, disable clock pointer after downing FDI */ + if (HAS_PCH_IBX(dev)) + I915_WRITE(FDI_RX_CHICKEN(pipe), + I915_READ(FDI_RX_CHICKEN(pipe) & + ~FDI_RX_PHASE_SYNC_POINTER_ENABLE)); + + /* still set train pattern 1 */ + reg = FDI_TX_CTL(pipe); + temp = I915_READ(reg); + temp &= ~FDI_LINK_TRAIN_NONE; + temp |= FDI_LINK_TRAIN_PATTERN_1; + I915_WRITE(reg, temp); + + reg = FDI_RX_CTL(pipe); + temp = I915_READ(reg); + if (HAS_PCH_CPT(dev)) { + temp &= ~FDI_LINK_TRAIN_PATTERN_MASK_CPT; + temp |= FDI_LINK_TRAIN_PATTERN_1_CPT; + } else { + temp &= ~FDI_LINK_TRAIN_NONE; + temp |= FDI_LINK_TRAIN_PATTERN_1; + } + /* BPC in FDI rx is consistent with that in PIPECONF */ + temp &= ~(0x07 << 16); + temp |= (I915_READ(PIPECONF(pipe)) & PIPE_BPC_MASK) << 11; + I915_WRITE(reg, temp); + + POSTING_READ(reg); + udelay(100); + } /* Enable panel fitting for LVDS */ if (dev_priv->pch_pf_size && @@ -2100,6 +2175,10 @@ static void ironlake_crtc_enable(struct drm_crtc *crtc) intel_flush_display_plane(dev, plane); } + /* Skip the PCH stuff if possible */ + if (!is_pch_port) + goto done; + /* For PCH output, training FDI link */ if (IS_GEN6(dev)) gen6_fdi_link_train(crtc); @@ -2184,7 +2263,7 @@ static void ironlake_crtc_enable(struct drm_crtc *crtc) I915_WRITE(reg, temp | TRANS_ENABLE); if (wait_for(I915_READ(reg) & TRANS_STATE_ENABLE, 100)) DRM_ERROR("failed to enable transcoder %d\n", pipe); - +done: intel_crtc_load_lut(crtc); intel_update_fbc(dev); intel_crtc_update_cursor(crtc, true); @@ -6496,7 +6575,7 @@ static void ironlake_disable_rc6(struct drm_device *dev) POSTING_READ(RSTDBYCTL); } - ironlake_disable_rc6(dev); + ironlake_teardown_rc6(dev); } static int ironlake_setup_rc6(struct drm_device *dev) diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c index c65992df458d..d860abeda70f 100644 --- a/drivers/gpu/drm/i915/intel_panel.c +++ b/drivers/gpu/drm/i915/intel_panel.c @@ -30,8 +30,6 @@ #include "intel_drv.h" -#define PCI_LBPC 0xf4 /* legacy/combination backlight modes */ - void intel_fixed_panel_mode(struct drm_display_mode *fixed_mode, struct drm_display_mode *adjusted_mode) @@ -112,19 +110,6 @@ done: dev_priv->pch_pf_size = (width << 16) | height; } -static int is_backlight_combination_mode(struct drm_device *dev) -{ - struct drm_i915_private *dev_priv = dev->dev_private; - - if (INTEL_INFO(dev)->gen >= 4) - return I915_READ(BLC_PWM_CTL2) & BLM_COMBINATION_MODE; - - if (IS_GEN2(dev)) - return I915_READ(BLC_PWM_CTL) & BLM_LEGACY_MODE; - - return 0; -} - static u32 i915_read_blc_pwm_ctl(struct drm_i915_private *dev_priv) { u32 val; @@ -181,9 +166,6 @@ u32 intel_panel_get_max_backlight(struct drm_device *dev) if (INTEL_INFO(dev)->gen < 4) max &= ~1; } - - if (is_backlight_combination_mode(dev)) - max *= 0xff; } DRM_DEBUG_DRIVER("max backlight PWM = %d\n", max); @@ -201,15 +183,6 @@ u32 intel_panel_get_backlight(struct drm_device *dev) val = I915_READ(BLC_PWM_CTL) & BACKLIGHT_DUTY_CYCLE_MASK; if (IS_PINEVIEW(dev)) val >>= 1; - - if (is_backlight_combination_mode(dev)){ - u8 lbpc; - - val &= ~1; - pci_read_config_byte(dev->pdev, PCI_LBPC, &lbpc); - val *= lbpc; - val >>= 1; - } } DRM_DEBUG_DRIVER("get backlight PWM = %d\n", val); @@ -232,16 +205,6 @@ void intel_panel_set_backlight(struct drm_device *dev, u32 level) if (HAS_PCH_SPLIT(dev)) return intel_pch_panel_set_backlight(dev, level); - - if (is_backlight_combination_mode(dev)){ - u32 max = intel_panel_get_max_backlight(dev); - u8 lpbc; - - lpbc = level * 0xfe / max + 1; - level /= lpbc; - pci_write_config_byte(dev->pdev, PCI_LBPC, lpbc); - } - tmp = I915_READ(BLC_PWM_CTL); if (IS_PINEVIEW(dev)) { tmp &= ~(BACKLIGHT_DUTY_CYCLE_MASK - 1); diff --git a/drivers/gpu/drm/radeon/r100.c b/drivers/gpu/drm/radeon/r100.c index 56deae5bf02e..93fa735c8c1a 100644 --- a/drivers/gpu/drm/radeon/r100.c +++ b/drivers/gpu/drm/radeon/r100.c @@ -3490,7 +3490,7 @@ void r100_cs_track_clear(struct radeon_device *rdev, struct r100_cs_track *track track->num_texture = 16; track->maxy = 4096; track->separate_cube = 0; - track->aaresolve = true; + track->aaresolve = false; track->aa.robj = NULL; } @@ -3801,8 +3801,6 @@ static int r100_startup(struct radeon_device *rdev) r100_mc_program(rdev); /* Resume clock */ r100_clock_startup(rdev); - /* Initialize GPU configuration (# pipes, ...) */ -// r100_gpu_init(rdev); /* Initialize GART (initialize after TTM so we can allocate * memory through TTM but finalize after TTM) */ r100_enable_bm(rdev); diff --git a/drivers/gpu/drm/radeon/radeon_display.c b/drivers/gpu/drm/radeon/radeon_display.c index 0e657095de7c..3e7e7f9eb781 100644 --- a/drivers/gpu/drm/radeon/radeon_display.c +++ b/drivers/gpu/drm/radeon/radeon_display.c @@ -971,7 +971,7 @@ void radeon_compute_pll_legacy(struct radeon_pll *pll, max_fractional_feed_div = pll->max_frac_feedback_div; } - for (post_div = min_post_div; post_div <= max_post_div; ++post_div) { + for (post_div = max_post_div; post_div >= min_post_div; --post_div) { uint32_t ref_div; if ((pll->flags & RADEON_PLL_NO_ODD_POST_DIV) && (post_div & 1)) diff --git a/drivers/gpu/drm/radeon/radeon_fb.c b/drivers/gpu/drm/radeon/radeon_fb.c index 66324b5bb5ba..cc44bdfec80f 100644 --- a/drivers/gpu/drm/radeon/radeon_fb.c +++ b/drivers/gpu/drm/radeon/radeon_fb.c @@ -113,11 +113,14 @@ static int radeonfb_create_pinned_object(struct radeon_fbdev *rfbdev, u32 tiling_flags = 0; int ret; int aligned_size, size; + int height = mode_cmd->height; /* need to align pitch with crtc limits */ mode_cmd->pitch = radeon_align_pitch(rdev, mode_cmd->width, mode_cmd->bpp, fb_tiled) * ((mode_cmd->bpp + 1) / 8); - size = mode_cmd->pitch * mode_cmd->height; + if (rdev->family >= CHIP_R600) + height = ALIGN(mode_cmd->height, 8); + size = mode_cmd->pitch * height; aligned_size = ALIGN(size, PAGE_SIZE); ret = radeon_gem_object_create(rdev, aligned_size, 0, RADEON_GEM_DOMAIN_VRAM, diff --git a/drivers/hid/Kconfig b/drivers/hid/Kconfig index 2560f01c1a63..4b88bbe9ed07 100644 --- a/drivers/hid/Kconfig +++ b/drivers/hid/Kconfig @@ -180,6 +180,14 @@ config HID_EZKEY ---help--- Support for Ezkey BTC 8193 keyboard. +config HID_KEYTOUCH + tristate "Keyoutch HID devices" + depends on USB_HID + ---help--- + Support for Keytouch HID devices not fully compliant with + the specification. Currently supported: + - Keytouch IEC 60945 + config HID_KYE tristate "Kye/Genius Ergo Mouse" if EXPERT depends on USB_HID @@ -218,6 +226,12 @@ config HID_KENSINGTON ---help--- Support for Kensington Slimblade Trackball. +config HID_LCPOWER + tristate "LC-Power" + depends on USB_HID + ---help--- + Support for LC-Power RC1000MCE RF remote control. + config HID_LOGITECH tristate "Logitech devices" if EXPERT depends on USB_HID @@ -304,6 +318,7 @@ config HID_MULTITOUCH Say Y here if you have one of the following devices: - Cypress TrueTouch panels - Hanvon dual touch panels + - IrTouch Infrared USB panels - Pixcir dual touch panels - 'Sensing Win7-TwoFinger' panel by GeneralTouch @@ -417,10 +432,22 @@ config HID_ROCCAT Say Y here if you have a Roccat mouse or keyboard and want OSD or macro execution support. +config HID_ROCCAT_COMMON + tristate + +config HID_ROCCAT_ARVO + tristate "Roccat Arvo keyboard support" + depends on USB_HID + select HID_ROCCAT + select HID_ROCCAT_COMMON + ---help--- + Support for Roccat Arvo keyboard. + config HID_ROCCAT_KONE tristate "Roccat Kone Mouse support" depends on USB_HID select HID_ROCCAT + select HID_ROCCAT_COMMON ---help--- Support for Roccat Kone mouse. @@ -428,13 +455,23 @@ config HID_ROCCAT_KONEPLUS tristate "Roccat Kone[+] mouse support" depends on USB_HID select HID_ROCCAT + select HID_ROCCAT_COMMON ---help--- Support for Roccat Kone[+] mouse. +config HID_ROCCAT_KOVAPLUS + tristate "Roccat Kova[+] mouse support" + depends on USB_HID + select HID_ROCCAT + select HID_ROCCAT_COMMON + ---help--- + Support for Roccat Kova[+] mouse. + config HID_ROCCAT_PYRA tristate "Roccat Pyra mouse support" depends on USB_HID select HID_ROCCAT + select HID_ROCCAT_COMMON ---help--- Support for Roccat Pyra mouse. diff --git a/drivers/hid/Makefile b/drivers/hid/Makefile index 6efc2a0370ad..04496abc7b76 100644 --- a/drivers/hid/Makefile +++ b/drivers/hid/Makefile @@ -41,7 +41,9 @@ obj-$(CONFIG_HID_ELECOM) += hid-elecom.o obj-$(CONFIG_HID_EZKEY) += hid-ezkey.o obj-$(CONFIG_HID_GYRATION) += hid-gyration.o obj-$(CONFIG_HID_KENSINGTON) += hid-kensington.o +obj-$(CONFIG_HID_KEYTOUCH) += hid-keytouch.o obj-$(CONFIG_HID_KYE) += hid-kye.o +obj-$(CONFIG_HID_LCPOWER) += hid-lcpower.o obj-$(CONFIG_HID_LOGITECH) += hid-logitech.o obj-$(CONFIG_HID_MAGICMOUSE) += hid-magicmouse.o obj-$(CONFIG_HID_MICROSOFT) += hid-microsoft.o @@ -56,8 +58,11 @@ obj-$(CONFIG_HID_PANTHERLORD) += hid-pl.o obj-$(CONFIG_HID_PETALYNX) += hid-petalynx.o obj-$(CONFIG_HID_PICOLCD) += hid-picolcd.o obj-$(CONFIG_HID_ROCCAT) += hid-roccat.o +obj-$(CONFIG_HID_ROCCAT_COMMON) += hid-roccat-common.o +obj-$(CONFIG_HID_ROCCAT_ARVO) += hid-roccat-arvo.o obj-$(CONFIG_HID_ROCCAT_KONE) += hid-roccat-kone.o obj-$(CONFIG_HID_ROCCAT_KONEPLUS) += hid-roccat-koneplus.o +obj-$(CONFIG_HID_ROCCAT_KOVAPLUS) += hid-roccat-kovaplus.o obj-$(CONFIG_HID_ROCCAT_PYRA) += hid-roccat-pyra.o obj-$(CONFIG_HID_SAMSUNG) += hid-samsung.o obj-$(CONFIG_HID_SMARTJOYPLUS) += hid-sjoy.o diff --git a/drivers/hid/hid-core.c b/drivers/hid/hid-core.c index d678cf3d33d5..7d7034af4ca0 100644 --- a/drivers/hid/hid-core.c +++ b/drivers/hid/hid-core.c @@ -1159,6 +1159,32 @@ static bool hid_hiddev(struct hid_device *hdev) return !!hid_match_id(hdev, hid_hiddev_list); } + +static ssize_t +read_report_descriptor(struct file *filp, struct kobject *kobj, + struct bin_attribute *attr, + char *buf, loff_t off, size_t count) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct hid_device *hdev = container_of(dev, struct hid_device, dev); + + if (off >= hdev->rsize) + return 0; + + if (off + count > hdev->rsize) + count = hdev->rsize - off; + + memcpy(buf, hdev->rdesc + off, count); + + return count; +} + +static struct bin_attribute dev_bin_attr_report_desc = { + .attr = { .name = "report_descriptor", .mode = 0444 }, + .read = read_report_descriptor, + .size = HID_MAX_DESCRIPTOR_SIZE, +}; + int hid_connect(struct hid_device *hdev, unsigned int connect_mask) { static const char *types[] = { "Device", "Pointer", "Mouse", "Device", @@ -1169,6 +1195,7 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) char buf[64]; unsigned int i; int len; + int ret; if (hdev->quirks & HID_QUIRK_HIDDEV_FORCE) connect_mask |= (HID_CONNECT_HIDDEV_FORCE | HID_CONNECT_HIDDEV); @@ -1230,6 +1257,11 @@ int hid_connect(struct hid_device *hdev, unsigned int connect_mask) bus = "<UNKNOWN>"; } + ret = device_create_bin_file(&hdev->dev, &dev_bin_attr_report_desc); + if (ret) + hid_warn(hdev, + "can't create sysfs report descriptor attribute err: %d\n", ret); + hid_info(hdev, "%s: %s HID v%x.%02x %s [%s] on %s\n", buf, bus, hdev->version >> 8, hdev->version & 0xff, type, hdev->name, hdev->phys); @@ -1240,6 +1272,7 @@ EXPORT_SYMBOL_GPL(hid_connect); void hid_disconnect(struct hid_device *hdev) { + device_remove_bin_file(&hdev->dev, &dev_bin_attr_report_desc); if (hdev->claimed & HID_CLAIMED_INPUT) hidinput_disconnect(hdev); if (hdev->claimed & HID_CLAIMED_HIDDEV) @@ -1345,9 +1378,12 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_2) }, { HID_USB_DEVICE(USB_VENDOR_ID_GYRATION, USB_DEVICE_ID_GYRATION_REMOTE_3) }, { HID_USB_DEVICE(USB_VENDOR_ID_HANVON, USB_DEVICE_ID_HANVON_MULTITOUCH) }, + { HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS, USB_DEVICE_ID_IRTOUCH_INFRARED_USB) }, { HID_USB_DEVICE(USB_VENDOR_ID_KENSINGTON, USB_DEVICE_ID_KS_SLIMBLADE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) }, { HID_USB_DEVICE(USB_VENDOR_ID_KYE, USB_DEVICE_ID_KYE_ERGO_525V) }, { HID_USB_DEVICE(USB_VENDOR_ID_LABTEC, USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD) }, + { HID_USB_DEVICE(USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000 ) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_MX3000_RECEIVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER) }, { HID_USB_DEVICE(USB_VENDOR_ID_LOGITECH, USB_DEVICE_ID_S510_RECEIVER_2) }, @@ -1405,7 +1441,9 @@ static const struct hid_device_id hid_have_special_driver[] = { { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_QUANTA_OPTICAL_TOUCH) }, { HID_USB_DEVICE(USB_VENDOR_ID_QUANTA, USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN) }, { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONE) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) }, { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KONEPLUS) }, + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) }, { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_PYRA_WIRED) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_IR_REMOTE) }, { HID_USB_DEVICE(USB_VENDOR_ID_SAMSUNG, USB_DEVICE_ID_SAMSUNG_WIRELESS_KBD_MOUSE) }, diff --git a/drivers/hid/hid-gyration.c b/drivers/hid/hid-gyration.c index 3975e039c3dd..e88b951cd10d 100644 --- a/drivers/hid/hid-gyration.c +++ b/drivers/hid/hid-gyration.c @@ -43,6 +43,11 @@ static int gyration_input_mapping(struct hid_device *hdev, struct hid_input *hi, case 0x048: gy_map_key_clear(KEY_MEDIA); break; case 0x049: gy_map_key_clear(KEY_CAMERA); break; case 0x04a: gy_map_key_clear(KEY_VIDEO); break; + case 0x05a: gy_map_key_clear(KEY_TEXT); break; + case 0x05b: gy_map_key_clear(KEY_RED); break; + case 0x05c: gy_map_key_clear(KEY_GREEN); break; + case 0x05d: gy_map_key_clear(KEY_YELLOW); break; + case 0x05e: gy_map_key_clear(KEY_BLUE); break; default: return 0; diff --git a/drivers/hid/hid-ids.h b/drivers/hid/hid-ids.h index 92a0d61a7379..f43525218fa2 100644 --- a/drivers/hid/hid-ids.h +++ b/drivers/hid/hid-ids.h @@ -333,6 +333,9 @@ #define USB_VENDOR_ID_IMATION 0x0718 #define USB_DEVICE_ID_DISC_STAKKA 0xd000 +#define USB_VENDOR_ID_IRTOUCHSYSTEMS 0x6615 +#define USB_DEVICE_ID_IRTOUCH_INFRARED_USB 0x0070 + #define USB_VENDOR_ID_JESS 0x0c45 #define USB_DEVICE_ID_JESS_YUREX 0x1010 @@ -345,6 +348,9 @@ #define USB_VENDOR_ID_KWORLD 0x1b80 #define USB_DEVICE_ID_KWORLD_RADIO_FM700 0xd700 +#define USB_VENDOR_ID_KEYTOUCH 0x0926 +#define USB_DEVICE_ID_KEYTOUCH_IEC 0x3333 + #define USB_VENDOR_ID_KYE 0x0458 #define USB_DEVICE_ID_KYE_ERGO_525V 0x0087 #define USB_DEVICE_ID_KYE_GPEN_560 0x5003 @@ -352,6 +358,9 @@ #define USB_VENDOR_ID_LABTEC 0x1020 #define USB_DEVICE_ID_LABTEC_WIRELESS_KEYBOARD 0x0006 +#define USB_VENDOR_ID_LCPOWER 0x1241 +#define USB_DEVICE_ID_LCPOWER_LC1000 0xf767 + #define USB_VENDOR_ID_LD 0x0f11 #define USB_DEVICE_ID_LD_CASSY 0x1000 #define USB_DEVICE_ID_LD_POCKETCASSY 0x1010 @@ -496,8 +505,10 @@ #define USB_DEVICE_ID_PIXART_IMAGING_INC_OPTICAL_TOUCH_SCREEN 0x3001 #define USB_VENDOR_ID_ROCCAT 0x1e7d +#define USB_DEVICE_ID_ROCCAT_ARVO 0x30d4 #define USB_DEVICE_ID_ROCCAT_KONE 0x2ced #define USB_DEVICE_ID_ROCCAT_KONEPLUS 0x2d51 +#define USB_DEVICE_ID_ROCCAT_KOVAPLUS 0x2d50 #define USB_DEVICE_ID_ROCCAT_PYRA_WIRED 0x2c24 #define USB_DEVICE_ID_ROCCAT_PYRA_WIRELESS 0x2cf6 diff --git a/drivers/hid/hid-input.c b/drivers/hid/hid-input.c index 7f552bfad32c..f53911dcdc2d 100644 --- a/drivers/hid/hid-input.c +++ b/drivers/hid/hid-input.c @@ -928,6 +928,7 @@ int hidinput_connect(struct hid_device *hid, unsigned int force) return 0; out_cleanup: + list_del(&hidinput->list); input_free_device(hidinput->input); kfree(hidinput); out_unwind: diff --git a/drivers/hid/hid-keytouch.c b/drivers/hid/hid-keytouch.c new file mode 100644 index 000000000000..07cd825f6f01 --- /dev/null +++ b/drivers/hid/hid-keytouch.c @@ -0,0 +1,66 @@ +/* + * HID driver for Keytouch devices not fully compliant with HID standard + * + * Copyright (c) 2011 Jiri Kosina + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> + +#include "hid-ids.h" + +/* Replace the broken report descriptor of this device with rather + * a default one */ +static __u8 keytouch_fixed_rdesc[] = { +0x05, 0x01, 0x09, 0x06, 0xa1, 0x01, 0x05, 0x07, 0x19, 0xe0, 0x29, 0xe7, 0x15, +0x00, 0x25, 0x01, 0x75, 0x01, 0x95, 0x08, 0x81, 0x02, 0x95, 0x01, 0x75, 0x08, +0x81, 0x01, 0x95, 0x03, 0x75, 0x01, 0x05, 0x08, 0x19, 0x01, 0x29, 0x03, 0x91, +0x02, 0x95, 0x05, 0x75, 0x01, 0x91, 0x01, 0x95, 0x06, 0x75, 0x08, 0x15, 0x00, +0x26, 0xff, 0x00, 0x05, 0x07, 0x19, 0x00, 0x2a, 0xff, 0x00, 0x81, 0x00, 0xc0 +}; + +static __u8 *keytouch_report_fixup(struct hid_device *hdev, __u8 *rdesc, + unsigned int *rsize) +{ + hid_info(hdev, "fixing up Keytouch IEC report descriptor\n"); + + rdesc = keytouch_fixed_rdesc; + *rsize = sizeof(keytouch_fixed_rdesc); + + return rdesc; +} + +static const struct hid_device_id keytouch_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_KEYTOUCH, USB_DEVICE_ID_KEYTOUCH_IEC) }, + { } +}; +MODULE_DEVICE_TABLE(hid, keytouch_devices); + +static struct hid_driver keytouch_driver = { + .name = "keytouch", + .id_table = keytouch_devices, + .report_fixup = keytouch_report_fixup, +}; + +static int __init keytouch_init(void) +{ + return hid_register_driver(&keytouch_driver); +} + +static void __exit keytouch_exit(void) +{ + hid_unregister_driver(&keytouch_driver); +} + +module_init(keytouch_init); +module_exit(keytouch_exit); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Jiri Kosina"); diff --git a/drivers/hid/hid-lcpower.c b/drivers/hid/hid-lcpower.c new file mode 100644 index 000000000000..c4fe9bd095b7 --- /dev/null +++ b/drivers/hid/hid-lcpower.c @@ -0,0 +1,70 @@ +/* + * HID driver for LC Power Model RC1000MCE + * + * Copyright (c) 2011 Chris Schlund + * based on hid-topseed module + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/device.h> +#include <linux/hid.h> +#include <linux/module.h> + +#include "hid-ids.h" + +#define ts_map_key_clear(c) hid_map_usage_clear(hi, usage, bit, max, \ + EV_KEY, (c)) +static int ts_input_mapping(struct hid_device *hdev, struct hid_input *hi, + struct hid_field *field, struct hid_usage *usage, + unsigned long **bit, int *max) +{ + if ((usage->hid & HID_USAGE_PAGE) != 0x0ffbc0000) + return 0; + + switch (usage->hid & HID_USAGE) { + case 0x046: ts_map_key_clear(KEY_YELLOW); break; + case 0x047: ts_map_key_clear(KEY_GREEN); break; + case 0x049: ts_map_key_clear(KEY_BLUE); break; + case 0x04a: ts_map_key_clear(KEY_RED); break; + case 0x00d: ts_map_key_clear(KEY_HOME); break; + case 0x025: ts_map_key_clear(KEY_TV); break; + case 0x048: ts_map_key_clear(KEY_VCR); break; + case 0x024: ts_map_key_clear(KEY_MENU); break; + default: + return 0; + } + + return 1; +} + +static const struct hid_device_id ts_devices[] = { + { HID_USB_DEVICE( USB_VENDOR_ID_LCPOWER, USB_DEVICE_ID_LCPOWER_LC1000) }, + { } +}; +MODULE_DEVICE_TABLE(hid, ts_devices); + +static struct hid_driver ts_driver = { + .name = "LC RC1000MCE", + .id_table = ts_devices, + .input_mapping = ts_input_mapping, +}; + +static int __init ts_init(void) +{ + return hid_register_driver(&ts_driver); +} + +static void __exit ts_exit(void) +{ + hid_unregister_driver(&ts_driver); +} + +module_init(ts_init); +module_exit(ts_exit); +MODULE_LICENSE("GPL"); diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index 07d3183fdde5..69f8744deac8 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -24,6 +24,7 @@ MODULE_AUTHOR("Stephane Chatty <chatty@enac.fr>"); +MODULE_AUTHOR("Benjamin Tissoires <benjamin.tissoires@gmail.com>"); MODULE_DESCRIPTION("HID multitouch panels"); MODULE_LICENSE("GPL"); @@ -65,10 +66,10 @@ struct mt_class { }; /* classes of device behavior */ -#define MT_CLS_DEFAULT 1 -#define MT_CLS_DUAL1 2 -#define MT_CLS_DUAL2 3 -#define MT_CLS_CYPRESS 4 +#define MT_CLS_DEFAULT 1 +#define MT_CLS_DUAL_INRANGE_CONTACTID 2 +#define MT_CLS_DUAL_INRANGE_CONTACTNUMBER 3 +#define MT_CLS_CYPRESS 4 /* * these device-dependent functions determine what slot corresponds @@ -104,13 +105,13 @@ static int find_slot_from_contactid(struct mt_device *td) struct mt_class mt_classes[] = { { .name = MT_CLS_DEFAULT, - .quirks = MT_QUIRK_VALID_IS_INRANGE, + .quirks = MT_QUIRK_NOT_SEEN_MEANS_UP, .maxcontacts = 10 }, - { .name = MT_CLS_DUAL1, + { .name = MT_CLS_DUAL_INRANGE_CONTACTID, .quirks = MT_QUIRK_VALID_IS_INRANGE | MT_QUIRK_SLOT_IS_CONTACTID, .maxcontacts = 2 }, - { .name = MT_CLS_DUAL2, + { .name = MT_CLS_DUAL_INRANGE_CONTACTNUMBER, .quirks = MT_QUIRK_VALID_IS_INRANGE | MT_QUIRK_SLOT_IS_CONTACTNUMBER, .maxcontacts = 2 }, @@ -466,15 +467,20 @@ static const struct hid_device_id mt_devices[] = { USB_DEVICE_ID_CYPRESS_TRUETOUCH) }, /* GeneralTouch panel */ - { .driver_data = MT_CLS_DUAL2, + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTNUMBER, HID_USB_DEVICE(USB_VENDOR_ID_GENERAL_TOUCH, USB_DEVICE_ID_GENERAL_TOUCH_WIN7_TWOFINGERS) }, + /* IRTOUCH panels */ + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, + HID_USB_DEVICE(USB_VENDOR_ID_IRTOUCHSYSTEMS, + USB_DEVICE_ID_IRTOUCH_INFRARED_USB) }, + /* PixCir-based panels */ - { .driver_data = MT_CLS_DUAL1, + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, HID_USB_DEVICE(USB_VENDOR_ID_HANVON, USB_DEVICE_ID_HANVON_MULTITOUCH) }, - { .driver_data = MT_CLS_DUAL1, + { .driver_data = MT_CLS_DUAL_INRANGE_CONTACTID, HID_USB_DEVICE(USB_VENDOR_ID_CANDO, USB_DEVICE_ID_CANDO_PIXCIR_MULTI_TOUCH) }, diff --git a/drivers/hid/hid-roccat-arvo.c b/drivers/hid/hid-roccat-arvo.c new file mode 100644 index 000000000000..2307471d96dc --- /dev/null +++ b/drivers/hid/hid-roccat-arvo.c @@ -0,0 +1,450 @@ +/* + * Roccat Arvo driver for Linux + * + * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* + * Roccat Arvo is a gamer keyboard with 5 macro keys that can be configured in + * 5 profiles. + */ + +#include <linux/device.h> +#include <linux/input.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/hid-roccat.h> +#include "hid-ids.h" +#include "hid-roccat-common.h" +#include "hid-roccat-arvo.h" + +static struct class *arvo_class; + +static ssize_t arvo_sysfs_show_mode_key(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arvo_device *arvo = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + struct usb_device *usb_dev = + interface_to_usbdev(to_usb_interface(dev->parent->parent)); + struct arvo_mode_key temp_buf; + int retval; + + mutex_lock(&arvo->arvo_lock); + retval = roccat_common_receive(usb_dev, ARVO_USB_COMMAND_MODE_KEY, + &temp_buf, sizeof(struct arvo_mode_key)); + mutex_unlock(&arvo->arvo_lock); + if (retval) + return retval; + + return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.state); +} + +static ssize_t arvo_sysfs_set_mode_key(struct device *dev, + struct device_attribute *attr, char const *buf, size_t size) +{ + struct arvo_device *arvo = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + struct usb_device *usb_dev = + interface_to_usbdev(to_usb_interface(dev->parent->parent)); + struct arvo_mode_key temp_buf; + unsigned long state; + int retval; + + retval = strict_strtoul(buf, 10, &state); + if (retval) + return retval; + + temp_buf.command = ARVO_COMMAND_MODE_KEY; + temp_buf.state = state; + + mutex_lock(&arvo->arvo_lock); + retval = roccat_common_send(usb_dev, ARVO_USB_COMMAND_MODE_KEY, + &temp_buf, sizeof(struct arvo_mode_key)); + mutex_unlock(&arvo->arvo_lock); + if (retval) + return retval; + + return size; +} + +static ssize_t arvo_sysfs_show_key_mask(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arvo_device *arvo = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + struct usb_device *usb_dev = + interface_to_usbdev(to_usb_interface(dev->parent->parent)); + struct arvo_key_mask temp_buf; + int retval; + + mutex_lock(&arvo->arvo_lock); + retval = roccat_common_receive(usb_dev, ARVO_USB_COMMAND_KEY_MASK, + &temp_buf, sizeof(struct arvo_key_mask)); + mutex_unlock(&arvo->arvo_lock); + if (retval) + return retval; + + return snprintf(buf, PAGE_SIZE, "%d\n", temp_buf.key_mask); +} + +static ssize_t arvo_sysfs_set_key_mask(struct device *dev, + struct device_attribute *attr, char const *buf, size_t size) +{ + struct arvo_device *arvo = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + struct usb_device *usb_dev = + interface_to_usbdev(to_usb_interface(dev->parent->parent)); + struct arvo_key_mask temp_buf; + unsigned long key_mask; + int retval; + + retval = strict_strtoul(buf, 10, &key_mask); + if (retval) + return retval; + + temp_buf.command = ARVO_COMMAND_KEY_MASK; + temp_buf.key_mask = key_mask; + + mutex_lock(&arvo->arvo_lock); + retval = roccat_common_send(usb_dev, ARVO_USB_COMMAND_KEY_MASK, + &temp_buf, sizeof(struct arvo_key_mask)); + mutex_unlock(&arvo->arvo_lock); + if (retval) + return retval; + + return size; +} + +/* retval is 1-5 on success, < 0 on error */ +static int arvo_get_actual_profile(struct usb_device *usb_dev) +{ + struct arvo_actual_profile temp_buf; + int retval; + + retval = roccat_common_receive(usb_dev, ARVO_USB_COMMAND_ACTUAL_PROFILE, + &temp_buf, sizeof(struct arvo_actual_profile)); + + if (retval) + return retval; + + return temp_buf.actual_profile; +} + +static ssize_t arvo_sysfs_show_actual_profile(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct arvo_device *arvo = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + + return snprintf(buf, PAGE_SIZE, "%d\n", arvo->actual_profile); +} + +static ssize_t arvo_sysfs_set_actual_profile(struct device *dev, + struct device_attribute *attr, char const *buf, size_t size) +{ + struct arvo_device *arvo = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + struct usb_device *usb_dev = + interface_to_usbdev(to_usb_interface(dev->parent->parent)); + struct arvo_actual_profile temp_buf; + unsigned long profile; + int retval; + + retval = strict_strtoul(buf, 10, &profile); + if (retval) + return retval; + + temp_buf.command = ARVO_COMMAND_ACTUAL_PROFILE; + temp_buf.actual_profile = profile; + + mutex_lock(&arvo->arvo_lock); + retval = roccat_common_send(usb_dev, ARVO_USB_COMMAND_ACTUAL_PROFILE, + &temp_buf, sizeof(struct arvo_actual_profile)); + if (!retval) { + arvo->actual_profile = profile; + retval = size; + } + mutex_unlock(&arvo->arvo_lock); + return retval; +} + +static ssize_t arvo_sysfs_write(struct file *fp, + struct kobject *kobj, void const *buf, + loff_t off, size_t count, size_t real_size, uint command) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct arvo_device *arvo = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval; + + if (off != 0 || count != real_size) + return -EINVAL; + + mutex_lock(&arvo->arvo_lock); + retval = roccat_common_send(usb_dev, command, buf, real_size); + mutex_unlock(&arvo->arvo_lock); + + return (retval ? retval : real_size); +} + +static ssize_t arvo_sysfs_read(struct file *fp, + struct kobject *kobj, void *buf, loff_t off, + size_t count, size_t real_size, uint command) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct arvo_device *arvo = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval; + + if (off >= real_size) + return 0; + + if (off != 0 || count != real_size) + return -EINVAL; + + mutex_lock(&arvo->arvo_lock); + retval = roccat_common_receive(usb_dev, command, buf, real_size); + mutex_unlock(&arvo->arvo_lock); + + return (retval ? retval : real_size); +} + +static ssize_t arvo_sysfs_write_button(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return arvo_sysfs_write(fp, kobj, buf, off, count, + sizeof(struct arvo_button), ARVO_USB_COMMAND_BUTTON); +} + +static ssize_t arvo_sysfs_read_info(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + return arvo_sysfs_read(fp, kobj, buf, off, count, + sizeof(struct arvo_info), ARVO_USB_COMMAND_INFO); +} + + +static struct device_attribute arvo_attributes[] = { + __ATTR(mode_key, 0660, + arvo_sysfs_show_mode_key, arvo_sysfs_set_mode_key), + __ATTR(key_mask, 0660, + arvo_sysfs_show_key_mask, arvo_sysfs_set_key_mask), + __ATTR(actual_profile, 0660, + arvo_sysfs_show_actual_profile, + arvo_sysfs_set_actual_profile), + __ATTR_NULL +}; + +static struct bin_attribute arvo_bin_attributes[] = { + { + .attr = { .name = "button", .mode = 0220 }, + .size = sizeof(struct arvo_button), + .write = arvo_sysfs_write_button + }, + { + .attr = { .name = "info", .mode = 0440 }, + .size = sizeof(struct arvo_info), + .read = arvo_sysfs_read_info + }, + __ATTR_NULL +}; + +static int arvo_init_arvo_device_struct(struct usb_device *usb_dev, + struct arvo_device *arvo) +{ + int retval; + + mutex_init(&arvo->arvo_lock); + + retval = arvo_get_actual_profile(usb_dev); + if (retval < 0) + return retval; + arvo->actual_profile = retval; + + return 0; +} + +static int arvo_init_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct arvo_device *arvo; + int retval; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_KEYBOARD) { + hid_set_drvdata(hdev, NULL); + return 0; + } + + arvo = kzalloc(sizeof(*arvo), GFP_KERNEL); + if (!arvo) { + hid_err(hdev, "can't alloc device descriptor\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, arvo); + + retval = arvo_init_arvo_device_struct(usb_dev, arvo); + if (retval) { + hid_err(hdev, "couldn't init struct arvo_device\n"); + goto exit_free; + } + + retval = roccat_connect(arvo_class, hdev, + sizeof(struct arvo_roccat_report)); + if (retval < 0) { + hid_err(hdev, "couldn't init char dev\n"); + } else { + arvo->chrdev_minor = retval; + arvo->roccat_claimed = 1; + } + + return 0; +exit_free: + kfree(arvo); + return retval; +} + +static void arvo_remove_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct arvo_device *arvo; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_KEYBOARD) + return; + + arvo = hid_get_drvdata(hdev); + if (arvo->roccat_claimed) + roccat_disconnect(arvo->chrdev_minor); + kfree(arvo); +} + +static int arvo_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int retval; + + retval = hid_parse(hdev); + if (retval) { + hid_err(hdev, "parse failed\n"); + goto exit; + } + + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (retval) { + hid_err(hdev, "hw start failed\n"); + goto exit; + } + + retval = arvo_init_specials(hdev); + if (retval) { + hid_err(hdev, "couldn't install keyboard\n"); + goto exit_stop; + } + + return 0; + +exit_stop: + hid_hw_stop(hdev); +exit: + return retval; +} + +static void arvo_remove(struct hid_device *hdev) +{ + arvo_remove_specials(hdev); + hid_hw_stop(hdev); +} + +static void arvo_report_to_chrdev(struct arvo_device const *arvo, + u8 const *data) +{ + struct arvo_special_report const *special_report; + struct arvo_roccat_report roccat_report; + + special_report = (struct arvo_special_report const *)data; + + roccat_report.profile = arvo->actual_profile; + roccat_report.button = special_report->event & + ARVO_SPECIAL_REPORT_EVENT_MASK_BUTTON; + if ((special_report->event & ARVO_SPECIAL_REPORT_EVENT_MASK_ACTION) == + ARVO_SPECIAL_REPORT_EVENT_ACTION_PRESS) + roccat_report.action = ARVO_ROCCAT_REPORT_ACTION_PRESS; + else + roccat_report.action = ARVO_ROCCAT_REPORT_ACTION_RELEASE; + + roccat_report_event(arvo->chrdev_minor, + (uint8_t const *)&roccat_report); +} + +static int arvo_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct arvo_device *arvo = hid_get_drvdata(hdev); + + if (size != 3) + return 0; + + if (arvo->roccat_claimed) + arvo_report_to_chrdev(arvo, data); + + return 0; +} + +static const struct hid_device_id arvo_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_ARVO) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, arvo_devices); + +static struct hid_driver arvo_driver = { + .name = "arvo", + .id_table = arvo_devices, + .probe = arvo_probe, + .remove = arvo_remove, + .raw_event = arvo_raw_event +}; + +static int __init arvo_init(void) +{ + int retval; + + arvo_class = class_create(THIS_MODULE, "arvo"); + if (IS_ERR(arvo_class)) + return PTR_ERR(arvo_class); + arvo_class->dev_attrs = arvo_attributes; + arvo_class->dev_bin_attrs = arvo_bin_attributes; + + retval = hid_register_driver(&arvo_driver); + if (retval) + class_destroy(arvo_class); + return retval; +} + +static void __exit arvo_exit(void) +{ + hid_unregister_driver(&arvo_driver); + class_destroy(arvo_class); +} + +module_init(arvo_init); +module_exit(arvo_exit); + +MODULE_AUTHOR("Stefan Achatz"); +MODULE_DESCRIPTION("USB Roccat Arvo driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-roccat-arvo.h b/drivers/hid/hid-roccat-arvo.h new file mode 100644 index 000000000000..d284a781c99e --- /dev/null +++ b/drivers/hid/hid-roccat-arvo.h @@ -0,0 +1,98 @@ +#ifndef __HID_ROCCAT_ARVO_H +#define __HID_ROCCAT_ARVO_H + +/* + * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/types.h> + +struct arvo_mode_key { /* 2 bytes */ + uint8_t command; /* ARVO_COMMAND_MODE_KEY */ + uint8_t state; +} __packed; + +struct arvo_button { + uint8_t unknown[24]; +} __packed; + +struct arvo_info { + uint8_t unknown[8]; +} __packed; + +struct arvo_key_mask { /* 2 bytes */ + uint8_t command; /* ARVO_COMMAND_KEY_MASK */ + uint8_t key_mask; +} __packed; + +/* selected profile is persistent */ +struct arvo_actual_profile { /* 2 bytes */ + uint8_t command; /* ARVO_COMMAND_ACTUAL_PROFILE */ + uint8_t actual_profile; +} __packed; + +enum arvo_commands { + ARVO_COMMAND_MODE_KEY = 0x3, + ARVO_COMMAND_BUTTON = 0x4, + ARVO_COMMAND_INFO = 0x5, + ARVO_COMMAND_KEY_MASK = 0x6, + ARVO_COMMAND_ACTUAL_PROFILE = 0x7, +}; + +enum arvo_usb_commands { + ARVO_USB_COMMAND_MODE_KEY = 0x303, + /* + * read/write + * Read uses both index bytes as profile/key indexes + * Write has index 0, profile/key is determined by payload + */ + ARVO_USB_COMMAND_BUTTON = 0x304, + ARVO_USB_COMMAND_INFO = 0x305, + ARVO_USB_COMMAND_KEY_MASK = 0x306, + ARVO_USB_COMMAND_ACTUAL_PROFILE = 0x307, +}; + +struct arvo_special_report { + uint8_t unknown1; /* always 0x01 */ + uint8_t event; + uint8_t unknown2; /* always 0x70 */ +} __packed; + +enum arvo_special_report_events { + ARVO_SPECIAL_REPORT_EVENT_ACTION_PRESS = 0x10, + ARVO_SPECIAL_REPORT_EVENT_ACTION_RELEASE = 0x0, +}; + +enum arvo_special_report_event_masks { + ARVO_SPECIAL_REPORT_EVENT_MASK_ACTION = 0xf0, + ARVO_SPECIAL_REPORT_EVENT_MASK_BUTTON = 0x0f, +}; + +struct arvo_roccat_report { + uint8_t profile; + uint8_t button; + uint8_t action; +} __packed; + +enum arvo_roccat_report_action { + ARVO_ROCCAT_REPORT_ACTION_RELEASE = 0, + ARVO_ROCCAT_REPORT_ACTION_PRESS = 1, +}; + +struct arvo_device { + int roccat_claimed; + int chrdev_minor; + + struct mutex arvo_lock; + + int actual_profile; +}; + +#endif diff --git a/drivers/hid/hid-roccat-common.c b/drivers/hid/hid-roccat-common.c new file mode 100644 index 000000000000..13b1eb0c8c65 --- /dev/null +++ b/drivers/hid/hid-roccat-common.c @@ -0,0 +1,62 @@ +/* + * Roccat common functions for device specific drivers + * + * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/slab.h> +#include "hid-roccat-common.h" + +int roccat_common_receive(struct usb_device *usb_dev, uint usb_command, + void *data, uint size) +{ + char *buf; + int len; + + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), + USB_REQ_CLEAR_FEATURE, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, + usb_command, 0, buf, size, USB_CTRL_SET_TIMEOUT); + + memcpy(data, buf, size); + kfree(buf); + return ((len < 0) ? len : ((len != size) ? -EIO : 0)); +} +EXPORT_SYMBOL_GPL(roccat_common_receive); + +int roccat_common_send(struct usb_device *usb_dev, uint usb_command, + void const *data, uint size) +{ + char *buf; + int len; + + buf = kmalloc(size, GFP_KERNEL); + if (buf == NULL) + return -ENOMEM; + + memcpy(buf, data, size); + + len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), + USB_REQ_SET_CONFIGURATION, + USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, + usb_command, 0, buf, size, USB_CTRL_SET_TIMEOUT); + + kfree(buf); + return ((len < 0) ? len : ((len != size) ? -EIO : 0)); +} +EXPORT_SYMBOL_GPL(roccat_common_send); + +MODULE_AUTHOR("Stefan Achatz"); +MODULE_DESCRIPTION("USB Roccat common driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-roccat-common.h b/drivers/hid/hid-roccat-common.h new file mode 100644 index 000000000000..fe45fae05bb9 --- /dev/null +++ b/drivers/hid/hid-roccat-common.h @@ -0,0 +1,23 @@ +#ifndef __HID_ROCCAT_COMMON_H +#define __HID_ROCCAT_COMMON_H + +/* + * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/usb.h> +#include <linux/types.h> + +int roccat_common_receive(struct usb_device *usb_dev, uint usb_command, + void *data, uint size); +int roccat_common_send(struct usb_device *usb_dev, uint usb_command, + void const *data, uint size); + +#endif diff --git a/drivers/hid/hid-roccat-kone.c b/drivers/hid/hid-roccat-kone.c index cbd8cc42e75a..a57838d15267 100644 --- a/drivers/hid/hid-roccat-kone.c +++ b/drivers/hid/hid-roccat-kone.c @@ -28,11 +28,11 @@ #include <linux/device.h> #include <linux/input.h> #include <linux/hid.h> -#include <linux/usb.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/hid-roccat.h> #include "hid-ids.h" -#include "hid-roccat.h" +#include "hid-roccat-common.h" #include "hid-roccat-kone.h" static uint profile_numbers[5] = {0, 1, 2, 3, 4}; @@ -58,12 +58,8 @@ static void kone_set_settings_checksum(struct kone_settings *settings) */ static int kone_check_write(struct usb_device *usb_dev) { - int len; - unsigned char *data; - - data = kmalloc(1, GFP_KERNEL); - if (!data) - return -ENOMEM; + int retval; + uint8_t data; do { /* @@ -72,56 +68,36 @@ static int kone_check_write(struct usb_device *usb_dev) */ msleep(80); - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | - USB_DIR_IN, - kone_command_confirm_write, 0, data, 1, - USB_CTRL_SET_TIMEOUT); - - if (len != 1) { - kfree(data); - return -EIO; - } + retval = roccat_common_receive(usb_dev, + kone_command_confirm_write, &data, 1); + if (retval) + return retval; /* * value of 3 seems to mean something like * "not finished yet, but it looks good" * So check again after a moment. */ - } while (*data == 3); + } while (data == 3); - if (*data == 1) { /* everything alright */ - kfree(data); + if (data == 1) /* everything alright */ return 0; - } else { /* unknown answer */ - hid_err(usb_dev, "got retval %d when checking write\n", *data); - kfree(data); - return -EIO; - } + + /* unknown answer */ + hid_err(usb_dev, "got retval %d when checking write\n", data); + return -EIO; } /* * Reads settings from mouse and stores it in @buf - * @buf has to be alloced with GFP_KERNEL * On success returns 0 * On failure returns errno */ static int kone_get_settings(struct usb_device *usb_dev, struct kone_settings *buf) { - int len; - - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - kone_command_settings, 0, buf, - sizeof(struct kone_settings), USB_CTRL_SET_TIMEOUT); - - if (len != sizeof(struct kone_settings)) - return -EIO; - - return 0; + return roccat_common_receive(usb_dev, kone_command_settings, buf, + sizeof(struct kone_settings)); } /* @@ -132,22 +108,12 @@ static int kone_get_settings(struct usb_device *usb_dev, static int kone_set_settings(struct usb_device *usb_dev, struct kone_settings const *settings) { - int len; - - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - kone_command_settings, 0, (char *)settings, - sizeof(struct kone_settings), - USB_CTRL_SET_TIMEOUT); - - if (len != sizeof(struct kone_settings)) - return -EIO; - - if (kone_check_write(usb_dev)) - return -EIO; - - return 0; + int retval; + retval = roccat_common_send(usb_dev, kone_command_settings, + settings, sizeof(struct kone_settings)); + if (retval) + return retval; + return kone_check_write(usb_dev); } /* @@ -193,7 +159,7 @@ static int kone_set_profile(struct usb_device *usb_dev, len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), USB_REQ_SET_CONFIGURATION, USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - kone_command_profile, number, (char *)profile, + kone_command_profile, number, (void *)profile, sizeof(struct kone_profile), USB_CTRL_SET_TIMEOUT); @@ -213,24 +179,15 @@ static int kone_set_profile(struct usb_device *usb_dev, */ static int kone_get_weight(struct usb_device *usb_dev, int *result) { - int len; - uint8_t *data; + int retval; + uint8_t data; - data = kmalloc(1, GFP_KERNEL); - if (!data) - return -ENOMEM; + retval = roccat_common_receive(usb_dev, kone_command_weight, &data, 1); - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - kone_command_weight, 0, data, 1, USB_CTRL_SET_TIMEOUT); + if (retval) + return retval; - if (len != 1) { - kfree(data); - return -EIO; - } - *result = (int)*data; - kfree(data); + *result = (int)data; return 0; } @@ -241,25 +198,15 @@ static int kone_get_weight(struct usb_device *usb_dev, int *result) */ static int kone_get_firmware_version(struct usb_device *usb_dev, int *result) { - int len; - unsigned char *data; - - data = kmalloc(2, GFP_KERNEL); - if (!data) - return -ENOMEM; + int retval; + uint16_t data; - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - kone_command_firmware_version, 0, data, 2, - USB_CTRL_SET_TIMEOUT); + retval = roccat_common_receive(usb_dev, kone_command_firmware_version, + &data, 2); + if (retval) + return retval; - if (len != 2) { - kfree(data); - return -EIO; - } - *result = le16_to_cpu(*data); - kfree(data); + *result = le16_to_cpu(data); return 0; } @@ -435,23 +382,9 @@ static ssize_t kone_sysfs_show_tcu(struct device *dev, static int kone_tcu_command(struct usb_device *usb_dev, int number) { - int len; - char *value; - - value = kmalloc(1, GFP_KERNEL); - if (!value) - return -ENOMEM; - - *value = number; - - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - kone_command_calibrate, 0, value, 1, - USB_CTRL_SET_TIMEOUT); - - kfree(value); - return ((len != 1) ? -EIO : 0); + unsigned char value; + value = number; + return roccat_common_send(usb_dev, kone_command_calibrate, &value, 1); } /* @@ -727,7 +660,8 @@ static int kone_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(kone_class, hdev); + retval = roccat_connect(kone_class, hdev, + sizeof(struct kone_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); /* be tolerant about not getting chrdev */ @@ -827,8 +761,7 @@ static void kone_report_to_chrdev(struct kone_device const *kone, roccat_report.value = event->value; roccat_report.key = 0; roccat_report_event(kone->chrdev_minor, - (uint8_t *)&roccat_report, - sizeof(struct kone_roccat_report)); + (uint8_t *)&roccat_report); break; case kone_mouse_event_call_overlong_macro: if (event->value == kone_keystroke_action_press) { @@ -836,8 +769,7 @@ static void kone_report_to_chrdev(struct kone_device const *kone, roccat_report.value = kone->actual_profile; roccat_report.key = event->macro_key; roccat_report_event(kone->chrdev_minor, - (uint8_t *)&roccat_report, - sizeof(struct kone_roccat_report)); + (uint8_t *)&roccat_report); } break; } @@ -912,8 +844,8 @@ static int __init kone_init(void) static void __exit kone_exit(void) { - class_destroy(kone_class); hid_unregister_driver(&kone_driver); + class_destroy(kone_class); } module_init(kone_init); diff --git a/drivers/hid/hid-roccat-koneplus.c b/drivers/hid/hid-roccat-koneplus.c index 1608c8d1efd6..a8e2117756ac 100644 --- a/drivers/hid/hid-roccat-koneplus.c +++ b/drivers/hid/hid-roccat-koneplus.c @@ -19,11 +19,11 @@ #include <linux/device.h> #include <linux/input.h> #include <linux/hid.h> -#include <linux/usb.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/hid-roccat.h> #include "hid-ids.h" -#include "hid-roccat.h" +#include "hid-roccat-common.h" #include "hid-roccat-koneplus.h" static uint profile_numbers[5] = {0, 1, 2, 3, 4}; @@ -39,110 +39,63 @@ static void koneplus_profile_activated(struct koneplus_device *koneplus, static int koneplus_send_control(struct usb_device *usb_dev, uint value, enum koneplus_control_requests request) { - int len; - struct koneplus_control *control; + struct koneplus_control control; if ((request == KONEPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || request == KONEPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && value > 4) return -EINVAL; - control = kmalloc(sizeof(struct koneplus_control), GFP_KERNEL); - if (!control) - return -ENOMEM; + control.command = KONEPLUS_COMMAND_CONTROL; + control.value = value; + control.request = request; - control->command = KONEPLUS_COMMAND_CONTROL; - control->value = value; - control->request = request; - - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - KONEPLUS_USB_COMMAND_CONTROL, 0, control, - sizeof(struct koneplus_control), - USB_CTRL_SET_TIMEOUT); - - kfree(control); - - if (len != sizeof(struct koneplus_control)) - return len; - - return 0; -} - -static int koneplus_receive(struct usb_device *usb_dev, uint usb_command, - void *buf, uint size) { - int len; - - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - usb_command, 0, buf, size, USB_CTRL_SET_TIMEOUT); - - return (len != size) ? -EIO : 0; + return roccat_common_send(usb_dev, KONEPLUS_USB_COMMAND_CONTROL, + &control, sizeof(struct koneplus_control)); } static int koneplus_receive_control_status(struct usb_device *usb_dev) { int retval; - struct koneplus_control *control; - - control = kmalloc(sizeof(struct koneplus_control), GFP_KERNEL); - if (!control) - return -ENOMEM; + struct koneplus_control control; do { - retval = koneplus_receive(usb_dev, KONEPLUS_USB_COMMAND_CONTROL, - control, sizeof(struct koneplus_control)); + retval = roccat_common_receive(usb_dev, KONEPLUS_USB_COMMAND_CONTROL, + &control, sizeof(struct koneplus_control)); /* check if we get a completely wrong answer */ if (retval) - goto out; + return retval; - if (control->value == KONEPLUS_CONTROL_REQUEST_STATUS_OK) { - retval = 0; - goto out; - } + if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OK) + return 0; /* indicates that hardware needs some more time to complete action */ - if (control->value == KONEPLUS_CONTROL_REQUEST_STATUS_WAIT) { + if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_WAIT) { msleep(500); /* windows driver uses 1000 */ continue; } /* seems to be critical - replug necessary */ - if (control->value == KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD) { - retval = -EINVAL; - goto out; - } - - dev_err(&usb_dev->dev, "koneplus_receive_control_status: " - "unknown response value 0x%x\n", control->value); - retval = -EINVAL; - goto out; + if (control.value == KONEPLUS_CONTROL_REQUEST_STATUS_OVERLOAD) + return -EINVAL; + hid_err(usb_dev, "koneplus_receive_control_status: " + "unknown response value 0x%x\n", control.value); + return -EINVAL; } while (1); -out: - kfree(control); - return retval; } static int koneplus_send(struct usb_device *usb_dev, uint command, - void *buf, uint size) { - int len; - - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - command, 0, buf, size, USB_CTRL_SET_TIMEOUT); - - if (len != size) - return -EIO; + void const *buf, uint size) +{ + int retval; - if (koneplus_receive_control_status(usb_dev)) - return -EIO; + retval = roccat_common_send(usb_dev, command, buf, size); + if (retval) + return retval; - return 0; + return koneplus_receive_control_status(usb_dev); } static int koneplus_select_profile(struct usb_device *usb_dev, uint number, @@ -167,7 +120,7 @@ static int koneplus_select_profile(struct usb_device *usb_dev, uint number, static int koneplus_get_info(struct usb_device *usb_dev, struct koneplus_info *buf) { - return koneplus_receive(usb_dev, KONEPLUS_USB_COMMAND_INFO, + return roccat_common_receive(usb_dev, KONEPLUS_USB_COMMAND_INFO, buf, sizeof(struct koneplus_info)); } @@ -181,7 +134,7 @@ static int koneplus_get_profile_settings(struct usb_device *usb_dev, if (retval) return retval; - return koneplus_receive(usb_dev, KONEPLUS_USB_COMMAND_PROFILE_SETTINGS, + return roccat_common_receive(usb_dev, KONEPLUS_USB_COMMAND_PROFILE_SETTINGS, buf, sizeof(struct koneplus_profile_settings)); } @@ -189,7 +142,7 @@ static int koneplus_set_profile_settings(struct usb_device *usb_dev, struct koneplus_profile_settings const *settings) { return koneplus_send(usb_dev, KONEPLUS_USB_COMMAND_PROFILE_SETTINGS, - (void *)settings, sizeof(struct koneplus_profile_settings)); + settings, sizeof(struct koneplus_profile_settings)); } static int koneplus_get_profile_buttons(struct usb_device *usb_dev, @@ -202,7 +155,7 @@ static int koneplus_get_profile_buttons(struct usb_device *usb_dev, if (retval) return retval; - return koneplus_receive(usb_dev, KONEPLUS_USB_COMMAND_PROFILE_BUTTONS, + return roccat_common_receive(usb_dev, KONEPLUS_USB_COMMAND_PROFILE_BUTTONS, buf, sizeof(struct koneplus_profile_buttons)); } @@ -210,27 +163,19 @@ static int koneplus_set_profile_buttons(struct usb_device *usb_dev, struct koneplus_profile_buttons const *buttons) { return koneplus_send(usb_dev, KONEPLUS_USB_COMMAND_PROFILE_BUTTONS, - (void *)buttons, sizeof(struct koneplus_profile_buttons)); + buttons, sizeof(struct koneplus_profile_buttons)); } /* retval is 0-4 on success, < 0 on error */ static int koneplus_get_startup_profile(struct usb_device *usb_dev) { - struct koneplus_startup_profile *buf; + struct koneplus_startup_profile buf; int retval; - buf = kmalloc(sizeof(struct koneplus_startup_profile), GFP_KERNEL); + retval = roccat_common_receive(usb_dev, KONEPLUS_USB_COMMAND_STARTUP_PROFILE, + &buf, sizeof(struct koneplus_startup_profile)); - retval = koneplus_receive(usb_dev, KONEPLUS_USB_COMMAND_STARTUP_PROFILE, - buf, sizeof(struct koneplus_startup_profile)); - - if (retval) - goto out; - - retval = buf->startup_profile; -out: - kfree(buf); - return retval; + return retval ? retval : buf.startup_profile; } static int koneplus_set_startup_profile(struct usb_device *usb_dev, @@ -243,7 +188,7 @@ static int koneplus_set_startup_profile(struct usb_device *usb_dev, buf.startup_profile = startup_profile; return koneplus_send(usb_dev, KONEPLUS_USB_COMMAND_STARTUP_PROFILE, - (char *)&buf, sizeof(struct koneplus_profile_buttons)); + &buf, sizeof(struct koneplus_profile_buttons)); } static ssize_t koneplus_sysfs_read(struct file *fp, struct kobject *kobj, @@ -256,11 +201,14 @@ static ssize_t koneplus_sysfs_read(struct file *fp, struct kobject *kobj, struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); int retval; + if (off >= real_size) + return 0; + if (off != 0 || count != real_size) return -EINVAL; mutex_lock(&koneplus->koneplus_lock); - retval = koneplus_receive(usb_dev, command, buf, real_size); + retval = roccat_common_receive(usb_dev, command, buf, real_size); mutex_unlock(&koneplus->koneplus_lock); if (retval) @@ -283,7 +231,7 @@ static ssize_t koneplus_sysfs_write(struct file *fp, struct kobject *kobj, return -EINVAL; mutex_lock(&koneplus->koneplus_lock); - retval = koneplus_send(usb_dev, command, (void *)buf, real_size); + retval = koneplus_send(usb_dev, command, buf, real_size); mutex_unlock(&koneplus->koneplus_lock); if (retval) @@ -347,7 +295,7 @@ static ssize_t koneplus_sysfs_read_profilex_settings(struct file *fp, count = sizeof(struct koneplus_profile_settings) - off; mutex_lock(&koneplus->koneplus_lock); - memcpy(buf, ((void const *)&koneplus->profile_settings[*(uint *)(attr->private)]) + off, + memcpy(buf, ((char const *)&koneplus->profile_settings[*(uint *)(attr->private)]) + off, count); mutex_unlock(&koneplus->koneplus_lock); @@ -406,7 +354,7 @@ static ssize_t koneplus_sysfs_read_profilex_buttons(struct file *fp, count = sizeof(struct koneplus_profile_buttons) - off; mutex_lock(&koneplus->koneplus_lock); - memcpy(buf, ((void const *)&koneplus->profile_buttons[*(uint *)(attr->private)]) + off, + memcpy(buf, ((char const *)&koneplus->profile_buttons[*(uint *)(attr->private)]) + off, count); mutex_unlock(&koneplus->koneplus_lock); @@ -609,11 +557,13 @@ static int koneplus_init_koneplus_device_struct(struct usb_device *usb_dev, struct koneplus_device *koneplus) { int retval, i; - static uint wait = 70; /* device will freeze with just 60 */ + static uint wait = 100; /* device will freeze with just 60 */ mutex_init(&koneplus->koneplus_lock); koneplus->startup_profile = koneplus_get_startup_profile(usb_dev); + if (koneplus->startup_profile < 0) + return koneplus->startup_profile; msleep(wait); retval = koneplus_get_info(usb_dev, &koneplus->info); @@ -651,21 +601,21 @@ static int koneplus_init_specials(struct hid_device *hdev) koneplus = kzalloc(sizeof(*koneplus), GFP_KERNEL); if (!koneplus) { - dev_err(&hdev->dev, "can't alloc device descriptor\n"); + hid_err(hdev, "can't alloc device descriptor\n"); return -ENOMEM; } hid_set_drvdata(hdev, koneplus); retval = koneplus_init_koneplus_device_struct(usb_dev, koneplus); if (retval) { - dev_err(&hdev->dev, - "couldn't init struct koneplus_device\n"); + hid_err(hdev, "couldn't init struct koneplus_device\n"); goto exit_free; } - retval = roccat_connect(koneplus_class, hdev); + retval = roccat_connect(koneplus_class, hdev, + sizeof(struct koneplus_roccat_report)); if (retval < 0) { - dev_err(&hdev->dev, "couldn't init char dev\n"); + hid_err(hdev, "couldn't init char dev\n"); } else { koneplus->chrdev_minor = retval; koneplus->roccat_claimed = 1; @@ -701,19 +651,19 @@ static int koneplus_probe(struct hid_device *hdev, retval = hid_parse(hdev); if (retval) { - dev_err(&hdev->dev, "parse failed\n"); + hid_err(hdev, "parse failed\n"); goto exit; } retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); if (retval) { - dev_err(&hdev->dev, "hw start failed\n"); + hid_err(hdev, "hw start failed\n"); goto exit; } retval = koneplus_init_specials(hdev); if (retval) { - dev_err(&hdev->dev, "couldn't install mouse\n"); + hid_err(hdev, "couldn't install mouse\n"); goto exit_stop; } @@ -769,8 +719,7 @@ static void koneplus_report_to_chrdev(struct koneplus_device const *koneplus, roccat_report.data2 = button_report->data2; roccat_report.profile = koneplus->actual_profile + 1; roccat_report_event(koneplus->chrdev_minor, - (uint8_t const *)&roccat_report, - sizeof(struct koneplus_roccat_report)); + (uint8_t const *)&roccat_report); } static int koneplus_raw_event(struct hid_device *hdev, @@ -825,8 +774,8 @@ static int __init koneplus_init(void) static void __exit koneplus_exit(void) { - class_destroy(koneplus_class); hid_unregister_driver(&koneplus_driver); + class_destroy(koneplus_class); } module_init(koneplus_init); diff --git a/drivers/hid/hid-roccat-kovaplus.c b/drivers/hid/hid-roccat-kovaplus.c new file mode 100644 index 000000000000..984be2f8967e --- /dev/null +++ b/drivers/hid/hid-roccat-kovaplus.c @@ -0,0 +1,715 @@ +/* + * Roccat Kova[+] driver for Linux + * + * Copyright (c) 2011 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +/* + * Roccat Kova[+] is a bigger version of the Pyra with two more side buttons. + */ + +#include <linux/device.h> +#include <linux/input.h> +#include <linux/hid.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/hid-roccat.h> +#include "hid-ids.h" +#include "hid-roccat-common.h" +#include "hid-roccat-kovaplus.h" + +static uint profile_numbers[5] = {0, 1, 2, 3, 4}; + +static struct class *kovaplus_class; + +static uint kovaplus_convert_event_cpi(uint value) +{ + return (value == 7 ? 4 : (value == 4 ? 3 : value)); +} + +static void kovaplus_profile_activated(struct kovaplus_device *kovaplus, + uint new_profile_index) +{ + kovaplus->actual_profile = new_profile_index; + kovaplus->actual_cpi = kovaplus->profile_settings[new_profile_index].cpi_startup_level; + kovaplus->actual_x_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_x; + kovaplus->actual_y_sensitivity = kovaplus->profile_settings[new_profile_index].sensitivity_y; +} + +static int kovaplus_send_control(struct usb_device *usb_dev, uint value, + enum kovaplus_control_requests request) +{ + int retval; + struct kovaplus_control control; + + if ((request == KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS || + request == KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS) && + value > 4) + return -EINVAL; + + control.command = KOVAPLUS_COMMAND_CONTROL; + control.value = value; + control.request = request; + + retval = roccat_common_send(usb_dev, KOVAPLUS_USB_COMMAND_CONTROL, + &control, sizeof(struct kovaplus_control)); + + return retval; +} + +static int kovaplus_receive_control_status(struct usb_device *usb_dev) +{ + int retval; + struct kovaplus_control control; + + do { + retval = roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_CONTROL, + &control, sizeof(struct kovaplus_control)); + + /* check if we get a completely wrong answer */ + if (retval) + return retval; + + if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OK) + return 0; + + /* indicates that hardware needs some more time to complete action */ + if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT) { + msleep(500); /* windows driver uses 1000 */ + continue; + } + + /* seems to be critical - replug necessary */ + if (control.value == KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD) + return -EINVAL; + + hid_err(usb_dev, "kovaplus_receive_control_status: " + "unknown response value 0x%x\n", control.value); + return -EINVAL; + } while (1); +} + +static int kovaplus_send(struct usb_device *usb_dev, uint command, + void const *buf, uint size) +{ + int retval; + + retval = roccat_common_send(usb_dev, command, buf, size); + if (retval) + return retval; + + msleep(100); + + return kovaplus_receive_control_status(usb_dev); +} + +static int kovaplus_select_profile(struct usb_device *usb_dev, uint number, + enum kovaplus_control_requests request) +{ + return kovaplus_send_control(usb_dev, number, request); +} + +static int kovaplus_get_info(struct usb_device *usb_dev, + struct kovaplus_info *buf) +{ + return roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_INFO, + buf, sizeof(struct kovaplus_info)); +} + +static int kovaplus_get_profile_settings(struct usb_device *usb_dev, + struct kovaplus_profile_settings *buf, uint number) +{ + int retval; + + retval = kovaplus_select_profile(usb_dev, number, + KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS); + if (retval) + return retval; + + return roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_SETTINGS, + buf, sizeof(struct kovaplus_profile_settings)); +} + +static int kovaplus_set_profile_settings(struct usb_device *usb_dev, + struct kovaplus_profile_settings const *settings) +{ + return kovaplus_send(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_SETTINGS, + settings, sizeof(struct kovaplus_profile_settings)); +} + +static int kovaplus_get_profile_buttons(struct usb_device *usb_dev, + struct kovaplus_profile_buttons *buf, int number) +{ + int retval; + + retval = kovaplus_select_profile(usb_dev, number, + KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS); + if (retval) + return retval; + + return roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_BUTTONS, + buf, sizeof(struct kovaplus_profile_buttons)); +} + +static int kovaplus_set_profile_buttons(struct usb_device *usb_dev, + struct kovaplus_profile_buttons const *buttons) +{ + return kovaplus_send(usb_dev, KOVAPLUS_USB_COMMAND_PROFILE_BUTTONS, + buttons, sizeof(struct kovaplus_profile_buttons)); +} + +/* retval is 0-4 on success, < 0 on error */ +static int kovaplus_get_actual_profile(struct usb_device *usb_dev) +{ + struct kovaplus_actual_profile buf; + int retval; + + retval = roccat_common_receive(usb_dev, KOVAPLUS_USB_COMMAND_ACTUAL_PROFILE, + &buf, sizeof(struct kovaplus_actual_profile)); + + return retval ? retval : buf.actual_profile; +} + +static int kovaplus_set_actual_profile(struct usb_device *usb_dev, + int new_profile) +{ + struct kovaplus_actual_profile buf; + + buf.command = KOVAPLUS_COMMAND_ACTUAL_PROFILE; + buf.size = sizeof(struct kovaplus_actual_profile); + buf.actual_profile = new_profile; + + return kovaplus_send(usb_dev, KOVAPLUS_USB_COMMAND_ACTUAL_PROFILE, + &buf, sizeof(struct kovaplus_actual_profile)); +} + +static ssize_t kovaplus_sysfs_read_profilex_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct kovaplus_profile_settings)) + return 0; + + if (off + count > sizeof(struct kovaplus_profile_settings)) + count = sizeof(struct kovaplus_profile_settings) - off; + + mutex_lock(&kovaplus->kovaplus_lock); + memcpy(buf, ((char const *)&kovaplus->profile_settings[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&kovaplus->kovaplus_lock); + + return count; +} + +static ssize_t kovaplus_sysfs_write_profile_settings(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + int profile_index; + struct kovaplus_profile_settings *profile_settings; + + if (off != 0 || count != sizeof(struct kovaplus_profile_settings)) + return -EINVAL; + + profile_index = ((struct kovaplus_profile_settings const *)buf)->profile_index; + profile_settings = &kovaplus->profile_settings[profile_index]; + + mutex_lock(&kovaplus->kovaplus_lock); + difference = memcmp(buf, profile_settings, + sizeof(struct kovaplus_profile_settings)); + if (difference) { + retval = kovaplus_set_profile_settings(usb_dev, + (struct kovaplus_profile_settings const *)buf); + if (!retval) + memcpy(profile_settings, buf, + sizeof(struct kovaplus_profile_settings)); + } + mutex_unlock(&kovaplus->kovaplus_lock); + + if (retval) + return retval; + + return sizeof(struct kovaplus_profile_settings); +} + +static ssize_t kovaplus_sysfs_read_profilex_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + + if (off >= sizeof(struct kovaplus_profile_buttons)) + return 0; + + if (off + count > sizeof(struct kovaplus_profile_buttons)) + count = sizeof(struct kovaplus_profile_buttons) - off; + + mutex_lock(&kovaplus->kovaplus_lock); + memcpy(buf, ((char const *)&kovaplus->profile_buttons[*(uint *)(attr->private)]) + off, + count); + mutex_unlock(&kovaplus->kovaplus_lock); + + return count; +} + +static ssize_t kovaplus_sysfs_write_profile_buttons(struct file *fp, + struct kobject *kobj, struct bin_attribute *attr, char *buf, + loff_t off, size_t count) +{ + struct device *dev = + container_of(kobj, struct device, kobj)->parent->parent; + struct kovaplus_device *kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + struct usb_device *usb_dev = interface_to_usbdev(to_usb_interface(dev)); + int retval = 0; + int difference; + uint profile_index; + struct kovaplus_profile_buttons *profile_buttons; + + if (off != 0 || count != sizeof(struct kovaplus_profile_buttons)) + return -EINVAL; + + profile_index = ((struct kovaplus_profile_buttons const *)buf)->profile_index; + profile_buttons = &kovaplus->profile_buttons[profile_index]; + + mutex_lock(&kovaplus->kovaplus_lock); + difference = memcmp(buf, profile_buttons, + sizeof(struct kovaplus_profile_buttons)); + if (difference) { + retval = kovaplus_set_profile_buttons(usb_dev, + (struct kovaplus_profile_buttons const *)buf); + if (!retval) + memcpy(profile_buttons, buf, + sizeof(struct kovaplus_profile_buttons)); + } + mutex_unlock(&kovaplus->kovaplus_lock); + + if (retval) + return retval; + + return sizeof(struct kovaplus_profile_buttons); +} + +static ssize_t kovaplus_sysfs_show_actual_profile(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kovaplus_device *kovaplus = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_profile); +} + +static ssize_t kovaplus_sysfs_set_actual_profile(struct device *dev, + struct device_attribute *attr, char const *buf, size_t size) +{ + struct kovaplus_device *kovaplus; + struct usb_device *usb_dev; + unsigned long profile; + int retval; + + dev = dev->parent->parent; + kovaplus = hid_get_drvdata(dev_get_drvdata(dev)); + usb_dev = interface_to_usbdev(to_usb_interface(dev)); + + retval = strict_strtoul(buf, 10, &profile); + if (retval) + return retval; + + if (profile >= 5) + return -EINVAL; + + mutex_lock(&kovaplus->kovaplus_lock); + retval = kovaplus_set_actual_profile(usb_dev, profile); + kovaplus->actual_profile = profile; + mutex_unlock(&kovaplus->kovaplus_lock); + if (retval) + return retval; + + return size; +} + +static ssize_t kovaplus_sysfs_show_actual_cpi(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kovaplus_device *kovaplus = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_cpi); +} + +static ssize_t kovaplus_sysfs_show_actual_sensitivity_x(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kovaplus_device *kovaplus = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_x_sensitivity); +} + +static ssize_t kovaplus_sysfs_show_actual_sensitivity_y(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kovaplus_device *kovaplus = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->actual_y_sensitivity); +} + +static ssize_t kovaplus_sysfs_show_firmware_version(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct kovaplus_device *kovaplus = + hid_get_drvdata(dev_get_drvdata(dev->parent->parent)); + return snprintf(buf, PAGE_SIZE, "%d\n", kovaplus->info.firmware_version); +} + +static struct device_attribute kovaplus_attributes[] = { + __ATTR(actual_cpi, 0440, + kovaplus_sysfs_show_actual_cpi, NULL), + __ATTR(firmware_version, 0440, + kovaplus_sysfs_show_firmware_version, NULL), + __ATTR(actual_profile, 0660, + kovaplus_sysfs_show_actual_profile, + kovaplus_sysfs_set_actual_profile), + __ATTR(actual_sensitivity_x, 0440, + kovaplus_sysfs_show_actual_sensitivity_x, NULL), + __ATTR(actual_sensitivity_y, 0440, + kovaplus_sysfs_show_actual_sensitivity_y, NULL), + __ATTR_NULL +}; + +static struct bin_attribute kovaplus_bin_attributes[] = { + { + .attr = { .name = "profile_settings", .mode = 0220 }, + .size = sizeof(struct kovaplus_profile_settings), + .write = kovaplus_sysfs_write_profile_settings + }, + { + .attr = { .name = "profile1_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_settings", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_settings), + .read = kovaplus_sysfs_read_profilex_settings, + .private = &profile_numbers[4] + }, + { + .attr = { .name = "profile_buttons", .mode = 0220 }, + .size = sizeof(struct kovaplus_profile_buttons), + .write = kovaplus_sysfs_write_profile_buttons + }, + { + .attr = { .name = "profile1_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[0] + }, + { + .attr = { .name = "profile2_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[1] + }, + { + .attr = { .name = "profile3_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[2] + }, + { + .attr = { .name = "profile4_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[3] + }, + { + .attr = { .name = "profile5_buttons", .mode = 0440 }, + .size = sizeof(struct kovaplus_profile_buttons), + .read = kovaplus_sysfs_read_profilex_buttons, + .private = &profile_numbers[4] + }, + __ATTR_NULL +}; + +static int kovaplus_init_kovaplus_device_struct(struct usb_device *usb_dev, + struct kovaplus_device *kovaplus) +{ + int retval, i; + static uint wait = 70; /* device will freeze with just 60 */ + + mutex_init(&kovaplus->kovaplus_lock); + + retval = kovaplus_get_info(usb_dev, &kovaplus->info); + if (retval) + return retval; + + for (i = 0; i < 5; ++i) { + msleep(wait); + retval = kovaplus_get_profile_settings(usb_dev, + &kovaplus->profile_settings[i], i); + if (retval) + return retval; + + msleep(wait); + retval = kovaplus_get_profile_buttons(usb_dev, + &kovaplus->profile_buttons[i], i); + if (retval) + return retval; + } + + msleep(wait); + retval = kovaplus_get_actual_profile(usb_dev); + if (retval < 0) + return retval; + kovaplus_profile_activated(kovaplus, retval); + + return 0; +} + +static int kovaplus_init_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct usb_device *usb_dev = interface_to_usbdev(intf); + struct kovaplus_device *kovaplus; + int retval; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_MOUSE) { + + kovaplus = kzalloc(sizeof(*kovaplus), GFP_KERNEL); + if (!kovaplus) { + hid_err(hdev, "can't alloc device descriptor\n"); + return -ENOMEM; + } + hid_set_drvdata(hdev, kovaplus); + + retval = kovaplus_init_kovaplus_device_struct(usb_dev, kovaplus); + if (retval) { + hid_err(hdev, "couldn't init struct kovaplus_device\n"); + goto exit_free; + } + + retval = roccat_connect(kovaplus_class, hdev, + sizeof(struct kovaplus_roccat_report)); + if (retval < 0) { + hid_err(hdev, "couldn't init char dev\n"); + } else { + kovaplus->chrdev_minor = retval; + kovaplus->roccat_claimed = 1; + } + + } else { + hid_set_drvdata(hdev, NULL); + } + + return 0; +exit_free: + kfree(kovaplus); + return retval; +} + +static void kovaplus_remove_specials(struct hid_device *hdev) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct kovaplus_device *kovaplus; + + if (intf->cur_altsetting->desc.bInterfaceProtocol + == USB_INTERFACE_PROTOCOL_MOUSE) { + kovaplus = hid_get_drvdata(hdev); + if (kovaplus->roccat_claimed) + roccat_disconnect(kovaplus->chrdev_minor); + kfree(kovaplus); + } +} + +static int kovaplus_probe(struct hid_device *hdev, + const struct hid_device_id *id) +{ + int retval; + + retval = hid_parse(hdev); + if (retval) { + hid_err(hdev, "parse failed\n"); + goto exit; + } + + retval = hid_hw_start(hdev, HID_CONNECT_DEFAULT); + if (retval) { + hid_err(hdev, "hw start failed\n"); + goto exit; + } + + retval = kovaplus_init_specials(hdev); + if (retval) { + hid_err(hdev, "couldn't install mouse\n"); + goto exit_stop; + } + + return 0; + +exit_stop: + hid_hw_stop(hdev); +exit: + return retval; +} + +static void kovaplus_remove(struct hid_device *hdev) +{ + kovaplus_remove_specials(hdev); + hid_hw_stop(hdev); +} + +static void kovaplus_keep_values_up_to_date(struct kovaplus_device *kovaplus, + u8 const *data) +{ + struct kovaplus_mouse_report_button const *button_report; + + if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON) + return; + + button_report = (struct kovaplus_mouse_report_button const *)data; + + switch (button_report->type) { + case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1: + kovaplus_profile_activated(kovaplus, button_report->data1 - 1); + break; + case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI: + kovaplus->actual_cpi = kovaplus_convert_event_cpi(button_report->data1); + case KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY: + kovaplus->actual_x_sensitivity = button_report->data1; + kovaplus->actual_y_sensitivity = button_report->data2; + } +} + +static void kovaplus_report_to_chrdev(struct kovaplus_device const *kovaplus, + u8 const *data) +{ + struct kovaplus_roccat_report roccat_report; + struct kovaplus_mouse_report_button const *button_report; + + if (data[0] != KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON) + return; + + button_report = (struct kovaplus_mouse_report_button const *)data; + + if (button_report->type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2) + return; + + roccat_report.type = button_report->type; + roccat_report.profile = kovaplus->actual_profile + 1; + + if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO || + roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT || + roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH || + roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER) + roccat_report.button = button_report->data1; + else + roccat_report.button = 0; + + if (roccat_report.type == KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI) + roccat_report.data1 = kovaplus_convert_event_cpi(button_report->data1); + else + roccat_report.data1 = button_report->data1; + + roccat_report.data2 = button_report->data2; + + roccat_report_event(kovaplus->chrdev_minor, + (uint8_t const *)&roccat_report); +} + +static int kovaplus_raw_event(struct hid_device *hdev, + struct hid_report *report, u8 *data, int size) +{ + struct usb_interface *intf = to_usb_interface(hdev->dev.parent); + struct kovaplus_device *kovaplus = hid_get_drvdata(hdev); + + if (intf->cur_altsetting->desc.bInterfaceProtocol + != USB_INTERFACE_PROTOCOL_MOUSE) + return 0; + + kovaplus_keep_values_up_to_date(kovaplus, data); + + if (kovaplus->roccat_claimed) + kovaplus_report_to_chrdev(kovaplus, data); + + return 0; +} + +static const struct hid_device_id kovaplus_devices[] = { + { HID_USB_DEVICE(USB_VENDOR_ID_ROCCAT, USB_DEVICE_ID_ROCCAT_KOVAPLUS) }, + { } +}; + +MODULE_DEVICE_TABLE(hid, kovaplus_devices); + +static struct hid_driver kovaplus_driver = { + .name = "kovaplus", + .id_table = kovaplus_devices, + .probe = kovaplus_probe, + .remove = kovaplus_remove, + .raw_event = kovaplus_raw_event +}; + +static int __init kovaplus_init(void) +{ + int retval; + + kovaplus_class = class_create(THIS_MODULE, "kovaplus"); + if (IS_ERR(kovaplus_class)) + return PTR_ERR(kovaplus_class); + kovaplus_class->dev_attrs = kovaplus_attributes; + kovaplus_class->dev_bin_attrs = kovaplus_bin_attributes; + + retval = hid_register_driver(&kovaplus_driver); + if (retval) + class_destroy(kovaplus_class); + return retval; +} + +static void __exit kovaplus_exit(void) +{ + hid_unregister_driver(&kovaplus_driver); + class_destroy(kovaplus_class); +} + +module_init(kovaplus_init); +module_exit(kovaplus_exit); + +MODULE_AUTHOR("Stefan Achatz"); +MODULE_DESCRIPTION("USB Roccat Kova[+] driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/hid/hid-roccat-kovaplus.h b/drivers/hid/hid-roccat-kovaplus.h new file mode 100644 index 000000000000..ce40607d21c7 --- /dev/null +++ b/drivers/hid/hid-roccat-kovaplus.h @@ -0,0 +1,157 @@ +#ifndef __HID_ROCCAT_KOVAPLUS_H +#define __HID_ROCCAT_KOVAPLUS_H + +/* + * Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net> + */ + +/* + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the Free + * Software Foundation; either version 2 of the License, or (at your option) + * any later version. + */ + +#include <linux/types.h> + +struct kovaplus_control { + uint8_t command; /* KOVAPLUS_COMMAND_CONTROL */ + uint8_t value; + uint8_t request; +} __packed; + +enum kovaplus_control_requests { + /* read after write; value = 1 */ + KOVAPLUS_CONTROL_REQUEST_STATUS = 0x0, + /* write; value = profile number range 0-4 */ + KOVAPLUS_CONTROL_REQUEST_PROFILE_SETTINGS = 0x10, + /* write; value = profile number range 0-4 */ + KOVAPLUS_CONTROL_REQUEST_PROFILE_BUTTONS = 0x20, +}; + +enum kovaplus_control_values { + KOVAPLUS_CONTROL_REQUEST_STATUS_OVERLOAD = 0, /* supposed */ + KOVAPLUS_CONTROL_REQUEST_STATUS_OK = 1, + KOVAPLUS_CONTROL_REQUEST_STATUS_WAIT = 3, /* supposed */ +}; + +struct kovaplus_actual_profile { + uint8_t command; /* KOVAPLUS_COMMAND_ACTUAL_PROFILE */ + uint8_t size; /* always 3 */ + uint8_t actual_profile; /* Range 0-4! */ +} __packed; + +struct kovaplus_profile_settings { + uint8_t command; /* KOVAPLUS_COMMAND_PROFILE_SETTINGS */ + uint8_t size; /* 16 */ + uint8_t profile_index; /* range 0-4 */ + uint8_t unknown1; + uint8_t sensitivity_x; /* range 1-10 */ + uint8_t sensitivity_y; /* range 1-10 */ + uint8_t cpi_levels_enabled; + uint8_t cpi_startup_level; /* range 1-4 */ + uint8_t data[8]; +} __packed; + +struct kovaplus_profile_buttons { + uint8_t command; /* KOVAPLUS_COMMAND_PROFILE_BUTTONS */ + uint8_t size; /* 23 */ + uint8_t profile_index; /* range 0-4 */ + uint8_t data[20]; +} __packed; + +struct kovaplus_info { + uint8_t command; /* KOVAPLUS_COMMAND_INFO */ + uint8_t size; /* 6 */ + uint8_t firmware_version; + uint8_t unknown[3]; +} __packed; + +/* writes 1 on plugin */ +struct kovaplus_a { + uint8_t command; /* KOVAPLUS_COMMAND_A */ + uint8_t size; /* 3 */ + uint8_t unknown; +} __packed; + +enum kovaplus_commands { + KOVAPLUS_COMMAND_CONTROL = 0x4, + KOVAPLUS_COMMAND_ACTUAL_PROFILE = 0x5, + KOVAPLUS_COMMAND_PROFILE_SETTINGS = 0x6, + KOVAPLUS_COMMAND_PROFILE_BUTTONS = 0x7, + KOVAPLUS_COMMAND_INFO = 0x9, + KOVAPLUS_COMMAND_A = 0xa, +}; + +enum kovaplus_usb_commands { + KOVAPLUS_USB_COMMAND_CONTROL = 0x304, + KOVAPLUS_USB_COMMAND_ACTUAL_PROFILE = 0x305, + KOVAPLUS_USB_COMMAND_PROFILE_SETTINGS = 0x306, + KOVAPLUS_USB_COMMAND_PROFILE_BUTTONS = 0x307, + KOVAPLUS_USB_COMMAND_INFO = 0x309, + KOVAPLUS_USB_COMMAND_A = 0x30a, +}; + +enum kovaplus_mouse_report_numbers { + KOVAPLUS_MOUSE_REPORT_NUMBER_MOUSE = 1, + KOVAPLUS_MOUSE_REPORT_NUMBER_AUDIO = 2, + KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON = 3, + KOVAPLUS_MOUSE_REPORT_NUMBER_KBD = 4, +}; + +struct kovaplus_mouse_report_button { + uint8_t report_number; /* KOVAPLUS_MOUSE_REPORT_NUMBER_BUTTON */ + uint8_t unknown1; + uint8_t type; + uint8_t data1; + uint8_t data2; +} __packed; + +enum kovaplus_mouse_report_button_types { + /* data1 = profile_number range 1-5; no release event */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_1 = 0x20, + /* data1 = profile_number range 1-5; no release event */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_PROFILE_2 = 0x30, + /* data1 = button_number range 1-18; data2 = action */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MACRO = 0x40, + /* data1 = button_number range 1-18; data2 = action */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SHORTCUT = 0x50, + /* data1 = button_number range 1-18; data2 = action */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_QUICKLAUNCH = 0x60, + /* data1 = button_number range 1-18; data2 = action */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_TIMER = 0x80, + /* data1 = 1 = 400, 2 = 800, 4 = 1600, 7 = 3200; no release event */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_CPI = 0xb0, + /* data1 + data2 = sense range 1-10; no release event */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_SENSITIVITY = 0xc0, + /* data1 = type as in profile_buttons; data2 = action */ + KOVAPLUS_MOUSE_REPORT_BUTTON_TYPE_MULTIMEDIA = 0xf0, +}; + +enum kovaplus_mouse_report_button_actions { + KOVAPLUS_MOUSE_REPORT_BUTTON_ACTION_PRESS = 0, + KOVAPLUS_MOUSE_REPORT_BUTTON_ACTION_RELEASE = 1, +}; + +struct kovaplus_roccat_report { + uint8_t type; + uint8_t profile; + uint8_t button; + uint8_t data1; + uint8_t data2; +} __packed; + +struct kovaplus_device { + int actual_profile; + int actual_cpi; + int actual_x_sensitivity; + int actual_y_sensitivity; + int roccat_claimed; + int chrdev_minor; + struct mutex kovaplus_lock; + struct kovaplus_info info; + struct kovaplus_profile_settings profile_settings[5]; + struct kovaplus_profile_buttons profile_buttons[5]; +}; + +#endif diff --git a/drivers/hid/hid-roccat-pyra.c b/drivers/hid/hid-roccat-pyra.c index 02c58e015bee..160f481344f6 100644 --- a/drivers/hid/hid-roccat-pyra.c +++ b/drivers/hid/hid-roccat-pyra.c @@ -20,11 +20,11 @@ #include <linux/device.h> #include <linux/input.h> #include <linux/hid.h> -#include <linux/usb.h> #include <linux/module.h> #include <linux/slab.h> +#include <linux/hid-roccat.h> #include "hid-ids.h" -#include "hid-roccat.h" +#include "hid-roccat-common.h" #include "hid-roccat-pyra.h" static uint profile_numbers[5] = {0, 1, 2, 3, 4}; @@ -42,7 +42,6 @@ static void profile_activated(struct pyra_device *pyra, static int pyra_send_control(struct usb_device *usb_dev, int value, enum pyra_control_requests request) { - int len; struct pyra_control control; if ((request == PYRA_CONTROL_REQUEST_PROFILE_SETTINGS || @@ -54,47 +53,31 @@ static int pyra_send_control(struct usb_device *usb_dev, int value, control.value = value; control.request = request; - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - PYRA_USB_COMMAND_CONTROL, 0, (char *)&control, - sizeof(struct pyra_control), - USB_CTRL_SET_TIMEOUT); - - if (len != sizeof(struct pyra_control)) - return len; - - return 0; + return roccat_common_send(usb_dev, PYRA_USB_COMMAND_CONTROL, + &control, sizeof(struct pyra_control)); } static int pyra_receive_control_status(struct usb_device *usb_dev) { - int len; + int retval; struct pyra_control control; do { msleep(10); - - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | - USB_DIR_IN, - PYRA_USB_COMMAND_CONTROL, 0, (char *)&control, - sizeof(struct pyra_control), - USB_CTRL_SET_TIMEOUT); + retval = roccat_common_receive(usb_dev, PYRA_USB_COMMAND_CONTROL, + &control, sizeof(struct pyra_control)); /* requested too early, try again */ - } while (len == -EPROTO); + } while (retval == -EPROTO); - if (len == sizeof(struct pyra_control) && - control.command == PYRA_COMMAND_CONTROL && + if (!retval && control.command == PYRA_COMMAND_CONTROL && control.request == PYRA_CONTROL_REQUEST_STATUS && control.value == 1) - return 0; + return 0; else { hid_err(usb_dev, "receive control status: unknown response 0x%x 0x%x\n", control.request, control.value); - return -EINVAL; + return retval ? retval : -EINVAL; } } @@ -102,125 +85,72 @@ static int pyra_get_profile_settings(struct usb_device *usb_dev, struct pyra_profile_settings *buf, int number) { int retval; - retval = pyra_send_control(usb_dev, number, PYRA_CONTROL_REQUEST_PROFILE_SETTINGS); - if (retval) return retval; - - retval = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - PYRA_USB_COMMAND_PROFILE_SETTINGS, 0, (char *)buf, - sizeof(struct pyra_profile_settings), - USB_CTRL_SET_TIMEOUT); - - if (retval != sizeof(struct pyra_profile_settings)) - return retval; - - return 0; + return roccat_common_receive(usb_dev, PYRA_USB_COMMAND_PROFILE_SETTINGS, + buf, sizeof(struct pyra_profile_settings)); } static int pyra_get_profile_buttons(struct usb_device *usb_dev, struct pyra_profile_buttons *buf, int number) { int retval; - retval = pyra_send_control(usb_dev, number, PYRA_CONTROL_REQUEST_PROFILE_BUTTONS); - if (retval) return retval; - - retval = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - PYRA_USB_COMMAND_PROFILE_BUTTONS, 0, (char *)buf, - sizeof(struct pyra_profile_buttons), - USB_CTRL_SET_TIMEOUT); - - if (retval != sizeof(struct pyra_profile_buttons)) - return retval; - - return 0; + return roccat_common_receive(usb_dev, PYRA_USB_COMMAND_PROFILE_BUTTONS, + buf, sizeof(struct pyra_profile_buttons)); } static int pyra_get_settings(struct usb_device *usb_dev, struct pyra_settings *buf) { - int len; - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - PYRA_USB_COMMAND_SETTINGS, 0, buf, - sizeof(struct pyra_settings), USB_CTRL_SET_TIMEOUT); - if (len != sizeof(struct pyra_settings)) - return -EIO; - return 0; + return roccat_common_receive(usb_dev, PYRA_USB_COMMAND_SETTINGS, + buf, sizeof(struct pyra_settings)); } static int pyra_get_info(struct usb_device *usb_dev, struct pyra_info *buf) { - int len; - len = usb_control_msg(usb_dev, usb_rcvctrlpipe(usb_dev, 0), - USB_REQ_CLEAR_FEATURE, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_IN, - PYRA_USB_COMMAND_INFO, 0, buf, - sizeof(struct pyra_info), USB_CTRL_SET_TIMEOUT); - if (len != sizeof(struct pyra_info)) - return -EIO; - return 0; + return roccat_common_receive(usb_dev, PYRA_USB_COMMAND_INFO, + buf, sizeof(struct pyra_info)); +} + +static int pyra_send(struct usb_device *usb_dev, uint command, + void const *buf, uint size) +{ + int retval; + retval = roccat_common_send(usb_dev, command, buf, size); + if (retval) + return retval; + return pyra_receive_control_status(usb_dev); } static int pyra_set_profile_settings(struct usb_device *usb_dev, struct pyra_profile_settings const *settings) { - int len; - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - PYRA_USB_COMMAND_PROFILE_SETTINGS, 0, (char *)settings, - sizeof(struct pyra_profile_settings), - USB_CTRL_SET_TIMEOUT); - if (len != sizeof(struct pyra_profile_settings)) - return -EIO; - if (pyra_receive_control_status(usb_dev)) - return -EIO; - return 0; + return pyra_send(usb_dev, PYRA_USB_COMMAND_PROFILE_SETTINGS, settings, + sizeof(struct pyra_profile_settings)); } static int pyra_set_profile_buttons(struct usb_device *usb_dev, struct pyra_profile_buttons const *buttons) { - int len; - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - PYRA_USB_COMMAND_PROFILE_BUTTONS, 0, (char *)buttons, - sizeof(struct pyra_profile_buttons), - USB_CTRL_SET_TIMEOUT); - if (len != sizeof(struct pyra_profile_buttons)) - return -EIO; - if (pyra_receive_control_status(usb_dev)) - return -EIO; - return 0; + return pyra_send(usb_dev, PYRA_USB_COMMAND_PROFILE_BUTTONS, buttons, + sizeof(struct pyra_profile_buttons)); } static int pyra_set_settings(struct usb_device *usb_dev, struct pyra_settings const *settings) { - int len; - len = usb_control_msg(usb_dev, usb_sndctrlpipe(usb_dev, 0), - USB_REQ_SET_CONFIGURATION, - USB_TYPE_CLASS | USB_RECIP_INTERFACE | USB_DIR_OUT, - PYRA_USB_COMMAND_SETTINGS, 0, (char *)settings, - sizeof(struct pyra_settings), USB_CTRL_SET_TIMEOUT); - if (len != sizeof(struct pyra_settings)) - return -EIO; - if (pyra_receive_control_status(usb_dev)) - return -EIO; - return 0; + int retval; + retval = roccat_common_send(usb_dev, PYRA_USB_COMMAND_SETTINGS, settings, + sizeof(struct pyra_settings)); + if (retval) + return retval; + return pyra_receive_control_status(usb_dev); } static ssize_t pyra_sysfs_read_profilex_settings(struct file *fp, @@ -521,21 +451,16 @@ static struct bin_attribute pyra_bin_attributes[] = { static int pyra_init_pyra_device_struct(struct usb_device *usb_dev, struct pyra_device *pyra) { - struct pyra_info *info; + struct pyra_info info; int retval, i; mutex_init(&pyra->pyra_lock); - info = kmalloc(sizeof(struct pyra_info), GFP_KERNEL); - if (!info) - return -ENOMEM; - retval = pyra_get_info(usb_dev, info); - if (retval) { - kfree(info); + retval = pyra_get_info(usb_dev, &info); + if (retval) return retval; - } - pyra->firmware_version = info->firmware_version; - kfree(info); + + pyra->firmware_version = info.firmware_version; retval = pyra_get_settings(usb_dev, &pyra->settings); if (retval) @@ -581,7 +506,8 @@ static int pyra_init_specials(struct hid_device *hdev) goto exit_free; } - retval = roccat_connect(pyra_class, hdev); + retval = roccat_connect(pyra_class, hdev, + sizeof(struct pyra_roccat_report)); if (retval < 0) { hid_err(hdev, "couldn't init char dev\n"); } else { @@ -685,8 +611,7 @@ static void pyra_report_to_chrdev(struct pyra_device const *pyra, roccat_report.value = button_event->data1; roccat_report.key = 0; roccat_report_event(pyra->chrdev_minor, - (uint8_t const *)&roccat_report, - sizeof(struct pyra_roccat_report)); + (uint8_t const *)&roccat_report); break; case PYRA_MOUSE_EVENT_BUTTON_TYPE_MACRO: case PYRA_MOUSE_EVENT_BUTTON_TYPE_SHORTCUT: @@ -700,8 +625,7 @@ static void pyra_report_to_chrdev(struct pyra_device const *pyra, */ roccat_report.value = pyra->actual_profile + 1; roccat_report_event(pyra->chrdev_minor, - (uint8_t const *)&roccat_report, - sizeof(struct pyra_roccat_report)); + (uint8_t const *)&roccat_report); } break; } @@ -761,8 +685,8 @@ static int __init pyra_init(void) static void __exit pyra_exit(void) { - class_destroy(pyra_class); hid_unregister_driver(&pyra_driver); + class_destroy(pyra_class); } module_init(pyra_init); diff --git a/drivers/hid/hid-roccat.c b/drivers/hid/hid-roccat.c index a14c579ea781..5666e7587b18 100644 --- a/drivers/hid/hid-roccat.c +++ b/drivers/hid/hid-roccat.c @@ -26,8 +26,7 @@ #include <linux/cdev.h> #include <linux/poll.h> #include <linux/sched.h> - -#include "hid-roccat.h" +#include <linux/hid-roccat.h> #define ROCCAT_FIRST_MINOR 0 #define ROCCAT_MAX_DEVICES 8 @@ -37,11 +36,11 @@ struct roccat_report { uint8_t *value; - int len; }; struct roccat_device { unsigned int minor; + int report_size; int open; int exist; wait_queue_head_t wait; @@ -123,7 +122,7 @@ static ssize_t roccat_read(struct file *file, char __user *buffer, * If report is larger than requested amount of data, rest of report * is lost! */ - len = report->len > count ? count : report->len; + len = device->report_size > count ? count : device->report_size; if (copy_to_user(buffer, report->value, len)) { retval = -EFAULT; @@ -248,26 +247,25 @@ static int roccat_release(struct inode *inode, struct file *file) * * This is called from interrupt handler. */ -int roccat_report_event(int minor, u8 const *data, int len) +int roccat_report_event(int minor, u8 const *data) { struct roccat_device *device; struct roccat_reader *reader; struct roccat_report *report; uint8_t *new_value; - new_value = kmemdup(data, len, GFP_ATOMIC); + device = devices[minor]; + + new_value = kmemdup(data, device->report_size, GFP_ATOMIC); if (!new_value) return -ENOMEM; - device = devices[minor]; - report = &device->cbuf[device->cbuf_end]; /* passing NULL is safe */ kfree(report->value); report->value = new_value; - report->len = len; device->cbuf_end = (device->cbuf_end + 1) % ROCCAT_CBUF_SIZE; list_for_each_entry(reader, &device->readers, node) { @@ -295,7 +293,7 @@ EXPORT_SYMBOL_GPL(roccat_report_event); * Return value is minor device number in Range [0, ROCCAT_MAX_DEVICES] on * success, a negative error code on failure. */ -int roccat_connect(struct class *klass, struct hid_device *hid) +int roccat_connect(struct class *klass, struct hid_device *hid, int report_size) { unsigned int minor; struct roccat_device *device; @@ -343,6 +341,7 @@ int roccat_connect(struct class *klass, struct hid_device *hid) device->hid = hid; device->exist = 1; device->cbuf_end = 0; + device->report_size = report_size; return minor; } @@ -357,13 +356,16 @@ void roccat_disconnect(int minor) mutex_lock(&devices_lock); device = devices[minor]; - devices[minor] = NULL; mutex_unlock(&devices_lock); device->exist = 0; /* TODO exist maybe not needed */ device_destroy(device->dev->class, MKDEV(roccat_major, minor)); + mutex_lock(&devices_lock); + devices[minor] = NULL; + mutex_unlock(&devices_lock); + if (device->open) { hid_hw_close(device->hid); wake_up_interruptible(&device->wait); @@ -373,6 +375,34 @@ void roccat_disconnect(int minor) } EXPORT_SYMBOL_GPL(roccat_disconnect); +static long roccat_ioctl(struct file *file, unsigned int cmd, unsigned long arg) +{ + struct inode *inode = file->f_path.dentry->d_inode; + struct roccat_device *device; + unsigned int minor = iminor(inode); + long retval = 0; + + mutex_lock(&devices_lock); + + device = devices[minor]; + if (!device) { + retval = -ENODEV; + goto out; + } + + switch (cmd) { + case ROCCATIOCGREPSIZE: + if (put_user(device->report_size, (int __user *)arg)) + retval = -EFAULT; + break; + default: + retval = -ENOTTY; + } +out: + mutex_unlock(&devices_lock); + return retval; +} + static const struct file_operations roccat_ops = { .owner = THIS_MODULE, .read = roccat_read, @@ -380,6 +410,7 @@ static const struct file_operations roccat_ops = { .open = roccat_open, .release = roccat_release, .llseek = noop_llseek, + .unlocked_ioctl = roccat_ioctl, }; static int __init roccat_init(void) diff --git a/drivers/hid/hid-roccat.h b/drivers/hid/hid-roccat.h deleted file mode 100644 index 5784281d613f..000000000000 --- a/drivers/hid/hid-roccat.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef __HID_ROCCAT_H -#define __HID_ROCCAT_H - -/* - * Copyright (c) 2010 Stefan Achatz <erazor_de@users.sourceforge.net> - */ - -/* - * This program is free software; you can redistribute it and/or modify it - * under the terms of the GNU General Public License as published by the Free - * Software Foundation; either version 2 of the License, or (at your option) - * any later version. - */ - -#include <linux/hid.h> -#include <linux/types.h> - -#if defined(CONFIG_HID_ROCCAT) || defined(CONFIG_HID_ROCCAT_MODULE) -int roccat_connect(struct class *klass, struct hid_device *hid); -void roccat_disconnect(int minor); -int roccat_report_event(int minor, u8 const *data, int len); -#else -static inline int roccat_connect(struct class *klass, - struct hid_device *hid) { return -1; } -static inline void roccat_disconnect(int minor) {} -static inline int roccat_report_event(int minor, u8 const *data, int len) -{ - return 0; -} -#endif - -#endif diff --git a/drivers/hid/hid-sony.c b/drivers/hid/hid-sony.c index 68d7b36e31e4..93819a08121a 100644 --- a/drivers/hid/hid-sony.c +++ b/drivers/hid/hid-sony.c @@ -46,6 +46,16 @@ static __u8 *sony_report_fixup(struct hid_device *hdev, __u8 *rdesc, return rdesc; } +/* + * The Sony Sixaxis does not handle HID Output Reports on the Interrupt EP + * like it should according to usbhid/hid-core.c::usbhid_output_raw_report() + * so we need to override that forcing HID Output Reports on the Control EP. + * + * There is also another issue about HID Output Reports via USB, the Sixaxis + * does not want the report_id as part of the data packet, so we have to + * discard buf[0] when sending the actual control message, even for numbered + * reports, humpf! + */ static int sixaxis_usb_output_raw_report(struct hid_device *hid, __u8 *buf, size_t count, unsigned char report_type) { @@ -55,6 +65,12 @@ static int sixaxis_usb_output_raw_report(struct hid_device *hid, __u8 *buf, int report_id = buf[0]; int ret; + if (report_type == HID_OUTPUT_REPORT) { + /* Don't send the Report ID */ + buf++; + count--; + } + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), HID_REQ_SET_REPORT, USB_DIR_OUT | USB_TYPE_CLASS | USB_RECIP_INTERFACE, @@ -62,6 +78,10 @@ static int sixaxis_usb_output_raw_report(struct hid_device *hid, __u8 *buf, interface->desc.bInterfaceNumber, buf, count, USB_CTRL_SET_TIMEOUT); + /* Count also the Report ID, in case of an Output report. */ + if (ret > 0 && report_type == HID_OUTPUT_REPORT) + ret++; + return ret; } diff --git a/drivers/hid/hidraw.c b/drivers/hid/hidraw.c index 468e87b53ed2..54409cba018c 100644 --- a/drivers/hid/hidraw.c +++ b/drivers/hid/hidraw.c @@ -91,7 +91,7 @@ static ssize_t hidraw_read(struct file *file, char __user *buffer, size_t count, ret = -EFAULT; goto out; } - ret += len; + ret = len; kfree(list->buffer[list->tail].value); list->tail = (list->tail + 1) & (HIDRAW_BUFFER_SIZE - 1); @@ -102,15 +102,14 @@ out: } /* the first byte is expected to be a report number */ -static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +/* This function is to be called with the minors_lock mutex held */ +static ssize_t hidraw_send_report(struct file *file, const char __user *buffer, size_t count, unsigned char report_type) { unsigned int minor = iminor(file->f_path.dentry->d_inode); struct hid_device *dev; __u8 *buf; int ret = 0; - mutex_lock(&minors_lock); - if (!hidraw_table[minor]) { ret = -ENODEV; goto out; @@ -148,14 +147,92 @@ static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t goto out_free; } - ret = dev->hid_output_raw_report(dev, buf, count, HID_OUTPUT_REPORT); + ret = dev->hid_output_raw_report(dev, buf, count, report_type); out_free: kfree(buf); out: + return ret; +} + +/* the first byte is expected to be a report number */ +static ssize_t hidraw_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos) +{ + ssize_t ret; + mutex_lock(&minors_lock); + ret = hidraw_send_report(file, buffer, count, HID_OUTPUT_REPORT); mutex_unlock(&minors_lock); return ret; } + +/* This function performs a Get_Report transfer over the control endpoint + per section 7.2.1 of the HID specification, version 1.1. The first byte + of buffer is the report number to request, or 0x0 if the defice does not + use numbered reports. The report_type parameter can be HID_FEATURE_REPORT + or HID_INPUT_REPORT. This function is to be called with the minors_lock + mutex held. */ +static ssize_t hidraw_get_report(struct file *file, char __user *buffer, size_t count, unsigned char report_type) +{ + unsigned int minor = iminor(file->f_path.dentry->d_inode); + struct hid_device *dev; + __u8 *buf; + int ret = 0, len; + unsigned char report_number; + + dev = hidraw_table[minor]->hid; + + if (!dev->hid_get_raw_report) { + ret = -ENODEV; + goto out; + } + + if (count > HID_MAX_BUFFER_SIZE) { + printk(KERN_WARNING "hidraw: pid %d passed too large report\n", + task_pid_nr(current)); + ret = -EINVAL; + goto out; + } + + if (count < 2) { + printk(KERN_WARNING "hidraw: pid %d passed too short report\n", + task_pid_nr(current)); + ret = -EINVAL; + goto out; + } + + buf = kmalloc(count * sizeof(__u8), GFP_KERNEL); + if (!buf) { + ret = -ENOMEM; + goto out; + } + + /* Read the first byte from the user. This is the report number, + which is passed to dev->hid_get_raw_report(). */ + if (copy_from_user(&report_number, buffer, 1)) { + ret = -EFAULT; + goto out_free; + } + + ret = dev->hid_get_raw_report(dev, report_number, buf, count, report_type); + + if (ret < 0) + goto out_free; + + len = (ret < count) ? ret : count; + + if (copy_to_user(buffer, buf, len)) { + ret = -EFAULT; + goto out_free; + } + + ret = len; + +out_free: + kfree(buf); +out: + return ret; +} + static unsigned int hidraw_poll(struct file *file, poll_table *wait) { struct hidraw_list *list = file->private_data; @@ -295,7 +372,24 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, default: { struct hid_device *hid = dev->hid; - if (_IOC_TYPE(cmd) != 'H' || _IOC_DIR(cmd) != _IOC_READ) { + if (_IOC_TYPE(cmd) != 'H') { + ret = -EINVAL; + break; + } + + if (_IOC_NR(cmd) == _IOC_NR(HIDIOCSFEATURE(0))) { + int len = _IOC_SIZE(cmd); + ret = hidraw_send_report(file, user_arg, len, HID_FEATURE_REPORT); + break; + } + if (_IOC_NR(cmd) == _IOC_NR(HIDIOCGFEATURE(0))) { + int len = _IOC_SIZE(cmd); + ret = hidraw_get_report(file, user_arg, len, HID_FEATURE_REPORT); + break; + } + + /* Begin Read-only ioctls. */ + if (_IOC_DIR(cmd) != _IOC_READ) { ret = -EINVAL; break; } @@ -327,7 +421,7 @@ static long hidraw_ioctl(struct file *file, unsigned int cmd, -EFAULT : len; break; } - } + } ret = -ENOTTY; } @@ -428,12 +522,12 @@ void hidraw_disconnect(struct hid_device *hid) hidraw->exist = 0; + device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); + mutex_lock(&minors_lock); hidraw_table[hidraw->minor] = NULL; mutex_unlock(&minors_lock); - device_destroy(hidraw_class, MKDEV(hidraw_major, hidraw->minor)); - if (hidraw->open) { hid_hw_close(hid); wake_up_interruptible(&hidraw->wait); diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index b336dd84036f..38c261a40c74 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -799,6 +799,40 @@ static int hid_alloc_buffers(struct usb_device *dev, struct hid_device *hid) return 0; } +static int usbhid_get_raw_report(struct hid_device *hid, + unsigned char report_number, __u8 *buf, size_t count, + unsigned char report_type) +{ + struct usbhid_device *usbhid = hid->driver_data; + struct usb_device *dev = hid_to_usb_dev(hid); + struct usb_interface *intf = usbhid->intf; + struct usb_host_interface *interface = intf->cur_altsetting; + int skipped_report_id = 0; + int ret; + + /* Byte 0 is the report number. Report data starts at byte 1.*/ + buf[0] = report_number; + if (report_number == 0x0) { + /* Offset the return buffer by 1, so that the report ID + will remain in byte 0. */ + buf++; + count--; + skipped_report_id = 1; + } + ret = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + HID_REQ_GET_REPORT, + USB_DIR_IN | USB_TYPE_CLASS | USB_RECIP_INTERFACE, + ((report_type + 1) << 8) | report_number, + interface->desc.bInterfaceNumber, buf, count, + USB_CTRL_SET_TIMEOUT); + + /* count also the report id */ + if (ret > 0 && skipped_report_id) + ret++; + + return ret; +} + static int usbhid_output_raw_report(struct hid_device *hid, __u8 *buf, size_t count, unsigned char report_type) { @@ -1139,6 +1173,7 @@ static int usbhid_probe(struct usb_interface *intf, const struct usb_device_id * usb_set_intfdata(intf, hid); hid->ll_driver = &usb_hid_driver; + hid->hid_get_raw_report = usbhid_get_raw_report; hid->hid_output_raw_report = usbhid_output_raw_report; hid->ff_init = hid_pidff_init; #ifdef CONFIG_USB_HIDDEV diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 773e484f1646..d6b124dde8eb 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -238,13 +238,13 @@ config SENSORS_K8TEMP will be called k8temp. config SENSORS_K10TEMP - tristate "AMD Phenom/Sempron/Turion/Opteron temperature sensor" + tristate "AMD Family 10h/11h/12h/14h temperature sensor" depends on X86 && PCI help If you say yes here you get support for the temperature sensor(s) inside your CPU. Supported are later revisions of - the AMD Family 10h and all revisions of the AMD Family 11h - microarchitectures. + the AMD Family 10h and all revisions of the AMD Family 11h, + 12h (Llano), and 14h (Brazos) microarchitectures. This driver can also be built as a module. If so, the module will be called k10temp. @@ -455,17 +455,29 @@ config SENSORS_JZ4740 called jz4740-hwmon. config SENSORS_JC42 - tristate "JEDEC JC42.4 compliant temperature sensors" + tristate "JEDEC JC42.4 compliant memory module temperature sensors" depends on I2C help - If you say yes here you get support for Jedec JC42.4 compliant - temperature sensors. Support will include, but not be limited to, - ADT7408, CAT34TS02,, CAT6095, MAX6604, MCP9805, MCP98242, MCP98243, - MCP9843, SE97, SE98, STTS424, TSE2002B3, and TS3000B3. + If you say yes here, you get support for JEDEC JC42.4 compliant + temperature sensors, which are used on many DDR3 memory modules for + mobile devices and servers. Support will include, but not be limited + to, ADT7408, CAT34TS02, CAT6095, MAX6604, MCP9805, MCP98242, MCP98243, + MCP9843, SE97, SE98, STTS424(E), TSE2002B3, and TS3000B3. This driver can also be built as a module. If so, the module will be called jc42. +config SENSORS_LINEAGE + tristate "Lineage Compact Power Line Power Entry Module" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the Lineage Compact Power Line + series of DC/DC and AC/DC converters such as CP1800, CP2000AC, + CP2000DC, CP2725, and others. + + This driver can also be built as a module. If so, the module + will be called lineage-pem. + config SENSORS_LM63 tristate "National Semiconductor LM63 and LM64" depends on I2C @@ -509,7 +521,7 @@ config SENSORS_LM75 - Dallas Semiconductor DS75 and DS1775 - Maxim MAX6625 and MAX6626 - Microchip MCP980x - - National Semiconductor LM75 + - National Semiconductor LM75, LM75A - NXP's LM75A - ST Microelectronics STDS75 - TelCom (now Microchip) TCN75 @@ -574,7 +586,7 @@ config SENSORS_LM85 help If you say yes here you get support for National Semiconductor LM85 sensor chips and clones: ADM1027, ADT7463, ADT7468, EMC6D100, - EMC6D101 and EMC6D102. + EMC6D101, EMC6D102, and EMC6D103. This driver can also be built as a module. If so, the module will be called lm85. @@ -684,6 +696,16 @@ config SENSORS_MAX1619 This driver can also be built as a module. If so, the module will be called max1619. +config SENSORS_MAX6639 + tristate "Maxim MAX6639 sensor chip" + depends on I2C && EXPERIMENTAL + help + If you say yes here you get support for the MAX6639 + sensor chips. + + This driver can also be built as a module. If so, the module + will be called max6639. + config SENSORS_MAX6650 tristate "Maxim MAX6650 sensor chip" depends on I2C && EXPERIMENTAL @@ -734,6 +756,61 @@ config SENSORS_PCF8591 These devices are hard to detect and rarely found on mainstream hardware. If unsure, say N. +config PMBUS + tristate "PMBus support" + depends on I2C && EXPERIMENTAL + default n + help + Say yes here if you want to enable PMBus support. + + This driver can also be built as a module. If so, the module will + be called pmbus_core. + +if PMBUS + +config SENSORS_PMBUS + tristate "Generic PMBus devices" + default n + help + If you say yes here you get hardware monitoring support for generic + PMBus devices, including but not limited to BMR450, BMR451, BMR453, + BMR454, and LTC2978. + + This driver can also be built as a module. If so, the module will + be called pmbus. + +config SENSORS_MAX16064 + tristate "Maxim MAX16064" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX16064. + + This driver can also be built as a module. If so, the module will + be called max16064. + +config SENSORS_MAX34440 + tristate "Maxim MAX34440/MAX34441" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX34440 and MAX34441. + + This driver can also be built as a module. If so, the module will + be called max34440. + +config SENSORS_MAX8688 + tristate "Maxim MAX8688" + default n + help + If you say yes here you get hardware monitoring support for Maxim + MAX8688. + + This driver can also be built as a module. If so, the module will + be called max8688. + +endif # PMBUS + config SENSORS_SHT15 tristate "Sensiron humidity and temperature sensors. SHT15 and compat." depends on GENERIC_GPIO @@ -1126,40 +1203,6 @@ config SENSORS_ULTRA45 This driver provides support for the Ultra45 workstation environmental sensors. -config SENSORS_LIS3_SPI - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" - depends on !ACPI && SPI_MASTER && INPUT - select INPUT_POLLDEV - default n - help - This driver provides support for the LIS3LV02Dx accelerometer connected - via SPI. The accelerometer data is readable via - /sys/devices/platform/lis3lv02d. - - This driver also provides an absolute input class device, allowing - the laptop to act as a pinball machine-esque joystick. - - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for the SPI transport - is called lis3lv02d_spi. - -config SENSORS_LIS3_I2C - tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" - depends on I2C && INPUT - select INPUT_POLLDEV - default n - help - This driver provides support for the LIS3LV02Dx accelerometer connected - via I2C. The accelerometer data is readable via - /sys/devices/platform/lis3lv02d. - - This driver also provides an absolute input class device, allowing - the device to act as a pinball machine-esque joystick. - - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for the I2C transport - is called lis3lv02d_i2c. - config SENSORS_APPLESMC tristate "Apple SMC (Motion sensor, light sensor, keyboard backlight)" depends on INPUT && X86 @@ -1207,36 +1250,6 @@ config SENSORS_ATK0110 This driver can also be built as a module. If so, the module will be called asus_atk0110. -config SENSORS_LIS3LV02D - tristate "STMicroeletronics LIS3* three-axis digital accelerometer" - depends on INPUT - select INPUT_POLLDEV - select NEW_LEDS - select LEDS_CLASS - default n - help - This driver provides support for the LIS3* accelerometers, such as the - LIS3LV02DL or the LIS331DL. In particular, it can be found in a number - of HP laptops, which have the "Mobile Data Protection System 3D" or - "3D DriveGuard" feature. On such systems the driver should load - automatically (via ACPI alias). The accelerometer might also be found - in other systems, connected via SPI or I2C. The accelerometer data is - readable via /sys/devices/platform/lis3lv02d. - - This driver also provides an absolute input class device, allowing - a laptop to act as a pinball machine-esque joystick. It provides also - a misc device which can be used to detect free-fall. On HP laptops, - if the led infrastructure is activated, support for a led indicating - disk protection will be provided as hp::hddprotect. For more - information on the feature, refer to Documentation/hwmon/lis3lv02d. - - This driver can also be built as modules. If so, the core module - will be called lis3lv02d and a specific module for HP laptops will be - called hp_accel. - - Say Y here if you have an applicable laptop and want to experience - the awesome power of lis3lv02d. - endif # ACPI endif # HWMON diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index dde02d99c238..cc029ee302f3 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -62,9 +62,7 @@ obj-$(CONFIG_SENSORS_JC42) += jc42.o obj-$(CONFIG_SENSORS_JZ4740) += jz4740-hwmon.o obj-$(CONFIG_SENSORS_K8TEMP) += k8temp.o obj-$(CONFIG_SENSORS_K10TEMP) += k10temp.o -obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o hp_accel.o -obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d.o lis3lv02d_spi.o -obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d.o lis3lv02d_i2c.o +obj-$(CONFIG_SENSORS_LINEAGE) += lineage-pem.o obj-$(CONFIG_SENSORS_LM63) += lm63.o obj-$(CONFIG_SENSORS_LM70) += lm70.o obj-$(CONFIG_SENSORS_LM73) += lm73.o @@ -84,6 +82,7 @@ obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_LTC4261) += ltc4261.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o +obj-$(CONFIG_SENSORS_MAX6639) += max6639.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o obj-$(CONFIG_SENSORS_MC13783_ADC)+= mc13783-adc.o obj-$(CONFIG_SENSORS_PC87360) += pc87360.o @@ -112,6 +111,13 @@ obj-$(CONFIG_SENSORS_W83L786NG) += w83l786ng.o obj-$(CONFIG_SENSORS_WM831X) += wm831x-hwmon.o obj-$(CONFIG_SENSORS_WM8350) += wm8350-hwmon.o +# PMBus drivers +obj-$(CONFIG_PMBUS) += pmbus_core.o +obj-$(CONFIG_SENSORS_PMBUS) += pmbus.o +obj-$(CONFIG_SENSORS_MAX16064) += max16064.o +obj-$(CONFIG_SENSORS_MAX34440) += max34440.o +obj-$(CONFIG_SENSORS_MAX8688) += max8688.o + ifeq ($(CONFIG_HWMON_DEBUG_CHIP),y) EXTRA_CFLAGS += -DDEBUG endif diff --git a/drivers/hwmon/ad7414.c b/drivers/hwmon/ad7414.c index 86d822aa9bbf..d46c0c758ddf 100644 --- a/drivers/hwmon/ad7414.c +++ b/drivers/hwmon/ad7414.c @@ -242,6 +242,7 @@ static const struct i2c_device_id ad7414_id[] = { { "ad7414", 0 }, {} }; +MODULE_DEVICE_TABLE(i2c, ad7414_id); static struct i2c_driver ad7414_driver = { .driver = { diff --git a/drivers/hwmon/adt7411.c b/drivers/hwmon/adt7411.c index f13c843a2964..5cc3e3784b42 100644 --- a/drivers/hwmon/adt7411.c +++ b/drivers/hwmon/adt7411.c @@ -334,6 +334,7 @@ static const struct i2c_device_id adt7411_id[] = { { "adt7411", 0 }, { } }; +MODULE_DEVICE_TABLE(i2c, adt7411_id); static struct i2c_driver adt7411_driver = { .driver = { diff --git a/drivers/hwmon/jc42.c b/drivers/hwmon/jc42.c index 340fc78c8dde..934991237061 100644 --- a/drivers/hwmon/jc42.c +++ b/drivers/hwmon/jc42.c @@ -53,6 +53,8 @@ static const unsigned short normal_i2c[] = { /* Configuration register defines */ #define JC42_CFG_CRIT_ONLY (1 << 2) +#define JC42_CFG_TCRIT_LOCK (1 << 6) +#define JC42_CFG_EVENT_LOCK (1 << 7) #define JC42_CFG_SHUTDOWN (1 << 8) #define JC42_CFG_HYST_SHIFT 9 #define JC42_CFG_HYST_MASK 0x03 @@ -332,7 +334,7 @@ static ssize_t set_temp_crit_hyst(struct device *dev, { struct i2c_client *client = to_i2c_client(dev); struct jc42_data *data = i2c_get_clientdata(client); - long val; + unsigned long val; int diff, hyst; int err; int ret = count; @@ -380,14 +382,14 @@ static ssize_t show_alarm(struct device *dev, static DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL); -static DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(temp1_crit, S_IRUGO, show_temp_crit, set_temp_crit); -static DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(temp1_min, S_IRUGO, show_temp_min, set_temp_min); -static DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(temp1_max, S_IRUGO, show_temp_max, set_temp_max); -static DEVICE_ATTR(temp1_crit_hyst, S_IWUSR | S_IRUGO, +static DEVICE_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, set_temp_crit_hyst); static DEVICE_ATTR(temp1_max_hyst, S_IRUGO, show_temp_max_hyst, NULL); @@ -412,8 +414,31 @@ static struct attribute *jc42_attributes[] = { NULL }; +static mode_t jc42_attribute_mode(struct kobject *kobj, + struct attribute *attr, int index) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct i2c_client *client = to_i2c_client(dev); + struct jc42_data *data = i2c_get_clientdata(client); + unsigned int config = data->config; + bool readonly; + + if (attr == &dev_attr_temp1_crit.attr) + readonly = config & JC42_CFG_TCRIT_LOCK; + else if (attr == &dev_attr_temp1_min.attr || + attr == &dev_attr_temp1_max.attr) + readonly = config & JC42_CFG_EVENT_LOCK; + else if (attr == &dev_attr_temp1_crit_hyst.attr) + readonly = config & (JC42_CFG_EVENT_LOCK | JC42_CFG_TCRIT_LOCK); + else + readonly = true; + + return S_IRUGO | (readonly ? 0 : S_IWUSR); +} + static const struct attribute_group jc42_group = { .attrs = jc42_attributes, + .is_visible = jc42_attribute_mode, }; /* Return 0 if detection is successful, -ENODEV otherwise */ diff --git a/drivers/hwmon/k10temp.c b/drivers/hwmon/k10temp.c index da5a2404cd3e..82bf65aa2968 100644 --- a/drivers/hwmon/k10temp.c +++ b/drivers/hwmon/k10temp.c @@ -1,5 +1,5 @@ /* - * k10temp.c - AMD Family 10h/11h processor hardware monitoring + * k10temp.c - AMD Family 10h/11h/12h/14h processor hardware monitoring * * Copyright (c) 2009 Clemens Ladisch <clemens@ladisch.de> * @@ -25,7 +25,7 @@ #include <linux/pci.h> #include <asm/processor.h> -MODULE_DESCRIPTION("AMD Family 10h/11h CPU core temperature monitor"); +MODULE_DESCRIPTION("AMD Family 10h/11h/12h/14h CPU core temperature monitor"); MODULE_AUTHOR("Clemens Ladisch <clemens@ladisch.de>"); MODULE_LICENSE("GPL"); @@ -208,6 +208,7 @@ static void __devexit k10temp_remove(struct pci_dev *pdev) static const struct pci_device_id k10temp_id_table[] = { { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_10H_NB_MISC) }, { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_11H_NB_MISC) }, + { PCI_VDEVICE(AMD, PCI_DEVICE_ID_AMD_CNB17H_F3) }, {} }; MODULE_DEVICE_TABLE(pci, k10temp_id_table); diff --git a/drivers/hwmon/lineage-pem.c b/drivers/hwmon/lineage-pem.c new file mode 100644 index 000000000000..d39ee24e52f6 --- /dev/null +++ b/drivers/hwmon/lineage-pem.c @@ -0,0 +1,589 @@ +/* + * Driver for Lineage Compact Power Line series of power entry modules. + * + * Copyright (C) 2010, 2011 Ericsson AB. + * + * Documentation: + * http://www.lineagepower.com/oem/pdf/CPLI2C.pdf + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> + +/* + * This driver supports various Lineage Compact Power Line DC/DC and AC/DC + * converters such as CP1800, CP2000AC, CP2000DC, CP2100DC, and others. + * + * The devices are nominally PMBus compliant. However, most standard PMBus + * commands are not supported. Specifically, all hardware monitoring and + * status reporting commands are non-standard. For this reason, a standard + * PMBus driver can not be used. + * + * All Lineage CPL devices have a built-in I2C bus master selector (PCA9541). + * To ensure device access, this driver should only be used as client driver + * to the pca9541 I2C master selector driver. + */ + +/* Command codes */ +#define PEM_OPERATION 0x01 +#define PEM_CLEAR_INFO_FLAGS 0x03 +#define PEM_VOUT_COMMAND 0x21 +#define PEM_VOUT_OV_FAULT_LIMIT 0x40 +#define PEM_READ_DATA_STRING 0xd0 +#define PEM_READ_INPUT_STRING 0xdc +#define PEM_READ_FIRMWARE_REV 0xdd +#define PEM_READ_RUN_TIMER 0xde +#define PEM_FAN_HI_SPEED 0xdf +#define PEM_FAN_NORMAL_SPEED 0xe0 +#define PEM_READ_FAN_SPEED 0xe1 + +/* offsets in data string */ +#define PEM_DATA_STATUS_2 0 +#define PEM_DATA_STATUS_1 1 +#define PEM_DATA_ALARM_2 2 +#define PEM_DATA_ALARM_1 3 +#define PEM_DATA_VOUT_LSB 4 +#define PEM_DATA_VOUT_MSB 5 +#define PEM_DATA_CURRENT 6 +#define PEM_DATA_TEMP 7 + +/* Virtual entries, to report constants */ +#define PEM_DATA_TEMP_MAX 10 +#define PEM_DATA_TEMP_CRIT 11 + +/* offsets in input string */ +#define PEM_INPUT_VOLTAGE 0 +#define PEM_INPUT_POWER_LSB 1 +#define PEM_INPUT_POWER_MSB 2 + +/* offsets in fan data */ +#define PEM_FAN_ADJUSTMENT 0 +#define PEM_FAN_FAN1 1 +#define PEM_FAN_FAN2 2 +#define PEM_FAN_FAN3 3 + +/* Status register bits */ +#define STS1_OUTPUT_ON (1 << 0) +#define STS1_LEDS_FLASHING (1 << 1) +#define STS1_EXT_FAULT (1 << 2) +#define STS1_SERVICE_LED_ON (1 << 3) +#define STS1_SHUTDOWN_OCCURRED (1 << 4) +#define STS1_INT_FAULT (1 << 5) +#define STS1_ISOLATION_TEST_OK (1 << 6) + +#define STS2_ENABLE_PIN_HI (1 << 0) +#define STS2_DATA_OUT_RANGE (1 << 1) +#define STS2_RESTARTED_OK (1 << 1) +#define STS2_ISOLATION_TEST_FAIL (1 << 3) +#define STS2_HIGH_POWER_CAP (1 << 4) +#define STS2_INVALID_INSTR (1 << 5) +#define STS2_WILL_RESTART (1 << 6) +#define STS2_PEC_ERR (1 << 7) + +/* Alarm register bits */ +#define ALRM1_VIN_OUT_LIMIT (1 << 0) +#define ALRM1_VOUT_OUT_LIMIT (1 << 1) +#define ALRM1_OV_VOLT_SHUTDOWN (1 << 2) +#define ALRM1_VIN_OVERCURRENT (1 << 3) +#define ALRM1_TEMP_WARNING (1 << 4) +#define ALRM1_TEMP_SHUTDOWN (1 << 5) +#define ALRM1_PRIMARY_FAULT (1 << 6) +#define ALRM1_POWER_LIMIT (1 << 7) + +#define ALRM2_5V_OUT_LIMIT (1 << 1) +#define ALRM2_TEMP_FAULT (1 << 2) +#define ALRM2_OV_LOW (1 << 3) +#define ALRM2_DCDC_TEMP_HIGH (1 << 4) +#define ALRM2_PRI_TEMP_HIGH (1 << 5) +#define ALRM2_NO_PRIMARY (1 << 6) +#define ALRM2_FAN_FAULT (1 << 7) + +#define FIRMWARE_REV_LEN 4 +#define DATA_STRING_LEN 9 +#define INPUT_STRING_LEN 5 /* 4 for most devices */ +#define FAN_SPEED_LEN 5 + +struct pem_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + bool fans_supported; + int input_length; + unsigned long last_updated; /* in jiffies */ + + u8 firmware_rev[FIRMWARE_REV_LEN]; + u8 data_string[DATA_STRING_LEN]; + u8 input_string[INPUT_STRING_LEN]; + u8 fan_speed[FAN_SPEED_LEN]; +}; + +static int pem_read_block(struct i2c_client *client, u8 command, u8 *data, + int data_len) +{ + u8 block_buffer[I2C_SMBUS_BLOCK_MAX]; + int result; + + result = i2c_smbus_read_block_data(client, command, block_buffer); + if (unlikely(result < 0)) + goto abort; + if (unlikely(result == 0xff || result != data_len)) { + result = -EIO; + goto abort; + } + memcpy(data, block_buffer, data_len); + result = 0; +abort: + return result; +} + +static struct pem_data *pem_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pem_data *data = i2c_get_clientdata(client); + struct pem_data *ret = data; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int result; + + /* Read data string */ + result = pem_read_block(client, PEM_READ_DATA_STRING, + data->data_string, + sizeof(data->data_string)); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + + /* Read input string */ + if (data->input_length) { + result = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + data->input_length); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + } + + /* Read fan speeds */ + if (data->fans_supported) { + result = pem_read_block(client, PEM_READ_FAN_SPEED, + data->fan_speed, + sizeof(data->fan_speed)); + if (unlikely(result < 0)) { + ret = ERR_PTR(result); + goto abort; + } + } + + i2c_smbus_write_byte(client, PEM_CLEAR_INFO_FLAGS); + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + return ret; +} + +static long pem_get_data(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_DATA_VOUT_LSB: + val = (data[index] + (data[index+1] << 8)) * 5 / 2; + break; + case PEM_DATA_CURRENT: + val = data[index] * 200; + break; + case PEM_DATA_TEMP: + val = data[index] * 1000; + break; + case PEM_DATA_TEMP_MAX: + val = 97 * 1000; /* 97 degrees C per datasheet */ + break; + case PEM_DATA_TEMP_CRIT: + val = 107 * 1000; /* 107 degrees C per datasheet */ + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +static long pem_get_input(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_INPUT_VOLTAGE: + if (len == INPUT_STRING_LEN) + val = (data[index] + (data[index+1] << 8) - 75) * 1000; + else + val = (data[index] - 75) * 1000; + break; + case PEM_INPUT_POWER_LSB: + if (len == INPUT_STRING_LEN) + index++; + val = (data[index] + (data[index+1] << 8)) * 1000000L; + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +static long pem_get_fan(u8 *data, int len, int index) +{ + long val; + + switch (index) { + case PEM_FAN_FAN1: + case PEM_FAN_FAN2: + case PEM_FAN_FAN3: + val = data[index] * 100; + break; + default: + WARN_ON_ONCE(1); + val = 0; + } + return val; +} + +/* + * Show boolean, either a fault or an alarm. + * .nr points to the register, .index is the bit mask to check + */ +static ssize_t pem_show_bool(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); + struct pem_data *data = pem_update_device(dev); + u8 status; + + if (IS_ERR(data)) + return PTR_ERR(data); + + status = data->data_string[attr->nr] & attr->index; + return snprintf(buf, PAGE_SIZE, "%d\n", !!status); +} + +static ssize_t pem_show_data(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_data(data->data_string, sizeof(data->data_string), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +static ssize_t pem_show_input(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_input(data->input_string, sizeof(data->input_string), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +static ssize_t pem_show_fan(struct device *dev, struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pem_data *data = pem_update_device(dev); + long value; + + if (IS_ERR(data)) + return PTR_ERR(data); + + value = pem_get_fan(data->fan_speed, sizeof(data->fan_speed), + attr->index); + + return snprintf(buf, PAGE_SIZE, "%ld\n", value); +} + +/* Voltages */ +static SENSOR_DEVICE_ATTR(in1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_VOUT_LSB); +static SENSOR_DEVICE_ATTR_2(in1_min_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_OV_LOW); +static SENSOR_DEVICE_ATTR_2(in1_max_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_VOUT_OUT_LIMIT); +static SENSOR_DEVICE_ATTR_2(in1_crit_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_OV_VOLT_SHUTDOWN); +static SENSOR_DEVICE_ATTR(in2_input, S_IRUGO, pem_show_input, NULL, + PEM_INPUT_VOLTAGE); +static SENSOR_DEVICE_ATTR_2(in2_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, + ALRM1_VIN_OUT_LIMIT | ALRM1_PRIMARY_FAULT); + +/* Currents */ +static SENSOR_DEVICE_ATTR(curr1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_CURRENT); +static SENSOR_DEVICE_ATTR_2(curr1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_VIN_OVERCURRENT); + +/* Power */ +static SENSOR_DEVICE_ATTR(power1_input, S_IRUGO, pem_show_input, NULL, + PEM_INPUT_POWER_LSB); +static SENSOR_DEVICE_ATTR_2(power1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_POWER_LIMIT); + +/* Fans */ +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN1); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN2); +static SENSOR_DEVICE_ATTR(fan3_input, S_IRUGO, pem_show_fan, NULL, + PEM_FAN_FAN3); +static SENSOR_DEVICE_ATTR_2(fan1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_FAN_FAULT); + +/* Temperatures */ +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP); +static SENSOR_DEVICE_ATTR(temp1_max, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP_MAX); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IRUGO, pem_show_data, NULL, + PEM_DATA_TEMP_CRIT); +static SENSOR_DEVICE_ATTR_2(temp1_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_TEMP_WARNING); +static SENSOR_DEVICE_ATTR_2(temp1_crit_alarm, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_1, ALRM1_TEMP_SHUTDOWN); +static SENSOR_DEVICE_ATTR_2(temp1_fault, S_IRUGO, pem_show_bool, NULL, + PEM_DATA_ALARM_2, ALRM2_TEMP_FAULT); + +static struct attribute *pem_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in1_max_alarm.dev_attr.attr, + &sensor_dev_attr_in1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_in2_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_alarm.dev_attr.attr, + + &sensor_dev_attr_power1_alarm.dev_attr.attr, + + &sensor_dev_attr_fan1_alarm.dev_attr.attr, + + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp1_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group pem_group = { + .attrs = pem_attributes, +}; + +static struct attribute *pem_input_attributes[] = { + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_power1_input.dev_attr.attr, +}; + +static const struct attribute_group pem_input_group = { + .attrs = pem_input_attributes, +}; + +static struct attribute *pem_fan_attributes[] = { + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan3_input.dev_attr.attr, +}; + +static const struct attribute_group pem_fan_group = { + .attrs = pem_fan_attributes, +}; + +static int pem_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct i2c_adapter *adapter = client->adapter; + struct pem_data *data; + int ret; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BLOCK_DATA + | I2C_FUNC_SMBUS_WRITE_BYTE)) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* + * We use the next two commands to determine if the device is really + * there. + */ + ret = pem_read_block(client, PEM_READ_FIRMWARE_REV, + data->firmware_rev, sizeof(data->firmware_rev)); + if (ret < 0) + goto out_kfree; + + ret = i2c_smbus_write_byte(client, PEM_CLEAR_INFO_FLAGS); + if (ret < 0) + goto out_kfree; + + dev_info(&client->dev, "Firmware revision %d.%d.%d\n", + data->firmware_rev[0], data->firmware_rev[1], + data->firmware_rev[2]); + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, &pem_group); + if (ret) + goto out_kfree; + + /* + * Check if input readings are supported. + * This is the case if we can read input data, + * and if the returned data is not all zeros. + * Note that input alarms are always supported. + */ + ret = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + sizeof(data->input_string) - 1); + if (!ret && (data->input_string[0] || data->input_string[1] || + data->input_string[2])) + data->input_length = sizeof(data->input_string) - 1; + else if (ret < 0) { + /* Input string is one byte longer for some devices */ + ret = pem_read_block(client, PEM_READ_INPUT_STRING, + data->input_string, + sizeof(data->input_string)); + if (!ret && (data->input_string[0] || data->input_string[1] || + data->input_string[2] || data->input_string[3])) + data->input_length = sizeof(data->input_string); + } + ret = 0; + if (data->input_length) { + ret = sysfs_create_group(&client->dev.kobj, &pem_input_group); + if (ret) + goto out_remove_groups; + } + + /* + * Check if fan speed readings are supported. + * This is the case if we can read fan speed data, + * and if the returned data is not all zeros. + * Note that the fan alarm is always supported. + */ + ret = pem_read_block(client, PEM_READ_FAN_SPEED, + data->fan_speed, + sizeof(data->fan_speed)); + if (!ret && (data->fan_speed[0] || data->fan_speed[1] || + data->fan_speed[2] || data->fan_speed[3])) { + data->fans_supported = true; + ret = sysfs_create_group(&client->dev.kobj, &pem_fan_group); + if (ret) + goto out_remove_groups; + } + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_remove_groups; + } + + return 0; + +out_remove_groups: + sysfs_remove_group(&client->dev.kobj, &pem_input_group); + sysfs_remove_group(&client->dev.kobj, &pem_fan_group); + sysfs_remove_group(&client->dev.kobj, &pem_group); +out_kfree: + kfree(data); + return ret; +} + +static int pem_remove(struct i2c_client *client) +{ + struct pem_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + + sysfs_remove_group(&client->dev.kobj, &pem_input_group); + sysfs_remove_group(&client->dev.kobj, &pem_fan_group); + sysfs_remove_group(&client->dev.kobj, &pem_group); + + kfree(data); + return 0; +} + +static const struct i2c_device_id pem_id[] = { + {"lineage_pem", 0}, + {} +}; +MODULE_DEVICE_TABLE(i2c, pem_id); + +static struct i2c_driver pem_driver = { + .driver = { + .name = "lineage_pem", + }, + .probe = pem_probe, + .remove = pem_remove, + .id_table = pem_id, +}; + +static int __init pem_init(void) +{ + return i2c_add_driver(&pem_driver); +} + +static void __exit pem_exit(void) +{ + i2c_del_driver(&pem_driver); +} + +MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>"); +MODULE_DESCRIPTION("Lineage CPL PEM hardware monitoring driver"); +MODULE_LICENSE("GPL"); + +module_init(pem_init); +module_exit(pem_exit); diff --git a/drivers/hwmon/lm75.c b/drivers/hwmon/lm75.c index f36eb80d227f..ef902d5d06ab 100644 --- a/drivers/hwmon/lm75.c +++ b/drivers/hwmon/lm75.c @@ -232,13 +232,16 @@ static const struct i2c_device_id lm75_ids[] = { }; MODULE_DEVICE_TABLE(i2c, lm75_ids); +#define LM75A_ID 0xA1 + /* Return 0 if detection is successful, -ENODEV otherwise */ static int lm75_detect(struct i2c_client *new_client, struct i2c_board_info *info) { struct i2c_adapter *adapter = new_client->adapter; int i; - int cur, conf, hyst, os; + int conf, hyst, os; + bool is_lm75a = 0; if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_WORD_DATA)) @@ -250,37 +253,58 @@ static int lm75_detect(struct i2c_client *new_client, addresses 0x04-0x07 returning the last read value. The cycling+unused addresses combination is not tested, since it would significantly slow the detection down and would - hardly add any value. */ + hardly add any value. - /* Unused addresses */ - cur = i2c_smbus_read_word_data(new_client, 0); - conf = i2c_smbus_read_byte_data(new_client, 1); - hyst = i2c_smbus_read_word_data(new_client, 2); - if (i2c_smbus_read_word_data(new_client, 4) != hyst - || i2c_smbus_read_word_data(new_client, 5) != hyst - || i2c_smbus_read_word_data(new_client, 6) != hyst - || i2c_smbus_read_word_data(new_client, 7) != hyst) - return -ENODEV; - os = i2c_smbus_read_word_data(new_client, 3); - if (i2c_smbus_read_word_data(new_client, 4) != os - || i2c_smbus_read_word_data(new_client, 5) != os - || i2c_smbus_read_word_data(new_client, 6) != os - || i2c_smbus_read_word_data(new_client, 7) != os) - return -ENODEV; + The National Semiconductor LM75A is different than earlier + LM75s. It has an ID byte of 0xaX (where X is the chip + revision, with 1 being the only revision in existence) in + register 7, and unused registers return 0xff rather than the + last read value. */ /* Unused bits */ + conf = i2c_smbus_read_byte_data(new_client, 1); if (conf & 0xe0) return -ENODEV; + /* First check for LM75A */ + if (i2c_smbus_read_byte_data(new_client, 7) == LM75A_ID) { + /* LM75A returns 0xff on unused registers so + just to be sure we check for that too. */ + if (i2c_smbus_read_byte_data(new_client, 4) != 0xff + || i2c_smbus_read_byte_data(new_client, 5) != 0xff + || i2c_smbus_read_byte_data(new_client, 6) != 0xff) + return -ENODEV; + is_lm75a = 1; + hyst = i2c_smbus_read_byte_data(new_client, 2); + os = i2c_smbus_read_byte_data(new_client, 3); + } else { /* Traditional style LM75 detection */ + /* Unused addresses */ + hyst = i2c_smbus_read_byte_data(new_client, 2); + if (i2c_smbus_read_byte_data(new_client, 4) != hyst + || i2c_smbus_read_byte_data(new_client, 5) != hyst + || i2c_smbus_read_byte_data(new_client, 6) != hyst + || i2c_smbus_read_byte_data(new_client, 7) != hyst) + return -ENODEV; + os = i2c_smbus_read_byte_data(new_client, 3); + if (i2c_smbus_read_byte_data(new_client, 4) != os + || i2c_smbus_read_byte_data(new_client, 5) != os + || i2c_smbus_read_byte_data(new_client, 6) != os + || i2c_smbus_read_byte_data(new_client, 7) != os) + return -ENODEV; + } + /* Addresses cycling */ - for (i = 8; i < 0xff; i += 8) { + for (i = 8; i <= 248; i += 40) { if (i2c_smbus_read_byte_data(new_client, i + 1) != conf - || i2c_smbus_read_word_data(new_client, i + 2) != hyst - || i2c_smbus_read_word_data(new_client, i + 3) != os) + || i2c_smbus_read_byte_data(new_client, i + 2) != hyst + || i2c_smbus_read_byte_data(new_client, i + 3) != os) + return -ENODEV; + if (is_lm75a && i2c_smbus_read_byte_data(new_client, i + 7) + != LM75A_ID) return -ENODEV; } - strlcpy(info->type, "lm75", I2C_NAME_SIZE); + strlcpy(info->type, is_lm75a ? "lm75a" : "lm75", I2C_NAME_SIZE); return 0; } diff --git a/drivers/hwmon/lm85.c b/drivers/hwmon/lm85.c index 1e229847f37a..cf47e6e476ed 100644 --- a/drivers/hwmon/lm85.c +++ b/drivers/hwmon/lm85.c @@ -41,7 +41,7 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, I2C_CLIENT_END }; enum chips { any_chip, lm85b, lm85c, adm1027, adt7463, adt7468, - emc6d100, emc6d102 + emc6d100, emc6d102, emc6d103, emc6d103s }; /* The LM85 registers */ @@ -90,6 +90,9 @@ enum chips { #define LM85_VERSTEP_EMC6D100_A0 0x60 #define LM85_VERSTEP_EMC6D100_A1 0x61 #define LM85_VERSTEP_EMC6D102 0x65 +#define LM85_VERSTEP_EMC6D103_A0 0x68 +#define LM85_VERSTEP_EMC6D103_A1 0x69 +#define LM85_VERSTEP_EMC6D103S 0x6A /* Also known as EMC6D103:A2 */ #define LM85_REG_CONFIG 0x40 @@ -280,10 +283,6 @@ struct lm85_zone { u8 hyst; /* Low limit hysteresis. (0-15) */ u8 range; /* Temp range, encoded */ s8 critical; /* "All fans ON" temp limit */ - u8 off_desired; /* Actual "off" temperature specified. Preserved - * to prevent "drift" as other autofan control - * values change. - */ u8 max_desired; /* Actual "max" temperature specified. Preserved * to prevent "drift" as other autofan control * values change. @@ -303,6 +302,8 @@ struct lm85_data { const int *freq_map; enum chips type; + bool has_vid5; /* true if VID5 is configured for ADT7463 or ADT7468 */ + struct mutex update_lock; int valid; /* !=0 if following fields are valid */ unsigned long last_reading; /* In jiffies */ @@ -348,6 +349,8 @@ static const struct i2c_device_id lm85_id[] = { { "emc6d100", emc6d100 }, { "emc6d101", emc6d100 }, { "emc6d102", emc6d102 }, + { "emc6d103", emc6d103 }, + { "emc6d103s", emc6d103s }, { } }; MODULE_DEVICE_TABLE(i2c, lm85_id); @@ -416,8 +419,7 @@ static ssize_t show_vid_reg(struct device *dev, struct device_attribute *attr, struct lm85_data *data = lm85_update_device(dev); int vid; - if ((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80)) { + if (data->has_vid5) { /* 6-pin VID (VRM 10) */ vid = vid_from_reg(data->vid & 0x3f, data->vrm); } else { @@ -887,7 +889,6 @@ static ssize_t set_temp_auto_temp_off(struct device *dev, mutex_lock(&data->update_lock); min = TEMP_FROM_REG(data->zone[nr].limit); - data->zone[nr].off_desired = TEMP_TO_REG(val); data->zone[nr].hyst = HYST_TO_REG(min - val); if (nr == 0 || nr == 1) { lm85_write_value(client, LM85_REG_AFAN_HYST1, @@ -930,18 +931,6 @@ static ssize_t set_temp_auto_temp_min(struct device *dev, ((data->zone[nr].range & 0x0f) << 4) | (data->pwm_freq[nr] & 0x07)); -/* Update temp_auto_hyst and temp_auto_off */ - data->zone[nr].hyst = HYST_TO_REG(TEMP_FROM_REG( - data->zone[nr].limit) - TEMP_FROM_REG( - data->zone[nr].off_desired)); - if (nr == 0 || nr == 1) { - lm85_write_value(client, LM85_REG_AFAN_HYST1, - (data->zone[0].hyst << 4) - | data->zone[1].hyst); - } else { - lm85_write_value(client, LM85_REG_AFAN_HYST2, - (data->zone[2].hyst << 4)); - } mutex_unlock(&data->update_lock); return count; } @@ -1080,13 +1069,7 @@ static struct attribute *lm85_attributes[] = { &sensor_dev_attr_pwm1_auto_pwm_min.dev_attr.attr, &sensor_dev_attr_pwm2_auto_pwm_min.dev_attr.attr, &sensor_dev_attr_pwm3_auto_pwm_min.dev_attr.attr, - &sensor_dev_attr_pwm1_auto_pwm_minctl.dev_attr.attr, - &sensor_dev_attr_pwm2_auto_pwm_minctl.dev_attr.attr, - &sensor_dev_attr_pwm3_auto_pwm_minctl.dev_attr.attr, - &sensor_dev_attr_temp1_auto_temp_off.dev_attr.attr, - &sensor_dev_attr_temp2_auto_temp_off.dev_attr.attr, - &sensor_dev_attr_temp3_auto_temp_off.dev_attr.attr, &sensor_dev_attr_temp1_auto_temp_min.dev_attr.attr, &sensor_dev_attr_temp2_auto_temp_min.dev_attr.attr, &sensor_dev_attr_temp3_auto_temp_min.dev_attr.attr, @@ -1107,6 +1090,26 @@ static const struct attribute_group lm85_group = { .attrs = lm85_attributes, }; +static struct attribute *lm85_attributes_minctl[] = { + &sensor_dev_attr_pwm1_auto_pwm_minctl.dev_attr.attr, + &sensor_dev_attr_pwm2_auto_pwm_minctl.dev_attr.attr, + &sensor_dev_attr_pwm3_auto_pwm_minctl.dev_attr.attr, +}; + +static const struct attribute_group lm85_group_minctl = { + .attrs = lm85_attributes_minctl, +}; + +static struct attribute *lm85_attributes_temp_off[] = { + &sensor_dev_attr_temp1_auto_temp_off.dev_attr.attr, + &sensor_dev_attr_temp2_auto_temp_off.dev_attr.attr, + &sensor_dev_attr_temp3_auto_temp_off.dev_attr.attr, +}; + +static const struct attribute_group lm85_group_temp_off = { + .attrs = lm85_attributes_temp_off, +}; + static struct attribute *lm85_attributes_in4[] = { &sensor_dev_attr_in4_input.dev_attr.attr, &sensor_dev_attr_in4_min.dev_attr.attr, @@ -1250,6 +1253,13 @@ static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info) case LM85_VERSTEP_EMC6D102: type_name = "emc6d102"; break; + case LM85_VERSTEP_EMC6D103_A0: + case LM85_VERSTEP_EMC6D103_A1: + type_name = "emc6d103"; + break; + case LM85_VERSTEP_EMC6D103S: + type_name = "emc6d103s"; + break; } } else { dev_dbg(&adapter->dev, @@ -1262,6 +1272,19 @@ static int lm85_detect(struct i2c_client *client, struct i2c_board_info *info) return 0; } +static void lm85_remove_files(struct i2c_client *client, struct lm85_data *data) +{ + sysfs_remove_group(&client->dev.kobj, &lm85_group); + if (data->type != emc6d103s) { + sysfs_remove_group(&client->dev.kobj, &lm85_group_minctl); + sysfs_remove_group(&client->dev.kobj, &lm85_group_temp_off); + } + if (!data->has_vid5) + sysfs_remove_group(&client->dev.kobj, &lm85_group_in4); + if (data->type == emc6d100) + sysfs_remove_group(&client->dev.kobj, &lm85_group_in567); +} + static int lm85_probe(struct i2c_client *client, const struct i2c_device_id *id) { @@ -1283,6 +1306,8 @@ static int lm85_probe(struct i2c_client *client, case adt7468: case emc6d100: case emc6d102: + case emc6d103: + case emc6d103s: data->freq_map = adm1027_freq_map; break; default: @@ -1300,11 +1325,26 @@ static int lm85_probe(struct i2c_client *client, if (err) goto err_kfree; + /* minctl and temp_off exist on all chips except emc6d103s */ + if (data->type != emc6d103s) { + err = sysfs_create_group(&client->dev.kobj, &lm85_group_minctl); + if (err) + goto err_kfree; + err = sysfs_create_group(&client->dev.kobj, + &lm85_group_temp_off); + if (err) + goto err_kfree; + } + /* The ADT7463/68 have an optional VRM 10 mode where pin 21 is used as a sixth digital VID input rather than an analog input. */ - data->vid = lm85_read_value(client, LM85_REG_VID); - if (!((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80))) + if (data->type == adt7463 || data->type == adt7468) { + u8 vid = lm85_read_value(client, LM85_REG_VID); + if (vid & 0x80) + data->has_vid5 = true; + } + + if (!data->has_vid5) if ((err = sysfs_create_group(&client->dev.kobj, &lm85_group_in4))) goto err_remove_files; @@ -1325,10 +1365,7 @@ static int lm85_probe(struct i2c_client *client, /* Error out and cleanup code */ err_remove_files: - sysfs_remove_group(&client->dev.kobj, &lm85_group); - sysfs_remove_group(&client->dev.kobj, &lm85_group_in4); - if (data->type == emc6d100) - sysfs_remove_group(&client->dev.kobj, &lm85_group_in567); + lm85_remove_files(client, data); err_kfree: kfree(data); return err; @@ -1338,10 +1375,7 @@ static int lm85_remove(struct i2c_client *client) { struct lm85_data *data = i2c_get_clientdata(client); hwmon_device_unregister(data->hwmon_dev); - sysfs_remove_group(&client->dev.kobj, &lm85_group); - sysfs_remove_group(&client->dev.kobj, &lm85_group_in4); - if (data->type == emc6d100) - sysfs_remove_group(&client->dev.kobj, &lm85_group_in567); + lm85_remove_files(client, data); kfree(data); return 0; } @@ -1438,11 +1472,8 @@ static struct lm85_data *lm85_update_device(struct device *dev) lm85_read_value(client, LM85_REG_FAN(i)); } - if (!((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80))) { - data->in[4] = lm85_read_value(client, - LM85_REG_IN(4)); - } + if (!data->has_vid5) + data->in[4] = lm85_read_value(client, LM85_REG_IN(4)); if (data->type == adt7468) data->cfg5 = lm85_read_value(client, ADT7468_REG_CFG5); @@ -1468,7 +1499,8 @@ static struct lm85_data *lm85_update_device(struct device *dev) /* More alarm bits */ data->alarms |= lm85_read_value(client, EMC6D100_REG_ALARM3) << 16; - } else if (data->type == emc6d102) { + } else if (data->type == emc6d102 || data->type == emc6d103 || + data->type == emc6d103s) { /* Have to read LSB bits after the MSB ones because the reading of the MSB bits has frozen the LSBs (backward from the ADM1027). @@ -1509,8 +1541,7 @@ static struct lm85_data *lm85_update_device(struct device *dev) lm85_read_value(client, LM85_REG_FAN_MIN(i)); } - if (!((data->type == adt7463 || data->type == adt7468) && - (data->vid & 0x80))) { + if (!data->has_vid5) { data->in_min[4] = lm85_read_value(client, LM85_REG_IN_MIN(4)); data->in_max[4] = lm85_read_value(client, @@ -1554,17 +1585,19 @@ static struct lm85_data *lm85_update_device(struct device *dev) } } - i = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); - data->autofan[0].min_off = (i & 0x20) != 0; - data->autofan[1].min_off = (i & 0x40) != 0; - data->autofan[2].min_off = (i & 0x80) != 0; + if (data->type != emc6d103s) { + i = lm85_read_value(client, LM85_REG_AFAN_SPIKE1); + data->autofan[0].min_off = (i & 0x20) != 0; + data->autofan[1].min_off = (i & 0x40) != 0; + data->autofan[2].min_off = (i & 0x80) != 0; - i = lm85_read_value(client, LM85_REG_AFAN_HYST1); - data->zone[0].hyst = i >> 4; - data->zone[1].hyst = i & 0x0f; + i = lm85_read_value(client, LM85_REG_AFAN_HYST1); + data->zone[0].hyst = i >> 4; + data->zone[1].hyst = i & 0x0f; - i = lm85_read_value(client, LM85_REG_AFAN_HYST2); - data->zone[2].hyst = i >> 4; + i = lm85_read_value(client, LM85_REG_AFAN_HYST2); + data->zone[2].hyst = i >> 4; + } data->last_config = jiffies; } /* last_config */ diff --git a/drivers/hwmon/max16064.c b/drivers/hwmon/max16064.c new file mode 100644 index 000000000000..1d6d717060d3 --- /dev/null +++ b/drivers/hwmon/max16064.c @@ -0,0 +1,91 @@ +/* + * Hardware monitoring driver for Maxim MAX16064 + * + * Copyright (c) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +static struct pmbus_driver_info max16064_info = { + .pages = 4, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_TEMP, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT, +}; + +static int max16064_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max16064_info); +} + +static int max16064_remove(struct i2c_client *client) +{ + return pmbus_do_remove(client); +} + +static const struct i2c_device_id max16064_id[] = { + {"max16064", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max16064_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max16064_driver = { + .driver = { + .name = "max16064", + }, + .probe = max16064_probe, + .remove = max16064_remove, + .id_table = max16064_id, +}; + +static int __init max16064_init(void) +{ + return i2c_add_driver(&max16064_driver); +} + +static void __exit max16064_exit(void) +{ + i2c_del_driver(&max16064_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX16064"); +MODULE_LICENSE("GPL"); +module_init(max16064_init); +module_exit(max16064_exit); diff --git a/drivers/hwmon/max34440.c b/drivers/hwmon/max34440.c new file mode 100644 index 000000000000..992b701b4c5e --- /dev/null +++ b/drivers/hwmon/max34440.c @@ -0,0 +1,199 @@ +/* + * Hardware monitoring driver for Maxim MAX34440/MAX34441 + * + * Copyright (c) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +enum chips { max34440, max34441 }; + +#define MAX34440_STATUS_OC_WARN (1 << 0) +#define MAX34440_STATUS_OC_FAULT (1 << 1) +#define MAX34440_STATUS_OT_FAULT (1 << 5) +#define MAX34440_STATUS_OT_WARN (1 << 6) + +static int max34440_get_status(struct i2c_client *client, int page, int reg) +{ + int ret; + int mfg_status; + + ret = pmbus_set_page(client, page); + if (ret < 0) + return ret; + + switch (reg) { + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OC_WARN) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX34440_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + PMBUS_STATUS_MFR_SPECIFIC); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX34440_STATUS_OT_WARN) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX34440_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max34440_info[] = { + [max34440] = { + .pages = 14, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .direct[PSC_CURRENT_OUT] = true, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, /* R = 0 in datasheet reflects mV */ + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, /* R = 0 in datasheet reflects mA */ + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[12] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[13] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .get_status = max34440_get_status, + }, + [max34441] = { + .pages = 12, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .direct[PSC_CURRENT_OUT] = true, + .direct[PSC_FAN] = true, + .m[PSC_VOLTAGE_IN] = 1, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = 3, + .m[PSC_VOLTAGE_OUT] = 1, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = 3, + .m[PSC_CURRENT_OUT] = 1, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = 3, + .m[PSC_TEMPERATURE] = 1, + .b[PSC_TEMPERATURE] = 0, + .R[PSC_TEMPERATURE] = 2, + .m[PSC_FAN] = 1, + .b[PSC_FAN] = 0, + .R[PSC_FAN] = 0, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[1] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[2] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[3] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[4] = PMBUS_HAVE_VOUT | PMBUS_HAVE_STATUS_VOUT + | PMBUS_HAVE_IOUT | PMBUS_HAVE_STATUS_IOUT, + .func[5] = PMBUS_HAVE_FAN12 | PMBUS_HAVE_STATUS_FAN12, + .func[6] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[7] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[8] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[9] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[10] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .func[11] = PMBUS_HAVE_TEMP | PMBUS_HAVE_STATUS_TEMP, + .get_status = max34440_get_status, + }, +}; + +static int max34440_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max34440_info[id->driver_data]); +} + +static int max34440_remove(struct i2c_client *client) +{ + return pmbus_do_remove(client); +} + +static const struct i2c_device_id max34440_id[] = { + {"max34440", max34440}, + {"max34441", max34441}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, max34440_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max34440_driver = { + .driver = { + .name = "max34440", + }, + .probe = max34440_probe, + .remove = max34440_remove, + .id_table = max34440_id, +}; + +static int __init max34440_init(void) +{ + return i2c_add_driver(&max34440_driver); +} + +static void __exit max34440_exit(void) +{ + i2c_del_driver(&max34440_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX34440/MAX34441"); +MODULE_LICENSE("GPL"); +module_init(max34440_init); +module_exit(max34440_exit); diff --git a/drivers/hwmon/max6639.c b/drivers/hwmon/max6639.c new file mode 100644 index 000000000000..f20d9978ee78 --- /dev/null +++ b/drivers/hwmon/max6639.c @@ -0,0 +1,653 @@ +/* + * max6639.c - Support for Maxim MAX6639 + * + * 2-Channel Temperature Monitor with Dual PWM Fan-Speed Controller + * + * Copyright (C) 2010, 2011 Roland Stigge <stigge@antcom.de> + * + * based on the initial MAX6639 support from semptian.net + * by He Changqing <hechangqing@semptian.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, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/jiffies.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/i2c/max6639.h> + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { 0x2c, 0x2e, 0x2f, I2C_CLIENT_END }; + +/* The MAX6639 registers, valid channel numbers: 0, 1 */ +#define MAX6639_REG_TEMP(ch) (0x00 + (ch)) +#define MAX6639_REG_STATUS 0x02 +#define MAX6639_REG_OUTPUT_MASK 0x03 +#define MAX6639_REG_GCONFIG 0x04 +#define MAX6639_REG_TEMP_EXT(ch) (0x05 + (ch)) +#define MAX6639_REG_ALERT_LIMIT(ch) (0x08 + (ch)) +#define MAX6639_REG_OT_LIMIT(ch) (0x0A + (ch)) +#define MAX6639_REG_THERM_LIMIT(ch) (0x0C + (ch)) +#define MAX6639_REG_FAN_CONFIG1(ch) (0x10 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG2a(ch) (0x11 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG2b(ch) (0x12 + (ch) * 4) +#define MAX6639_REG_FAN_CONFIG3(ch) (0x13 + (ch) * 4) +#define MAX6639_REG_FAN_CNT(ch) (0x20 + (ch)) +#define MAX6639_REG_TARGET_CNT(ch) (0x22 + (ch)) +#define MAX6639_REG_FAN_PPR(ch) (0x24 + (ch)) +#define MAX6639_REG_TARGTDUTY(ch) (0x26 + (ch)) +#define MAX6639_REG_FAN_START_TEMP(ch) (0x28 + (ch)) +#define MAX6639_REG_DEVID 0x3D +#define MAX6639_REG_MANUID 0x3E +#define MAX6639_REG_DEVREV 0x3F + +/* Register bits */ +#define MAX6639_GCONFIG_STANDBY 0x80 +#define MAX6639_GCONFIG_POR 0x40 +#define MAX6639_GCONFIG_DISABLE_TIMEOUT 0x20 +#define MAX6639_GCONFIG_CH2_LOCAL 0x10 +#define MAX6639_GCONFIG_PWM_FREQ_HI 0x08 + +#define MAX6639_FAN_CONFIG1_PWM 0x80 + +#define MAX6639_FAN_CONFIG3_THERM_FULL_SPEED 0x40 + +static const int rpm_ranges[] = { 2000, 4000, 8000, 16000 }; + +#define FAN_FROM_REG(val, div, rpm_range) ((val) == 0 ? -1 : \ + (val) == 255 ? 0 : (rpm_ranges[rpm_range] * 30) / ((div + 1) * (val))) +#define TEMP_LIMIT_TO_REG(val) SENSORS_LIMIT((val) / 1000, 0, 255) + +/* + * Client data (each client gets its own) + */ +struct max6639_data { + struct device *hwmon_dev; + struct mutex update_lock; + char valid; /* !=0 if following fields are valid */ + unsigned long last_updated; /* In jiffies */ + + /* Register values sampled regularly */ + u16 temp[2]; /* Temperature, in 1/8 C, 0..255 C */ + bool temp_fault[2]; /* Detected temperature diode failure */ + u8 fan[2]; /* Register value: TACH count for fans >=30 */ + u8 status; /* Detected channel alarms and fan failures */ + + /* Register values only written to */ + u8 pwm[2]; /* Register value: Duty cycle 0..120 */ + u8 temp_therm[2]; /* THERM Temperature, 0..255 C (->_max) */ + u8 temp_alert[2]; /* ALERT Temperature, 0..255 C (->_crit) */ + u8 temp_ot[2]; /* OT Temperature, 0..255 C (->_emergency) */ + + /* Register values initialized only once */ + u8 ppr; /* Pulses per rotation 0..3 for 1..4 ppr */ + u8 rpm_range; /* Index in above rpm_ranges table */ +}; + +static struct max6639_data *max6639_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct max6639_data *ret = data; + int i; + int status_reg; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + 2 * HZ) || !data->valid) { + int res; + + dev_dbg(&client->dev, "Starting max6639 update\n"); + + status_reg = i2c_smbus_read_byte_data(client, + MAX6639_REG_STATUS); + if (status_reg < 0) { + ret = ERR_PTR(status_reg); + goto abort; + } + + data->status = status_reg; + + for (i = 0; i < 2; i++) { + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_FAN_CNT(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->fan[i] = res; + + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_TEMP_EXT(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->temp[i] = res >> 5; + data->temp_fault[i] = res & 0x01; + + res = i2c_smbus_read_byte_data(client, + MAX6639_REG_TEMP(i)); + if (res < 0) { + ret = ERR_PTR(res); + goto abort; + } + data->temp[i] |= res << 3; + } + + data->last_updated = jiffies; + data->valid = 1; + } +abort: + mutex_unlock(&data->update_lock); + + return ret; +} + +static ssize_t show_temp_input(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + long temp; + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + temp = data->temp[attr->index] * 125; + return sprintf(buf, "%ld\n", temp); +} + +static ssize_t show_temp_fault(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", data->temp_fault[attr->index]); +} + +static ssize_t show_temp_max(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", (data->temp_therm[attr->index] * 1000)); +} + +static ssize_t set_temp_max(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_therm[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_THERM_LIMIT(attr->index), + data->temp_therm[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_crit(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", (data->temp_alert[attr->index] * 1000)); +} + +static ssize_t set_temp_crit(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_alert[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_ALERT_LIMIT(attr->index), + data->temp_alert[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_temp_emergency(struct device *dev, + struct device_attribute *dev_attr, + char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", (data->temp_ot[attr->index] * 1000)); +} + +static ssize_t set_temp_emergency(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + mutex_lock(&data->update_lock); + data->temp_ot[attr->index] = TEMP_LIMIT_TO_REG(val); + i2c_smbus_write_byte_data(client, + MAX6639_REG_OT_LIMIT(attr->index), + data->temp_ot[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + return sprintf(buf, "%d\n", data->pwm[attr->index] * 255 / 120); +} + +static ssize_t set_pwm(struct device *dev, + struct device_attribute *dev_attr, + const char *buf, size_t count) +{ + struct i2c_client *client = to_i2c_client(dev); + struct max6639_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + unsigned long val; + int res; + + res = strict_strtoul(buf, 10, &val); + if (res) + return res; + + val = SENSORS_LIMIT(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm[attr->index] = (u8)(val * 120 / 255); + i2c_smbus_write_byte_data(client, + MAX6639_REG_TARGTDUTY(attr->index), + data->pwm[attr->index]); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_fan_input(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", FAN_FROM_REG(data->fan[attr->index], + data->ppr, data->rpm_range)); +} + +static ssize_t show_alarm(struct device *dev, + struct device_attribute *dev_attr, char *buf) +{ + struct max6639_data *data = max6639_update_device(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(dev_attr); + + if (IS_ERR(data)) + return PTR_ERR(data); + + return sprintf(buf, "%d\n", !!(data->status & (1 << attr->index))); +} + +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_temp_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_temp_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0); +static SENSOR_DEVICE_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 0); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_temp_max, + set_temp_max, 1); +static SENSOR_DEVICE_ATTR(temp1_crit, S_IWUSR | S_IRUGO, show_temp_crit, + set_temp_crit, 0); +static SENSOR_DEVICE_ATTR(temp2_crit, S_IWUSR | S_IRUGO, show_temp_crit, + set_temp_crit, 1); +static SENSOR_DEVICE_ATTR(temp1_emergency, S_IWUSR | S_IRUGO, + show_temp_emergency, set_temp_emergency, 0); +static SENSOR_DEVICE_ATTR(temp2_emergency, S_IWUSR | S_IRUGO, + show_temp_emergency, set_temp_emergency, 1); +static SENSOR_DEVICE_ATTR(pwm1, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 0); +static SENSOR_DEVICE_ATTR(pwm2, S_IWUSR | S_IRUGO, show_pwm, set_pwm, 1); +static SENSOR_DEVICE_ATTR(fan1_input, S_IRUGO, show_fan_input, NULL, 0); +static SENSOR_DEVICE_ATTR(fan2_input, S_IRUGO, show_fan_input, NULL, 1); +static SENSOR_DEVICE_ATTR(fan1_fault, S_IRUGO, show_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(fan2_fault, S_IRUGO, show_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp1_crit_alarm, S_IRUGO, show_alarm, NULL, 7); +static SENSOR_DEVICE_ATTR(temp2_crit_alarm, S_IRUGO, show_alarm, NULL, 6); +static SENSOR_DEVICE_ATTR(temp1_emergency_alarm, S_IRUGO, show_alarm, NULL, 5); +static SENSOR_DEVICE_ATTR(temp2_emergency_alarm, S_IRUGO, show_alarm, NULL, 4); + + +static struct attribute *max6639_attributes[] = { + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp1_fault.dev_attr.attr, + &sensor_dev_attr_temp2_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp1_crit.dev_attr.attr, + &sensor_dev_attr_temp2_crit.dev_attr.attr, + &sensor_dev_attr_temp1_emergency.dev_attr.attr, + &sensor_dev_attr_temp2_emergency.dev_attr.attr, + &sensor_dev_attr_pwm1.dev_attr.attr, + &sensor_dev_attr_pwm2.dev_attr.attr, + &sensor_dev_attr_fan1_input.dev_attr.attr, + &sensor_dev_attr_fan2_input.dev_attr.attr, + &sensor_dev_attr_fan1_fault.dev_attr.attr, + &sensor_dev_attr_fan2_fault.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_crit_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_emergency_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_emergency_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group max6639_group = { + .attrs = max6639_attributes, +}; + +/* + * returns respective index in rpm_ranges table + * 1 by default on invalid range + */ +static int rpm_range_to_reg(int range) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(rpm_ranges); i++) { + if (rpm_ranges[i] == range) + return i; + } + + return 1; /* default: 4000 RPM */ +} + +static int max6639_init_client(struct i2c_client *client) +{ + struct max6639_data *data = i2c_get_clientdata(client); + struct max6639_platform_data *max6639_info = + client->dev.platform_data; + int i = 0; + int rpm_range = 1; /* default: 4000 RPM */ + int err = 0; + + /* Reset chip to default values, see below for GCONFIG setup */ + err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, + MAX6639_GCONFIG_POR); + if (err) + goto exit; + + /* Fans pulse per revolution is 2 by default */ + if (max6639_info && max6639_info->ppr > 0 && + max6639_info->ppr < 5) + data->ppr = max6639_info->ppr; + else + data->ppr = 2; + data->ppr -= 1; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_PPR(i), + data->ppr << 5); + if (err) + goto exit; + + if (max6639_info) + rpm_range = rpm_range_to_reg(max6639_info->rpm_range); + data->rpm_range = rpm_range; + + for (i = 0; i < 2; i++) { + + /* Fans config PWM, RPM */ + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG1(i), + MAX6639_FAN_CONFIG1_PWM | rpm_range); + if (err) + goto exit; + + /* Fans PWM polarity high by default */ + if (max6639_info && max6639_info->pwm_polarity == 0) + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG2a(i), 0x00); + else + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG2a(i), 0x02); + if (err) + goto exit; + + /* + * /THERM full speed enable, + * PWM frequency 25kHz, see also GCONFIG below + */ + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_FAN_CONFIG3(i), + MAX6639_FAN_CONFIG3_THERM_FULL_SPEED | 0x03); + if (err) + goto exit; + + /* Max. temp. 80C/90C/100C */ + data->temp_therm[i] = 80; + data->temp_alert[i] = 90; + data->temp_ot[i] = 100; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_THERM_LIMIT(i), + data->temp_therm[i]); + if (err) + goto exit; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_ALERT_LIMIT(i), + data->temp_alert[i]); + if (err) + goto exit; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_OT_LIMIT(i), data->temp_ot[i]); + if (err) + goto exit; + + /* PWM 120/120 (i.e. 100%) */ + data->pwm[i] = 120; + err = i2c_smbus_write_byte_data(client, + MAX6639_REG_TARGTDUTY(i), data->pwm[i]); + if (err) + goto exit; + } + /* Start monitoring */ + err = i2c_smbus_write_byte_data(client, MAX6639_REG_GCONFIG, + MAX6639_GCONFIG_DISABLE_TIMEOUT | MAX6639_GCONFIG_CH2_LOCAL | + MAX6639_GCONFIG_PWM_FREQ_HI); +exit: + return err; +} + +/* Return 0 if detection is successful, -ENODEV otherwise */ +static int max6639_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int dev_id, manu_id; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + /* Actual detection via device and manufacturer ID */ + dev_id = i2c_smbus_read_byte_data(client, MAX6639_REG_DEVID); + manu_id = i2c_smbus_read_byte_data(client, MAX6639_REG_MANUID); + if (dev_id != 0x58 || manu_id != 0x4D) + return -ENODEV; + + strlcpy(info->type, "max6639", I2C_NAME_SIZE); + + return 0; +} + +static int max6639_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct max6639_data *data; + int err; + + data = kzalloc(sizeof(struct max6639_data), GFP_KERNEL); + if (!data) { + err = -ENOMEM; + goto exit; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the max6639 chip */ + err = max6639_init_client(client); + if (err < 0) + goto error_free; + + /* Register sysfs hooks */ + err = sysfs_create_group(&client->dev.kobj, &max6639_group); + if (err) + goto error_free; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + goto error_remove; + } + + dev_info(&client->dev, "temperature sensor and fan control found\n"); + + return 0; + +error_remove: + sysfs_remove_group(&client->dev.kobj, &max6639_group); +error_free: + kfree(data); +exit: + return err; +} + +static int max6639_remove(struct i2c_client *client) +{ + struct max6639_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &max6639_group); + + kfree(data); + return 0; +} + +static int max6639_suspend(struct i2c_client *client, pm_message_t mesg) +{ + int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (data < 0) + return data; + + return i2c_smbus_write_byte_data(client, + MAX6639_REG_GCONFIG, data | MAX6639_GCONFIG_STANDBY); +} + +static int max6639_resume(struct i2c_client *client) +{ + int data = i2c_smbus_read_byte_data(client, MAX6639_REG_GCONFIG); + if (data < 0) + return data; + + return i2c_smbus_write_byte_data(client, + MAX6639_REG_GCONFIG, data & ~MAX6639_GCONFIG_STANDBY); +} + +static const struct i2c_device_id max6639_id[] = { + {"max6639", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max6639_id); + +static struct i2c_driver max6639_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "max6639", + }, + .probe = max6639_probe, + .remove = max6639_remove, + .suspend = max6639_suspend, + .resume = max6639_resume, + .id_table = max6639_id, + .detect = max6639_detect, + .address_list = normal_i2c, +}; + +static int __init max6639_init(void) +{ + return i2c_add_driver(&max6639_driver); +} + +static void __exit max6639_exit(void) +{ + i2c_del_driver(&max6639_driver); +} + +MODULE_AUTHOR("Roland Stigge <stigge@antcom.de>"); +MODULE_DESCRIPTION("max6639 driver"); +MODULE_LICENSE("GPL"); + +module_init(max6639_init); +module_exit(max6639_exit); diff --git a/drivers/hwmon/max8688.c b/drivers/hwmon/max8688.c new file mode 100644 index 000000000000..8ebfef2ecf26 --- /dev/null +++ b/drivers/hwmon/max8688.c @@ -0,0 +1,158 @@ +/* + * Hardware monitoring driver for Maxim MAX8688 + * + * Copyright (c) 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include "pmbus.h" + +#define MAX8688_MFG_STATUS 0xd8 + +#define MAX8688_STATUS_OC_FAULT (1 << 4) +#define MAX8688_STATUS_OV_FAULT (1 << 5) +#define MAX8688_STATUS_OV_WARNING (1 << 8) +#define MAX8688_STATUS_UV_FAULT (1 << 9) +#define MAX8688_STATUS_UV_WARNING (1 << 10) +#define MAX8688_STATUS_UC_FAULT (1 << 11) +#define MAX8688_STATUS_OC_WARNING (1 << 12) +#define MAX8688_STATUS_OT_FAULT (1 << 13) +#define MAX8688_STATUS_OT_WARNING (1 << 14) + +static int max8688_get_status(struct i2c_client *client, int page, int reg) +{ + int ret = 0; + int mfg_status; + + if (page) + return -EINVAL; + + switch (reg) { + case PMBUS_STATUS_VOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UV_WARNING) + ret |= PB_VOLTAGE_UV_WARNING; + if (mfg_status & MAX8688_STATUS_UV_FAULT) + ret |= PB_VOLTAGE_UV_FAULT; + if (mfg_status & MAX8688_STATUS_OV_WARNING) + ret |= PB_VOLTAGE_OV_WARNING; + if (mfg_status & MAX8688_STATUS_OV_FAULT) + ret |= PB_VOLTAGE_OV_FAULT; + break; + case PMBUS_STATUS_IOUT: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_UC_FAULT) + ret |= PB_IOUT_UC_FAULT; + if (mfg_status & MAX8688_STATUS_OC_WARNING) + ret |= PB_IOUT_OC_WARNING; + if (mfg_status & MAX8688_STATUS_OC_FAULT) + ret |= PB_IOUT_OC_FAULT; + break; + case PMBUS_STATUS_TEMPERATURE: + mfg_status = pmbus_read_word_data(client, 0, + MAX8688_MFG_STATUS); + if (mfg_status < 0) + return mfg_status; + if (mfg_status & MAX8688_STATUS_OT_WARNING) + ret |= PB_TEMP_OT_WARNING; + if (mfg_status & MAX8688_STATUS_OT_FAULT) + ret |= PB_TEMP_OT_FAULT; + break; + default: + ret = -ENODATA; + break; + } + return ret; +} + +static struct pmbus_driver_info max8688_info = { + .pages = 1, + .direct[PSC_VOLTAGE_IN] = true, + .direct[PSC_VOLTAGE_OUT] = true, + .direct[PSC_TEMPERATURE] = true, + .direct[PSC_CURRENT_OUT] = true, + .m[PSC_VOLTAGE_IN] = 19995, + .b[PSC_VOLTAGE_IN] = 0, + .R[PSC_VOLTAGE_IN] = -1, + .m[PSC_VOLTAGE_OUT] = 19995, + .b[PSC_VOLTAGE_OUT] = 0, + .R[PSC_VOLTAGE_OUT] = -1, + .m[PSC_CURRENT_OUT] = 23109, + .b[PSC_CURRENT_OUT] = 0, + .R[PSC_CURRENT_OUT] = -2, + .m[PSC_TEMPERATURE] = -7612, + .b[PSC_TEMPERATURE] = 335, + .R[PSC_TEMPERATURE] = -3, + .func[0] = PMBUS_HAVE_VOUT | PMBUS_HAVE_IOUT | PMBUS_HAVE_TEMP + | PMBUS_HAVE_STATUS_VOUT | PMBUS_HAVE_STATUS_IOUT + | PMBUS_HAVE_STATUS_TEMP, + .get_status = max8688_get_status, +}; + +static int max8688_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + return pmbus_do_probe(client, id, &max8688_info); +} + +static int max8688_remove(struct i2c_client *client) +{ + return pmbus_do_remove(client); +} + +static const struct i2c_device_id max8688_id[] = { + {"max8688", 0}, + { } +}; + +MODULE_DEVICE_TABLE(i2c, max8688_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver max8688_driver = { + .driver = { + .name = "max8688", + }, + .probe = max8688_probe, + .remove = max8688_remove, + .id_table = max8688_id, +}; + +static int __init max8688_init(void) +{ + return i2c_add_driver(&max8688_driver); +} + +static void __exit max8688_exit(void) +{ + i2c_del_driver(&max8688_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus driver for Maxim MAX8688"); +MODULE_LICENSE("GPL"); +module_init(max8688_init); +module_exit(max8688_exit); diff --git a/drivers/hwmon/pmbus.c b/drivers/hwmon/pmbus.c new file mode 100644 index 000000000000..98e2e28899e2 --- /dev/null +++ b/drivers/hwmon/pmbus.c @@ -0,0 +1,203 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/mutex.h> +#include <linux/i2c.h> +#include "pmbus.h" + +/* + * Find sensor groups and status registers on each page. + */ +static void pmbus_find_sensor_groups(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + int page; + + /* Sensors detected on page 0 only */ + if (pmbus_check_word_register(client, 0, PMBUS_READ_VIN)) + info->func[0] |= PMBUS_HAVE_VIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_VCAP)) + info->func[0] |= PMBUS_HAVE_VCAP; + if (pmbus_check_word_register(client, 0, PMBUS_READ_IIN)) + info->func[0] |= PMBUS_HAVE_IIN; + if (pmbus_check_word_register(client, 0, PMBUS_READ_PIN)) + info->func[0] |= PMBUS_HAVE_PIN; + if (info->func[0] + && pmbus_check_byte_register(client, 0, PMBUS_STATUS_INPUT)) + info->func[0] |= PMBUS_HAVE_STATUS_INPUT; + if (pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_1)) { + info->func[0] |= PMBUS_HAVE_FAN12; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_12)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN12; + } + if (pmbus_check_word_register(client, 0, PMBUS_READ_FAN_SPEED_3)) { + info->func[0] |= PMBUS_HAVE_FAN34; + if (pmbus_check_byte_register(client, 0, PMBUS_STATUS_FAN_34)) + info->func[0] |= PMBUS_HAVE_STATUS_FAN34; + } + if (pmbus_check_word_register(client, 0, PMBUS_READ_TEMPERATURE_1)) { + info->func[0] |= PMBUS_HAVE_TEMP; + if (pmbus_check_byte_register(client, 0, + PMBUS_STATUS_TEMPERATURE)) + info->func[0] |= PMBUS_HAVE_STATUS_TEMP; + } + + /* Sensors detected on all pages */ + for (page = 0; page < info->pages; page++) { + if (pmbus_check_word_register(client, page, PMBUS_READ_VOUT)) { + info->func[page] |= PMBUS_HAVE_VOUT; + if (pmbus_check_byte_register(client, page, + PMBUS_STATUS_VOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_VOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_IOUT)) { + info->func[page] |= PMBUS_HAVE_IOUT; + if (pmbus_check_byte_register(client, 0, + PMBUS_STATUS_IOUT)) + info->func[page] |= PMBUS_HAVE_STATUS_IOUT; + } + if (pmbus_check_word_register(client, page, PMBUS_READ_POUT)) + info->func[page] |= PMBUS_HAVE_POUT; + } +} + +/* + * Identify chip parameters. + */ +static int pmbus_identify(struct i2c_client *client, + struct pmbus_driver_info *info) +{ + if (!info->pages) { + /* + * Check if the PAGE command is supported. If it is, + * keep setting the page number until it fails or until the + * maximum number of pages has been reached. Assume that + * this is the number of pages supported by the chip. + */ + if (pmbus_check_byte_register(client, 0, PMBUS_PAGE)) { + int page; + + for (page = 1; page < PMBUS_PAGES; page++) { + if (pmbus_set_page(client, page) < 0) + break; + } + pmbus_set_page(client, 0); + info->pages = page; + } else { + info->pages = 1; + } + } + + /* + * We should check if the COEFFICIENTS register is supported. + * If it is, and the chip is configured for direct mode, we can read + * the coefficients from the chip, one set per group of sensor + * registers. + * + * To do this, we will need access to a chip which actually supports the + * COEFFICIENTS command, since the command is too complex to implement + * without testing it. + */ + + /* Try to find sensor groups */ + pmbus_find_sensor_groups(client, info); + + return 0; +} + +static int pmbus_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct pmbus_driver_info *info; + int ret; + + info = kzalloc(sizeof(struct pmbus_driver_info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + info->pages = id->driver_data; + info->identify = pmbus_identify; + + ret = pmbus_do_probe(client, id, info); + if (ret < 0) + goto out; + return 0; + +out: + kfree(info); + return ret; +} + +static int pmbus_remove(struct i2c_client *client) +{ + int ret; + const struct pmbus_driver_info *info; + + info = pmbus_get_driver_info(client); + ret = pmbus_do_remove(client); + kfree(info); + return ret; +} + +/* + * Use driver_data to set the number of pages supported by the chip. + */ +static const struct i2c_device_id pmbus_id[] = { + {"bmr450", 1}, + {"bmr451", 1}, + {"bmr453", 1}, + {"bmr454", 1}, + {"ltc2978", 8}, + {"pmbus", 0}, + {} +}; + +MODULE_DEVICE_TABLE(i2c, pmbus_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver pmbus_driver = { + .driver = { + .name = "pmbus", + }, + .probe = pmbus_probe, + .remove = pmbus_remove, + .id_table = pmbus_id, +}; + +static int __init pmbus_init(void) +{ + return i2c_add_driver(&pmbus_driver); +} + +static void __exit pmbus_exit(void) +{ + i2c_del_driver(&pmbus_driver); +} + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("Generic PMBus driver"); +MODULE_LICENSE("GPL"); +module_init(pmbus_init); +module_exit(pmbus_exit); diff --git a/drivers/hwmon/pmbus.h b/drivers/hwmon/pmbus.h new file mode 100644 index 000000000000..a81f7f228762 --- /dev/null +++ b/drivers/hwmon/pmbus.h @@ -0,0 +1,313 @@ +/* + * pmbus.h - Common defines and structures for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef PMBUS_H +#define PMBUS_H + +/* + * Registers + */ +#define PMBUS_PAGE 0x00 +#define PMBUS_OPERATION 0x01 +#define PMBUS_ON_OFF_CONFIG 0x02 +#define PMBUS_CLEAR_FAULTS 0x03 +#define PMBUS_PHASE 0x04 + +#define PMBUS_CAPABILITY 0x19 +#define PMBUS_QUERY 0x1A + +#define PMBUS_VOUT_MODE 0x20 +#define PMBUS_VOUT_COMMAND 0x21 +#define PMBUS_VOUT_TRIM 0x22 +#define PMBUS_VOUT_CAL_OFFSET 0x23 +#define PMBUS_VOUT_MAX 0x24 +#define PMBUS_VOUT_MARGIN_HIGH 0x25 +#define PMBUS_VOUT_MARGIN_LOW 0x26 +#define PMBUS_VOUT_TRANSITION_RATE 0x27 +#define PMBUS_VOUT_DROOP 0x28 +#define PMBUS_VOUT_SCALE_LOOP 0x29 +#define PMBUS_VOUT_SCALE_MONITOR 0x2A + +#define PMBUS_COEFFICIENTS 0x30 +#define PMBUS_POUT_MAX 0x31 + +#define PMBUS_FAN_CONFIG_12 0x3A +#define PMBUS_FAN_COMMAND_1 0x3B +#define PMBUS_FAN_COMMAND_2 0x3C +#define PMBUS_FAN_CONFIG_34 0x3D +#define PMBUS_FAN_COMMAND_3 0x3E +#define PMBUS_FAN_COMMAND_4 0x3F + +#define PMBUS_VOUT_OV_FAULT_LIMIT 0x40 +#define PMBUS_VOUT_OV_FAULT_RESPONSE 0x41 +#define PMBUS_VOUT_OV_WARN_LIMIT 0x42 +#define PMBUS_VOUT_UV_WARN_LIMIT 0x43 +#define PMBUS_VOUT_UV_FAULT_LIMIT 0x44 +#define PMBUS_VOUT_UV_FAULT_RESPONSE 0x45 +#define PMBUS_IOUT_OC_FAULT_LIMIT 0x46 +#define PMBUS_IOUT_OC_FAULT_RESPONSE 0x47 +#define PMBUS_IOUT_OC_LV_FAULT_LIMIT 0x48 +#define PMBUS_IOUT_OC_LV_FAULT_RESPONSE 0x49 +#define PMBUS_IOUT_OC_WARN_LIMIT 0x4A +#define PMBUS_IOUT_UC_FAULT_LIMIT 0x4B +#define PMBUS_IOUT_UC_FAULT_RESPONSE 0x4C + +#define PMBUS_OT_FAULT_LIMIT 0x4F +#define PMBUS_OT_FAULT_RESPONSE 0x50 +#define PMBUS_OT_WARN_LIMIT 0x51 +#define PMBUS_UT_WARN_LIMIT 0x52 +#define PMBUS_UT_FAULT_LIMIT 0x53 +#define PMBUS_UT_FAULT_RESPONSE 0x54 +#define PMBUS_VIN_OV_FAULT_LIMIT 0x55 +#define PMBUS_VIN_OV_FAULT_RESPONSE 0x56 +#define PMBUS_VIN_OV_WARN_LIMIT 0x57 +#define PMBUS_VIN_UV_WARN_LIMIT 0x58 +#define PMBUS_VIN_UV_FAULT_LIMIT 0x59 + +#define PMBUS_IIN_OC_FAULT_LIMIT 0x5B +#define PMBUS_IIN_OC_WARN_LIMIT 0x5D + +#define PMBUS_POUT_OP_FAULT_LIMIT 0x68 +#define PMBUS_POUT_OP_WARN_LIMIT 0x6A +#define PMBUS_PIN_OP_WARN_LIMIT 0x6B + +#define PMBUS_STATUS_BYTE 0x78 +#define PMBUS_STATUS_WORD 0x79 +#define PMBUS_STATUS_VOUT 0x7A +#define PMBUS_STATUS_IOUT 0x7B +#define PMBUS_STATUS_INPUT 0x7C +#define PMBUS_STATUS_TEMPERATURE 0x7D +#define PMBUS_STATUS_CML 0x7E +#define PMBUS_STATUS_OTHER 0x7F +#define PMBUS_STATUS_MFR_SPECIFIC 0x80 +#define PMBUS_STATUS_FAN_12 0x81 +#define PMBUS_STATUS_FAN_34 0x82 + +#define PMBUS_READ_VIN 0x88 +#define PMBUS_READ_IIN 0x89 +#define PMBUS_READ_VCAP 0x8A +#define PMBUS_READ_VOUT 0x8B +#define PMBUS_READ_IOUT 0x8C +#define PMBUS_READ_TEMPERATURE_1 0x8D +#define PMBUS_READ_TEMPERATURE_2 0x8E +#define PMBUS_READ_TEMPERATURE_3 0x8F +#define PMBUS_READ_FAN_SPEED_1 0x90 +#define PMBUS_READ_FAN_SPEED_2 0x91 +#define PMBUS_READ_FAN_SPEED_3 0x92 +#define PMBUS_READ_FAN_SPEED_4 0x93 +#define PMBUS_READ_DUTY_CYCLE 0x94 +#define PMBUS_READ_FREQUENCY 0x95 +#define PMBUS_READ_POUT 0x96 +#define PMBUS_READ_PIN 0x97 + +#define PMBUS_REVISION 0x98 +#define PMBUS_MFR_ID 0x99 +#define PMBUS_MFR_MODEL 0x9A +#define PMBUS_MFR_REVISION 0x9B +#define PMBUS_MFR_LOCATION 0x9C +#define PMBUS_MFR_DATE 0x9D +#define PMBUS_MFR_SERIAL 0x9E + +/* + * CAPABILITY + */ +#define PB_CAPABILITY_SMBALERT (1<<4) +#define PB_CAPABILITY_ERROR_CHECK (1<<7) + +/* + * VOUT_MODE + */ +#define PB_VOUT_MODE_MODE_MASK 0xe0 +#define PB_VOUT_MODE_PARAM_MASK 0x1f + +#define PB_VOUT_MODE_LINEAR 0x00 +#define PB_VOUT_MODE_VID 0x20 +#define PB_VOUT_MODE_DIRECT 0x40 + +/* + * Fan configuration + */ +#define PB_FAN_2_PULSE_MASK ((1 << 0) | (1 << 1)) +#define PB_FAN_2_RPM (1 << 2) +#define PB_FAN_2_INSTALLED (1 << 3) +#define PB_FAN_1_PULSE_MASK ((1 << 4) | (1 << 5)) +#define PB_FAN_1_RPM (1 << 6) +#define PB_FAN_1_INSTALLED (1 << 7) + +/* + * STATUS_BYTE, STATUS_WORD (lower) + */ +#define PB_STATUS_NONE_ABOVE (1<<0) +#define PB_STATUS_CML (1<<1) +#define PB_STATUS_TEMPERATURE (1<<2) +#define PB_STATUS_VIN_UV (1<<3) +#define PB_STATUS_IOUT_OC (1<<4) +#define PB_STATUS_VOUT_OV (1<<5) +#define PB_STATUS_OFF (1<<6) +#define PB_STATUS_BUSY (1<<7) + +/* + * STATUS_WORD (upper) + */ +#define PB_STATUS_UNKNOWN (1<<8) +#define PB_STATUS_OTHER (1<<9) +#define PB_STATUS_FANS (1<<10) +#define PB_STATUS_POWER_GOOD_N (1<<11) +#define PB_STATUS_WORD_MFR (1<<12) +#define PB_STATUS_INPUT (1<<13) +#define PB_STATUS_IOUT_POUT (1<<14) +#define PB_STATUS_VOUT (1<<15) + +/* + * STATUS_IOUT + */ +#define PB_POUT_OP_WARNING (1<<0) +#define PB_POUT_OP_FAULT (1<<1) +#define PB_POWER_LIMITING (1<<2) +#define PB_CURRENT_SHARE_FAULT (1<<3) +#define PB_IOUT_UC_FAULT (1<<4) +#define PB_IOUT_OC_WARNING (1<<5) +#define PB_IOUT_OC_LV_FAULT (1<<6) +#define PB_IOUT_OC_FAULT (1<<7) + +/* + * STATUS_VOUT, STATUS_INPUT + */ +#define PB_VOLTAGE_UV_FAULT (1<<4) +#define PB_VOLTAGE_UV_WARNING (1<<5) +#define PB_VOLTAGE_OV_WARNING (1<<6) +#define PB_VOLTAGE_OV_FAULT (1<<7) + +/* + * STATUS_INPUT + */ +#define PB_PIN_OP_WARNING (1<<0) +#define PB_IIN_OC_WARNING (1<<1) +#define PB_IIN_OC_FAULT (1<<2) + +/* + * STATUS_TEMPERATURE + */ +#define PB_TEMP_UT_FAULT (1<<4) +#define PB_TEMP_UT_WARNING (1<<5) +#define PB_TEMP_OT_WARNING (1<<6) +#define PB_TEMP_OT_FAULT (1<<7) + +/* + * STATUS_FAN + */ +#define PB_FAN_AIRFLOW_WARNING (1<<0) +#define PB_FAN_AIRFLOW_FAULT (1<<1) +#define PB_FAN_FAN2_SPEED_OVERRIDE (1<<2) +#define PB_FAN_FAN1_SPEED_OVERRIDE (1<<3) +#define PB_FAN_FAN2_WARNING (1<<4) +#define PB_FAN_FAN1_WARNING (1<<5) +#define PB_FAN_FAN2_FAULT (1<<6) +#define PB_FAN_FAN1_FAULT (1<<7) + +/* + * CML_FAULT_STATUS + */ +#define PB_CML_FAULT_OTHER_MEM_LOGIC (1<<0) +#define PB_CML_FAULT_OTHER_COMM (1<<1) +#define PB_CML_FAULT_PROCESSOR (1<<3) +#define PB_CML_FAULT_MEMORY (1<<4) +#define PB_CML_FAULT_PACKET_ERROR (1<<5) +#define PB_CML_FAULT_INVALID_DATA (1<<6) +#define PB_CML_FAULT_INVALID_COMMAND (1<<7) + +enum pmbus_sensor_classes { + PSC_VOLTAGE_IN = 0, + PSC_VOLTAGE_OUT, + PSC_CURRENT_IN, + PSC_CURRENT_OUT, + PSC_POWER, + PSC_TEMPERATURE, + PSC_FAN, + PSC_NUM_CLASSES /* Number of power sensor classes */ +}; + +#define PMBUS_PAGES 32 /* Per PMBus specification */ + +/* Functionality bit mask */ +#define PMBUS_HAVE_VIN (1 << 0) +#define PMBUS_HAVE_VCAP (1 << 1) +#define PMBUS_HAVE_VOUT (1 << 2) +#define PMBUS_HAVE_IIN (1 << 3) +#define PMBUS_HAVE_IOUT (1 << 4) +#define PMBUS_HAVE_PIN (1 << 5) +#define PMBUS_HAVE_POUT (1 << 6) +#define PMBUS_HAVE_FAN12 (1 << 7) +#define PMBUS_HAVE_FAN34 (1 << 8) +#define PMBUS_HAVE_TEMP (1 << 9) +#define PMBUS_HAVE_TEMP2 (1 << 10) +#define PMBUS_HAVE_TEMP3 (1 << 11) +#define PMBUS_HAVE_STATUS_VOUT (1 << 12) +#define PMBUS_HAVE_STATUS_IOUT (1 << 13) +#define PMBUS_HAVE_STATUS_INPUT (1 << 14) +#define PMBUS_HAVE_STATUS_TEMP (1 << 15) +#define PMBUS_HAVE_STATUS_FAN12 (1 << 16) +#define PMBUS_HAVE_STATUS_FAN34 (1 << 17) + +struct pmbus_driver_info { + int pages; /* Total number of pages */ + bool direct[PSC_NUM_CLASSES]; + /* true if device uses direct data format + for the given sensor class */ + /* + * Support one set of coefficients for each sensor type + * Used for chips providing data in direct mode. + */ + int m[PSC_NUM_CLASSES]; /* mantissa for direct data format */ + int b[PSC_NUM_CLASSES]; /* offset */ + int R[PSC_NUM_CLASSES]; /* exponent */ + + u32 func[PMBUS_PAGES]; /* Functionality, per page */ + /* + * The get_status function maps manufacturing specific status values + * into PMBus standard status values. + * This function is optional and only necessary if chip specific status + * register values have to be mapped into standard PMBus status register + * values. + */ + int (*get_status)(struct i2c_client *client, int page, int reg); + /* + * The identify function determines supported PMBus functionality. + * This function is only necessary if a chip driver supports multiple + * chips, and the chip functionality is not pre-determined. + */ + int (*identify)(struct i2c_client *client, + struct pmbus_driver_info *info); +}; + +/* Function declarations */ + +int pmbus_set_page(struct i2c_client *client, u8 page); +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg); +void pmbus_clear_faults(struct i2c_client *client); +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg); +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg); +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info); +int pmbus_do_remove(struct i2c_client *client); +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client + *client); + +#endif /* PMBUS_H */ diff --git a/drivers/hwmon/pmbus_core.c b/drivers/hwmon/pmbus_core.c new file mode 100644 index 000000000000..0edab1c9553e --- /dev/null +++ b/drivers/hwmon/pmbus_core.c @@ -0,0 +1,1628 @@ +/* + * Hardware monitoring driver for PMBus devices + * + * Copyright (c) 2010, 2011 Ericsson AB. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/i2c.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/delay.h> +#include <linux/i2c/pmbus.h> +#include "pmbus.h" + +/* + * Constants needed to determine number of sensors, booleans, and labels. + */ +#define PMBUS_MAX_INPUT_SENSORS 11 /* 6*volt, 3*curr, 2*power */ +#define PMBUS_VOUT_SENSORS_PER_PAGE 5 /* input, min, max, lcrit, + crit */ +#define PMBUS_IOUT_SENSORS_PER_PAGE 4 /* input, min, max, crit */ +#define PMBUS_POUT_SENSORS_PER_PAGE 4 /* input, cap, max, crit */ +#define PMBUS_MAX_SENSORS_PER_FAN 1 /* input */ +#define PMBUS_MAX_SENSORS_PER_TEMP 5 /* input, min, max, lcrit, + crit */ + +#define PMBUS_MAX_INPUT_BOOLEANS 7 /* v: min_alarm, max_alarm, + lcrit_alarm, crit_alarm; + c: alarm, crit_alarm; + p: crit_alarm */ +#define PMBUS_VOUT_BOOLEANS_PER_PAGE 4 /* min_alarm, max_alarm, + lcrit_alarm, crit_alarm */ +#define PMBUS_IOUT_BOOLEANS_PER_PAGE 3 /* alarm, lcrit_alarm, + crit_alarm */ +#define PMBUS_POUT_BOOLEANS_PER_PAGE 2 /* alarm, crit_alarm */ +#define PMBUS_MAX_BOOLEANS_PER_FAN 2 /* alarm, fault */ +#define PMBUS_MAX_BOOLEANS_PER_TEMP 4 /* min_alarm, max_alarm, + lcrit_alarm, crit_alarm */ + +#define PMBUS_MAX_INPUT_LABELS 4 /* vin, vcap, iin, pin */ + +/* + * status, status_vout, status_iout, status_fans, and status_temp + * are paged. status_input and status_fan34 are unpaged. + * status_fan34 is a special case to handle a second set of fans + * on page 0. + */ +#define PB_NUM_STATUS_REG (PMBUS_PAGES * 5 + 2) + +/* + * Index into status register array, per status register group + */ +#define PB_STATUS_BASE 0 +#define PB_STATUS_VOUT_BASE (PB_STATUS_BASE + PMBUS_PAGES) +#define PB_STATUS_IOUT_BASE (PB_STATUS_VOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN_BASE (PB_STATUS_IOUT_BASE + PMBUS_PAGES) +#define PB_STATUS_FAN34_BASE (PB_STATUS_FAN_BASE + PMBUS_PAGES) +#define PB_STATUS_INPUT_BASE (PB_STATUS_FAN34_BASE + 1) +#define PB_STATUS_TEMP_BASE (PB_STATUS_INPUT_BASE + 1) + +struct pmbus_sensor { + char name[I2C_NAME_SIZE]; /* sysfs sensor name */ + struct sensor_device_attribute attribute; + u8 page; /* page number */ + u8 reg; /* register */ + enum pmbus_sensor_classes class; /* sensor class */ + bool update; /* runtime sensor update needed */ + int data; /* Sensor data. + Negative if there was a read error */ +}; + +struct pmbus_boolean { + char name[I2C_NAME_SIZE]; /* sysfs boolean name */ + struct sensor_device_attribute attribute; +}; + +struct pmbus_label { + char name[I2C_NAME_SIZE]; /* sysfs label name */ + struct sensor_device_attribute attribute; + char label[I2C_NAME_SIZE]; /* label */ +}; + +struct pmbus_data { + struct device *hwmon_dev; + + u32 flags; /* from platform data */ + + int exponent; /* linear mode: exponent for output voltages */ + + const struct pmbus_driver_info *info; + + int max_attributes; + int num_attributes; + struct attribute **attributes; + struct attribute_group group; + + /* + * Sensors cover both sensor and limit registers. + */ + int max_sensors; + int num_sensors; + struct pmbus_sensor *sensors; + /* + * Booleans are used for alarms. + * Values are determined from status registers. + */ + int max_booleans; + int num_booleans; + struct pmbus_boolean *booleans; + /* + * Labels are used to map generic names (e.g., "in1") + * to PMBus specific names (e.g., "vin" or "vout1"). + */ + int max_labels; + int num_labels; + struct pmbus_label *labels; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* + * A single status register covers multiple attributes, + * so we keep them all together. + */ + u8 status_bits; + u8 status[PB_NUM_STATUS_REG]; + + u8 currpage; +}; + +int pmbus_set_page(struct i2c_client *client, u8 page) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int rv = 0; + int newpage; + + if (page != data->currpage) { + rv = i2c_smbus_write_byte_data(client, PMBUS_PAGE, page); + newpage = i2c_smbus_read_byte_data(client, PMBUS_PAGE); + if (newpage != page) + rv = -EINVAL; + else + data->currpage = page; + } + return rv; +} +EXPORT_SYMBOL_GPL(pmbus_set_page); + +static int pmbus_write_byte(struct i2c_client *client, u8 page, u8 value) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_write_byte(client, value); +} + +static int pmbus_write_word_data(struct i2c_client *client, u8 page, u8 reg, + u16 word) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_write_word_data(client, reg, word); +} + +int pmbus_read_word_data(struct i2c_client *client, u8 page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_word_data(client, reg); +} +EXPORT_SYMBOL_GPL(pmbus_read_word_data); + +static int pmbus_read_byte_data(struct i2c_client *client, u8 page, u8 reg) +{ + int rv; + + rv = pmbus_set_page(client, page); + if (rv < 0) + return rv; + + return i2c_smbus_read_byte_data(client, reg); +} + +static void pmbus_clear_fault_page(struct i2c_client *client, int page) +{ + pmbus_write_byte(client, page, PMBUS_CLEAR_FAULTS); +} + +void pmbus_clear_faults(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + int i; + + for (i = 0; i < data->info->pages; i++) + pmbus_clear_fault_page(client, i); +} +EXPORT_SYMBOL_GPL(pmbus_clear_faults); + +static int pmbus_check_status_cml(struct i2c_client *client, int page) +{ + int status, status2; + + status = pmbus_read_byte_data(client, page, PMBUS_STATUS_BYTE); + if (status < 0 || (status & PB_STATUS_CML)) { + status2 = pmbus_read_byte_data(client, page, PMBUS_STATUS_CML); + if (status2 < 0 || (status2 & PB_CML_FAULT_INVALID_COMMAND)) + return -EINVAL; + } + return 0; +} + +bool pmbus_check_byte_register(struct i2c_client *client, int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + + rv = pmbus_read_byte_data(client, page, reg); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client, page); + pmbus_clear_fault_page(client, page); + return rv >= 0; +} +EXPORT_SYMBOL_GPL(pmbus_check_byte_register); + +bool pmbus_check_word_register(struct i2c_client *client, int page, int reg) +{ + int rv; + struct pmbus_data *data = i2c_get_clientdata(client); + + rv = pmbus_read_word_data(client, page, reg); + if (rv >= 0 && !(data->flags & PMBUS_SKIP_STATUS_CHECK)) + rv = pmbus_check_status_cml(client, page); + pmbus_clear_fault_page(client, page); + return rv >= 0; +} +EXPORT_SYMBOL_GPL(pmbus_check_word_register); + +const struct pmbus_driver_info *pmbus_get_driver_info(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + + return data->info; +} +EXPORT_SYMBOL_GPL(pmbus_get_driver_info); + +static int pmbus_get_status(struct i2c_client *client, int page, int reg) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + int status; + + if (info->get_status) { + status = info->get_status(client, page, reg); + if (status != -ENODATA) + return status; + } + return pmbus_read_byte_data(client, page, reg); +} + +static struct pmbus_data *pmbus_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + const struct pmbus_driver_info *info = data->info; + + mutex_lock(&data->update_lock); + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + int i; + + for (i = 0; i < info->pages; i++) + data->status[PB_STATUS_BASE + i] + = pmbus_read_byte_data(client, i, + PMBUS_STATUS_BYTE); + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_VOUT)) + continue; + data->status[PB_STATUS_VOUT_BASE + i] + = pmbus_get_status(client, i, PMBUS_STATUS_VOUT); + } + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_IOUT)) + continue; + data->status[PB_STATUS_IOUT_BASE + i] + = pmbus_get_status(client, i, PMBUS_STATUS_IOUT); + } + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_TEMP)) + continue; + data->status[PB_STATUS_TEMP_BASE + i] + = pmbus_get_status(client, i, + PMBUS_STATUS_TEMPERATURE); + } + for (i = 0; i < info->pages; i++) { + if (!(info->func[i] & PMBUS_HAVE_STATUS_FAN12)) + continue; + data->status[PB_STATUS_FAN_BASE + i] + = pmbus_get_status(client, i, PMBUS_STATUS_FAN_12); + } + + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + data->status[PB_STATUS_INPUT_BASE] + = pmbus_get_status(client, 0, PMBUS_STATUS_INPUT); + + if (info->func[0] & PMBUS_HAVE_STATUS_FAN34) + data->status[PB_STATUS_FAN34_BASE] + = pmbus_get_status(client, 0, PMBUS_STATUS_FAN_34); + + for (i = 0; i < data->num_sensors; i++) { + struct pmbus_sensor *sensor = &data->sensors[i]; + + if (!data->valid || sensor->update) + sensor->data + = pmbus_read_word_data(client, sensor->page, + sensor->reg); + } + pmbus_clear_faults(client); + data->last_updated = jiffies; + data->valid = 1; + } + mutex_unlock(&data->update_lock); + return data; +} + +/* + * Convert linear sensor values to milli- or micro-units + * depending on sensor type. + */ +static int pmbus_reg2data_linear(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + s16 exponent, mantissa; + long val; + + if (sensor->class == PSC_VOLTAGE_OUT) { + exponent = data->exponent; + mantissa = (s16) sensor->data; + } else { + exponent = (sensor->data >> 11) & 0x001f; + mantissa = sensor->data & 0x07ff; + + if (exponent > 0x0f) + exponent |= 0xffe0; /* sign extend exponent */ + if (mantissa > 0x03ff) + mantissa |= 0xf800; /* sign extend mantissa */ + } + + val = mantissa; + + /* scale result to milli-units for all sensors except fans */ + if (sensor->class != PSC_FAN) + val = val * 1000L; + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) + val = val * 1000L; + + if (exponent >= 0) + val <<= exponent; + else + val >>= -exponent; + + return (int)val; +} + +/* + * Convert direct sensor values to milli- or micro-units + * depending on sensor type. + */ +static int pmbus_reg2data_direct(struct pmbus_data *data, + struct pmbus_sensor *sensor) +{ + long val = (s16) sensor->data; + long m, b, R; + + m = data->info->m[sensor->class]; + b = data->info->b[sensor->class]; + R = data->info->R[sensor->class]; + + if (m == 0) + return 0; + + /* X = 1/m * (Y * 10^-R - b) */ + R = -R; + /* scale result to milli-units for everything but fans */ + if (sensor->class != PSC_FAN) { + R += 3; + b *= 1000; + } + + /* scale result to micro-units for power sensors */ + if (sensor->class == PSC_POWER) { + R += 3; + b *= 1000; + } + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = DIV_ROUND_CLOSEST(val, 10); + R++; + } + + return (int)((val - b) / m); +} + +static int pmbus_reg2data(struct pmbus_data *data, struct pmbus_sensor *sensor) +{ + int val; + + if (data->info->direct[sensor->class]) + val = pmbus_reg2data_direct(data, sensor); + else + val = pmbus_reg2data_linear(data, sensor); + + return val; +} + +#define MAX_MANTISSA (1023 * 1000) +#define MIN_MANTISSA (511 * 1000) + +static u16 pmbus_data2reg_linear(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + s16 exponent = 0, mantissa = 0; + bool negative = false; + + /* simple case */ + if (val == 0) + return 0; + + if (val < 0) { + negative = true; + val = -val; + } + + if (class == PSC_VOLTAGE_OUT) { + /* + * For a static exponents, we don't have a choice + * but to adjust the value to it. + */ + if (data->exponent < 0) + val <<= -data->exponent; + else + val >>= data->exponent; + val = DIV_ROUND_CLOSEST(val, 1000); + if (val > 0x7fff) + val = 0x7fff; + return negative ? -val : val; + } + + /* Power is in uW. Convert to mW before converting. */ + if (class == PSC_POWER) + val = DIV_ROUND_CLOSEST(val, 1000L); + + /* + * For simplicity, convert fan data to milli-units + * before calculating the exponent. + */ + if (class == PSC_FAN) + val = val * 1000; + + /* Reduce large mantissa until it fits into 10 bit */ + while (val >= MAX_MANTISSA && exponent < 15) { + exponent++; + val >>= 1; + } + /* Increase small mantissa to improve precision */ + while (val < MIN_MANTISSA && exponent > -15) { + exponent--; + val <<= 1; + } + + /* Convert mantissa from milli-units to units */ + mantissa = DIV_ROUND_CLOSEST(val, 1000); + + /* Ensure that resulting number is within range */ + if (mantissa > 0x3ff) + mantissa = 0x3ff; + + /* restore sign */ + if (negative) + mantissa = -mantissa; + + /* Convert to 5 bit exponent, 11 bit mantissa */ + return (mantissa & 0x7ff) | ((exponent << 11) & 0xf800); +} + +static u16 pmbus_data2reg_direct(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + long m, b, R; + + m = data->info->m[class]; + b = data->info->b[class]; + R = data->info->R[class]; + + /* Power is in uW. Adjust R and b. */ + if (class == PSC_POWER) { + R -= 3; + b *= 1000; + } + + /* Calculate Y = (m * X + b) * 10^R */ + if (class != PSC_FAN) { + R -= 3; /* Adjust R and b for data in milli-units */ + b *= 1000; + } + val = val * m + b; + + while (R > 0) { + val *= 10; + R--; + } + while (R < 0) { + val = DIV_ROUND_CLOSEST(val, 10); + R++; + } + + return val; +} + +static u16 pmbus_data2reg(struct pmbus_data *data, + enum pmbus_sensor_classes class, long val) +{ + u16 regval; + + if (data->info->direct[class]) + regval = pmbus_data2reg_direct(data, class, val); + else + regval = pmbus_data2reg_linear(data, class, val); + + return regval; +} + +/* + * Return boolean calculated from converted data. + * <index> defines a status register index and mask, and optionally + * two sensor indexes. + * The upper half-word references the two sensors, + * two sensor indices. + * The upper half-word references the two optional sensors, + * the lower half word references status register and mask. + * The function returns true if (status[reg] & mask) is true and, + * if specified, if v1 >= v2. + * To determine if an object exceeds upper limits, specify <v, limit>. + * To determine if an object exceeds lower limits, specify <limit, v>. + * + * For booleans created with pmbus_add_boolean_reg(), only the lower 16 bits of + * index are set. s1 and s2 (the sensor index values) are zero in this case. + * The function returns true if (status[reg] & mask) is true. + * + * If the boolean was created with pmbus_add_boolean_cmp(), a comparison against + * a specified limit has to be performed to determine the boolean result. + * In this case, the function returns true if v1 >= v2 (where v1 and v2 are + * sensor values referenced by sensor indices s1 and s2). + * + * To determine if an object exceeds upper limits, specify <s1,s2> = <v,limit>. + * To determine if an object exceeds lower limits, specify <s1,s2> = <limit,v>. + * + * If a negative value is stored in any of the referenced registers, this value + * reflects an error code which will be returned. + */ +static int pmbus_get_boolean(struct pmbus_data *data, int index, int *val) +{ + u8 s1 = (index >> 24) & 0xff; + u8 s2 = (index >> 16) & 0xff; + u8 reg = (index >> 8) & 0xff; + u8 mask = index & 0xff; + int status; + u8 regval; + + status = data->status[reg]; + if (status < 0) + return status; + + regval = status & mask; + if (!s1 && !s2) + *val = !!regval; + else { + int v1, v2; + struct pmbus_sensor *sensor1, *sensor2; + + sensor1 = &data->sensors[s1]; + if (sensor1->data < 0) + return sensor1->data; + sensor2 = &data->sensors[s2]; + if (sensor2->data < 0) + return sensor2->data; + + v1 = pmbus_reg2data(data, sensor1); + v2 = pmbus_reg2data(data, sensor2); + *val = !!(regval && v1 >= v2); + } + return 0; +} + +static ssize_t pmbus_show_boolean(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_data *data = pmbus_update_device(dev); + int val; + int err; + + err = pmbus_get_boolean(data, attr->index, &val); + if (err) + return err; + return snprintf(buf, PAGE_SIZE, "%d\n", val); +} + +static ssize_t pmbus_show_sensor(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + struct pmbus_data *data = pmbus_update_device(dev); + struct pmbus_sensor *sensor; + + sensor = &data->sensors[attr->index]; + if (sensor->data < 0) + return sensor->data; + + return snprintf(buf, PAGE_SIZE, "%d\n", pmbus_reg2data(data, sensor)); +} + +static ssize_t pmbus_set_sensor(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + struct pmbus_sensor *sensor = &data->sensors[attr->index]; + ssize_t rv = count; + long val = 0; + int ret; + u16 regval; + + if (strict_strtol(buf, 10, &val) < 0) + return -EINVAL; + + mutex_lock(&data->update_lock); + regval = pmbus_data2reg(data, sensor->class, val); + ret = pmbus_write_word_data(client, sensor->page, sensor->reg, regval); + if (ret < 0) + rv = ret; + else + data->sensors[attr->index].data = regval; + mutex_unlock(&data->update_lock); + return rv; +} + +static ssize_t pmbus_show_label(struct device *dev, + struct device_attribute *da, char *buf) +{ + struct i2c_client *client = to_i2c_client(dev); + struct pmbus_data *data = i2c_get_clientdata(client); + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + + return snprintf(buf, PAGE_SIZE, "%s\n", + data->labels[attr->index].label); +} + +#define PMBUS_ADD_ATTR(data, _name, _idx, _mode, _type, _show, _set) \ +do { \ + struct sensor_device_attribute *a \ + = &data->_type##s[data->num_##_type##s].attribute; \ + BUG_ON(data->num_attributes >= data->max_attributes); \ + a->dev_attr.attr.name = _name; \ + a->dev_attr.attr.mode = _mode; \ + a->dev_attr.show = _show; \ + a->dev_attr.store = _set; \ + a->index = _idx; \ + data->attributes[data->num_attributes] = &a->dev_attr.attr; \ + data->num_attributes++; \ +} while (0) + +#define PMBUS_ADD_GET_ATTR(data, _name, _type, _idx) \ + PMBUS_ADD_ATTR(data, _name, _idx, S_IRUGO, _type, \ + pmbus_show_##_type, NULL) + +#define PMBUS_ADD_SET_ATTR(data, _name, _type, _idx) \ + PMBUS_ADD_ATTR(data, _name, _idx, S_IWUSR | S_IRUGO, _type, \ + pmbus_show_##_type, pmbus_set_##_type) + +static void pmbus_add_boolean(struct pmbus_data *data, + const char *name, const char *type, int seq, + int idx) +{ + struct pmbus_boolean *boolean; + + BUG_ON(data->num_booleans >= data->max_booleans); + + boolean = &data->booleans[data->num_booleans]; + + snprintf(boolean->name, sizeof(boolean->name), "%s%d_%s", + name, seq, type); + PMBUS_ADD_GET_ATTR(data, boolean->name, boolean, idx); + data->num_booleans++; +} + +static void pmbus_add_boolean_reg(struct pmbus_data *data, + const char *name, const char *type, + int seq, int reg, int bit) +{ + pmbus_add_boolean(data, name, type, seq, (reg << 8) | bit); +} + +static void pmbus_add_boolean_cmp(struct pmbus_data *data, + const char *name, const char *type, + int seq, int i1, int i2, int reg, int mask) +{ + pmbus_add_boolean(data, name, type, seq, + (i1 << 24) | (i2 << 16) | (reg << 8) | mask); +} + +static void pmbus_add_sensor(struct pmbus_data *data, + const char *name, const char *type, int seq, + int page, int reg, enum pmbus_sensor_classes class, + bool update) +{ + struct pmbus_sensor *sensor; + + BUG_ON(data->num_sensors >= data->max_sensors); + + sensor = &data->sensors[data->num_sensors]; + snprintf(sensor->name, sizeof(sensor->name), "%s%d_%s", + name, seq, type); + sensor->page = page; + sensor->reg = reg; + sensor->class = class; + sensor->update = update; + if (update) + PMBUS_ADD_GET_ATTR(data, sensor->name, sensor, + data->num_sensors); + else + PMBUS_ADD_SET_ATTR(data, sensor->name, sensor, + data->num_sensors); + data->num_sensors++; +} + +static void pmbus_add_label(struct pmbus_data *data, + const char *name, int seq, + const char *lstring, int index) +{ + struct pmbus_label *label; + + BUG_ON(data->num_labels >= data->max_labels); + + label = &data->labels[data->num_labels]; + snprintf(label->name, sizeof(label->name), "%s%d_label", name, seq); + if (!index) + strncpy(label->label, lstring, sizeof(label->label) - 1); + else + snprintf(label->label, sizeof(label->label), "%s%d", lstring, + index); + + PMBUS_ADD_GET_ATTR(data, label->name, label, data->num_labels); + data->num_labels++; +} + +static const int pmbus_temp_registers[] = { + PMBUS_READ_TEMPERATURE_1, + PMBUS_READ_TEMPERATURE_2, + PMBUS_READ_TEMPERATURE_3 +}; + +static const int pmbus_fan_registers[] = { + PMBUS_READ_FAN_SPEED_1, + PMBUS_READ_FAN_SPEED_2, + PMBUS_READ_FAN_SPEED_3, + PMBUS_READ_FAN_SPEED_4 +}; + +static const int pmbus_fan_config_registers[] = { + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_12, + PMBUS_FAN_CONFIG_34, + PMBUS_FAN_CONFIG_34 +}; + +static const int pmbus_fan_status_registers[] = { + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_12, + PMBUS_STATUS_FAN_34, + PMBUS_STATUS_FAN_34 +}; + +/* + * Determine maximum number of sensors, booleans, and labels. + * To keep things simple, only make a rough high estimate. + */ +static void pmbus_find_max_attr(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int page, max_sensors, max_booleans, max_labels; + + max_sensors = PMBUS_MAX_INPUT_SENSORS; + max_booleans = PMBUS_MAX_INPUT_BOOLEANS; + max_labels = PMBUS_MAX_INPUT_LABELS; + + for (page = 0; page < info->pages; page++) { + if (info->func[page] & PMBUS_HAVE_VOUT) { + max_sensors += PMBUS_VOUT_SENSORS_PER_PAGE; + max_booleans += PMBUS_VOUT_BOOLEANS_PER_PAGE; + max_labels++; + } + if (info->func[page] & PMBUS_HAVE_IOUT) { + max_sensors += PMBUS_IOUT_SENSORS_PER_PAGE; + max_booleans += PMBUS_IOUT_BOOLEANS_PER_PAGE; + max_labels++; + } + if (info->func[page] & PMBUS_HAVE_POUT) { + max_sensors += PMBUS_POUT_SENSORS_PER_PAGE; + max_booleans += PMBUS_POUT_BOOLEANS_PER_PAGE; + max_labels++; + } + if (info->func[page] & PMBUS_HAVE_FAN12) { + if (page == 0) { + max_sensors += + ARRAY_SIZE(pmbus_fan_registers) * + PMBUS_MAX_SENSORS_PER_FAN; + max_booleans += + ARRAY_SIZE(pmbus_fan_registers) * + PMBUS_MAX_BOOLEANS_PER_FAN; + } else { + max_sensors += PMBUS_MAX_SENSORS_PER_FAN; + max_booleans += PMBUS_MAX_BOOLEANS_PER_FAN; + } + } + if (info->func[page] & PMBUS_HAVE_TEMP) { + if (page == 0) { + max_sensors += + ARRAY_SIZE(pmbus_temp_registers) * + PMBUS_MAX_SENSORS_PER_TEMP; + max_booleans += + ARRAY_SIZE(pmbus_temp_registers) * + PMBUS_MAX_BOOLEANS_PER_TEMP; + } else { + max_sensors += PMBUS_MAX_SENSORS_PER_TEMP; + max_booleans += PMBUS_MAX_BOOLEANS_PER_TEMP; + } + } + } + data->max_sensors = max_sensors; + data->max_booleans = max_booleans; + data->max_labels = max_labels; + data->max_attributes = max_sensors + max_booleans + max_labels; +} + +/* + * Search for attributes. Allocate sensors, booleans, and labels as needed. + */ +static void pmbus_find_attributes(struct i2c_client *client, + struct pmbus_data *data) +{ + const struct pmbus_driver_info *info = data->info; + int page, i0, i1, in_index; + + /* + * Input voltage sensors + */ + in_index = 1; + if (info->func[0] & PMBUS_HAVE_VIN) { + bool have_alarm = false; + + i0 = data->num_sensors; + pmbus_add_label(data, "in", in_index, "vin", 0); + pmbus_add_sensor(data, "in", "input", in_index, + 0, PMBUS_READ_VIN, PSC_VOLTAGE_IN, true); + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_UV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "min", in_index, + 0, PMBUS_VIN_UV_WARN_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "min_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_UV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_UV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "lcrit", in_index, + 0, PMBUS_VIN_UV_FAULT_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "lcrit_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_UV_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_OV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "max", in_index, + 0, PMBUS_VIN_OV_WARN_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "max_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_OV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_VIN_OV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "crit", in_index, + 0, PMBUS_VIN_OV_FAULT_LIMIT, + PSC_VOLTAGE_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "in", "crit_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_VOLTAGE_OV_FAULT); + have_alarm = true; + } + } + /* + * Add generic alarm attribute only if there are no individual + * attributes. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "in", "alarm", + in_index, + PB_STATUS_BASE, + PB_STATUS_VIN_UV); + in_index++; + } + if (info->func[0] & PMBUS_HAVE_VCAP) { + pmbus_add_label(data, "in", in_index, "vcap", 0); + pmbus_add_sensor(data, "in", "input", in_index, 0, + PMBUS_READ_VCAP, PSC_VOLTAGE_IN, true); + in_index++; + } + + /* + * Output voltage sensors + */ + for (page = 0; page < info->pages; page++) { + bool have_alarm = false; + + if (!(info->func[page] & PMBUS_HAVE_VOUT)) + continue; + + i0 = data->num_sensors; + pmbus_add_label(data, "in", in_index, "vout", page + 1); + pmbus_add_sensor(data, "in", "input", in_index, page, + PMBUS_READ_VOUT, PSC_VOLTAGE_OUT, true); + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_UV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "min", in_index, page, + PMBUS_VOUT_UV_WARN_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "min_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_UV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_UV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "lcrit", in_index, page, + PMBUS_VOUT_UV_FAULT_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "lcrit_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_UV_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_OV_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "max", in_index, page, + PMBUS_VOUT_OV_WARN_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "max_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_OV_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_VOUT_OV_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "in", "crit", in_index, page, + PMBUS_VOUT_OV_FAULT_LIMIT, + PSC_VOLTAGE_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_VOUT) { + pmbus_add_boolean_reg(data, "in", "crit_alarm", + in_index, + PB_STATUS_VOUT_BASE + + page, + PB_VOLTAGE_OV_FAULT); + have_alarm = true; + } + } + /* + * Add generic alarm attribute only if there are no individual + * attributes. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "in", "alarm", + in_index, + PB_STATUS_BASE + page, + PB_STATUS_VOUT_OV); + in_index++; + } + + /* + * Current sensors + */ + + /* + * Input current sensors + */ + in_index = 1; + if (info->func[0] & PMBUS_HAVE_IIN) { + i0 = data->num_sensors; + pmbus_add_label(data, "curr", in_index, "iin", 0); + pmbus_add_sensor(data, "curr", "input", in_index, + 0, PMBUS_READ_IIN, PSC_CURRENT_IN, true); + if (pmbus_check_word_register(client, 0, + PMBUS_IIN_OC_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "max", in_index, + 0, PMBUS_IIN_OC_WARN_LIMIT, + PSC_CURRENT_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) { + pmbus_add_boolean_reg(data, "curr", "max_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_IIN_OC_WARNING); + } + } + if (pmbus_check_word_register(client, 0, + PMBUS_IIN_OC_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "crit", in_index, + 0, PMBUS_IIN_OC_FAULT_LIMIT, + PSC_CURRENT_IN, false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + pmbus_add_boolean_reg(data, "curr", + "crit_alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_IIN_OC_FAULT); + } + in_index++; + } + + /* + * Output current sensors + */ + for (page = 0; page < info->pages; page++) { + bool have_alarm = false; + + if (!(info->func[page] & PMBUS_HAVE_IOUT)) + continue; + + i0 = data->num_sensors; + pmbus_add_label(data, "curr", in_index, "iout", page + 1); + pmbus_add_sensor(data, "curr", "input", in_index, page, + PMBUS_READ_IOUT, PSC_CURRENT_OUT, true); + if (pmbus_check_word_register(client, page, + PMBUS_IOUT_OC_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "max", in_index, page, + PMBUS_IOUT_OC_WARN_LIMIT, + PSC_CURRENT_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) { + pmbus_add_boolean_reg(data, "curr", "max_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, PB_IOUT_OC_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_IOUT_UC_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "lcrit", in_index, page, + PMBUS_IOUT_UC_FAULT_LIMIT, + PSC_CURRENT_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) { + pmbus_add_boolean_reg(data, "curr", + "lcrit_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, PB_IOUT_UC_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_IOUT_OC_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "curr", "crit", in_index, page, + PMBUS_IOUT_OC_FAULT_LIMIT, + PSC_CURRENT_OUT, false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) { + pmbus_add_boolean_reg(data, "curr", + "crit_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, PB_IOUT_OC_FAULT); + have_alarm = true; + } + } + /* + * Add generic alarm attribute only if there are no individual + * attributes. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "curr", "alarm", + in_index, + PB_STATUS_BASE + page, + PB_STATUS_IOUT_OC); + in_index++; + } + + /* + * Power sensors + */ + /* + * Input Power sensors + */ + in_index = 1; + if (info->func[0] & PMBUS_HAVE_PIN) { + i0 = data->num_sensors; + pmbus_add_label(data, "power", in_index, "pin", 0); + pmbus_add_sensor(data, "power", "input", in_index, + 0, PMBUS_READ_PIN, PSC_POWER, true); + if (pmbus_check_word_register(client, 0, + PMBUS_PIN_OP_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "max", in_index, + 0, PMBUS_PIN_OP_WARN_LIMIT, PSC_POWER, + false); + if (info->func[0] & PMBUS_HAVE_STATUS_INPUT) + pmbus_add_boolean_reg(data, "power", + "alarm", + in_index, + PB_STATUS_INPUT_BASE, + PB_PIN_OP_WARNING); + } + in_index++; + } + + /* + * Output Power sensors + */ + for (page = 0; page < info->pages; page++) { + bool need_alarm = false; + + if (!(info->func[page] & PMBUS_HAVE_POUT)) + continue; + + i0 = data->num_sensors; + pmbus_add_label(data, "power", in_index, "pout", page + 1); + pmbus_add_sensor(data, "power", "input", in_index, page, + PMBUS_READ_POUT, PSC_POWER, true); + /* + * Per hwmon sysfs API, power_cap is to be used to limit output + * power. + * We have two registers related to maximum output power, + * PMBUS_POUT_MAX and PMBUS_POUT_OP_WARN_LIMIT. + * PMBUS_POUT_MAX matches the powerX_cap attribute definition. + * There is no attribute in the API to match + * PMBUS_POUT_OP_WARN_LIMIT. We use powerX_max for now. + */ + if (pmbus_check_word_register(client, page, PMBUS_POUT_MAX)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "cap", in_index, page, + PMBUS_POUT_MAX, PSC_POWER, false); + need_alarm = true; + } + if (pmbus_check_word_register(client, page, + PMBUS_POUT_OP_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "max", in_index, page, + PMBUS_POUT_OP_WARN_LIMIT, PSC_POWER, + false); + need_alarm = true; + } + if (need_alarm && (info->func[page] & PMBUS_HAVE_STATUS_IOUT)) + pmbus_add_boolean_reg(data, "power", "alarm", + in_index, + PB_STATUS_IOUT_BASE + page, + PB_POUT_OP_WARNING + | PB_POWER_LIMITING); + + if (pmbus_check_word_register(client, page, + PMBUS_POUT_OP_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "power", "crit", in_index, page, + PMBUS_POUT_OP_FAULT_LIMIT, PSC_POWER, + false); + if (info->func[page] & PMBUS_HAVE_STATUS_IOUT) + pmbus_add_boolean_reg(data, "power", + "crit_alarm", + in_index, + PB_STATUS_IOUT_BASE + + page, + PB_POUT_OP_FAULT); + } + in_index++; + } + + /* + * Temperature sensors + */ + in_index = 1; + for (page = 0; page < info->pages; page++) { + int t, temps; + + if (!(info->func[page] & PMBUS_HAVE_TEMP)) + continue; + + temps = page ? 1 : ARRAY_SIZE(pmbus_temp_registers); + for (t = 0; t < temps; t++) { + bool have_alarm = false; + + if (!pmbus_check_word_register + (client, page, pmbus_temp_registers[t])) + break; + + i0 = data->num_sensors; + pmbus_add_sensor(data, "temp", "input", in_index, page, + pmbus_temp_registers[t], + PSC_TEMPERATURE, true); + + /* + * PMBus provides only one status register for TEMP1-3. + * Thus, we can not use the status register to determine + * which of the three sensors actually caused an alarm. + * Always compare current temperature against the limit + * registers to determine alarm conditions for a + * specific sensor. + */ + if (pmbus_check_word_register + (client, page, PMBUS_UT_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "min", in_index, + page, PMBUS_UT_WARN_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "min_alarm", in_index, i1, i0, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_UT_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_UT_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "lcrit", + in_index, page, + PMBUS_UT_FAULT_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "lcrit_alarm", in_index, i1, i0, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_UT_FAULT); + have_alarm = true; + } + } + if (pmbus_check_word_register + (client, page, PMBUS_OT_WARN_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "max", in_index, + page, PMBUS_OT_WARN_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "max_alarm", in_index, i0, i1, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_OT_WARNING); + have_alarm = true; + } + } + if (pmbus_check_word_register(client, page, + PMBUS_OT_FAULT_LIMIT)) { + i1 = data->num_sensors; + pmbus_add_sensor(data, "temp", "crit", in_index, + page, PMBUS_OT_FAULT_LIMIT, + PSC_TEMPERATURE, false); + if (info->func[page] & PMBUS_HAVE_STATUS_TEMP) { + pmbus_add_boolean_cmp(data, "temp", + "crit_alarm", in_index, i0, i1, + PB_STATUS_TEMP_BASE + page, + PB_TEMP_OT_FAULT); + have_alarm = true; + } + } + /* + * Last resort - we were not able to create any alarm + * registers. Report alarm for all sensors using the + * status register temperature alarm bit. + */ + if (!have_alarm) + pmbus_add_boolean_reg(data, "temp", "alarm", + in_index, + PB_STATUS_BASE + page, + PB_STATUS_TEMPERATURE); + in_index++; + } + } + + /* + * Fans + */ + in_index = 1; + for (page = 0; page < info->pages; page++) { + int fans, f; + + if (!(info->func[page] & PMBUS_HAVE_FAN12)) + continue; + + fans = page ? 1 : ARRAY_SIZE(pmbus_fan_registers); + for (f = 0; f < fans; f++) { + int regval; + + if (!pmbus_check_word_register(client, page, + pmbus_fan_registers[f]) + || !pmbus_check_byte_register(client, page, + pmbus_fan_config_registers[f])) + break; + + /* + * Skip fan if not installed. + * Each fan configuration register covers multiple fans, + * so we have to do some magic. + */ + regval = pmbus_read_byte_data(client, page, + pmbus_fan_config_registers[f]); + if (regval < 0 || + (!(regval & (PB_FAN_1_INSTALLED >> ((f & 1) * 4))))) + continue; + + i0 = data->num_sensors; + pmbus_add_sensor(data, "fan", "input", in_index, page, + pmbus_fan_registers[f], PSC_FAN, true); + + /* + * Each fan status register covers multiple fans, + * so we have to do some magic. + */ + if (pmbus_check_byte_register + (client, page, pmbus_fan_status_registers[f])) { + int base; + + if (f > 1) /* fan 3, 4 */ + base = PB_STATUS_FAN34_BASE; + else + base = PB_STATUS_FAN_BASE + page; + pmbus_add_boolean_reg(data, "fan", "alarm", + in_index, base, + PB_FAN_FAN1_WARNING >> (f & 1)); + pmbus_add_boolean_reg(data, "fan", "fault", + in_index, base, + PB_FAN_FAN1_FAULT >> (f & 1)); + } + in_index++; + } + } +} + +/* + * Identify chip parameters. + * This function is called for all chips. + */ +static int pmbus_identify_common(struct i2c_client *client, + struct pmbus_data *data) +{ + int vout_mode, exponent; + + vout_mode = pmbus_read_byte_data(client, 0, PMBUS_VOUT_MODE); + if (vout_mode >= 0) { + /* + * Not all chips support the VOUT_MODE command, + * so a failure to read it is not an error. + */ + switch (vout_mode >> 5) { + case 0: /* linear mode */ + if (data->info->direct[PSC_VOLTAGE_OUT]) + return -ENODEV; + + exponent = vout_mode & 0x1f; + /* and sign-extend it */ + if (exponent & 0x10) + exponent |= ~0x1f; + data->exponent = exponent; + break; + case 2: /* direct mode */ + if (!data->info->direct[PSC_VOLTAGE_OUT]) + return -ENODEV; + break; + default: + return -ENODEV; + } + } + + /* Determine maximum number of sensors, booleans, and labels */ + pmbus_find_max_attr(client, data); + pmbus_clear_fault_page(client, 0); + return 0; +} + +int pmbus_do_probe(struct i2c_client *client, const struct i2c_device_id *id, + struct pmbus_driver_info *info) +{ + const struct pmbus_platform_data *pdata = client->dev.platform_data; + struct pmbus_data *data; + int ret; + + if (!info) { + dev_err(&client->dev, "Missing chip information"); + return -ENODEV; + } + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_WRITE_BYTE + | I2C_FUNC_SMBUS_BYTE_DATA + | I2C_FUNC_SMBUS_WORD_DATA)) + return -ENODEV; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + dev_err(&client->dev, "No memory to allocate driver data\n"); + return -ENOMEM; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* + * Bail out if status register or PMBus revision register + * does not exist. + */ + if (i2c_smbus_read_byte_data(client, PMBUS_STATUS_BYTE) < 0 + || i2c_smbus_read_byte_data(client, PMBUS_REVISION) < 0) { + dev_err(&client->dev, + "Status or revision register not found\n"); + ret = -ENODEV; + goto out_data; + } + + if (pdata) + data->flags = pdata->flags; + data->info = info; + + pmbus_clear_faults(client); + + if (info->identify) { + ret = (*info->identify)(client, info); + if (ret < 0) { + dev_err(&client->dev, "Chip identification failed\n"); + goto out_data; + } + } + + if (info->pages <= 0 || info->pages > PMBUS_PAGES) { + dev_err(&client->dev, "Bad number of PMBus pages: %d\n", + info->pages); + ret = -EINVAL; + goto out_data; + } + /* + * Bail out if more than one page was configured, but we can not + * select the highest page. This is an indication that the wrong + * chip type was selected. Better bail out now than keep + * returning errors later on. + */ + if (info->pages > 1 && pmbus_set_page(client, info->pages - 1) < 0) { + dev_err(&client->dev, "Failed to select page %d\n", + info->pages - 1); + ret = -EINVAL; + goto out_data; + } + + ret = pmbus_identify_common(client, data); + if (ret < 0) { + dev_err(&client->dev, "Failed to identify chip capabilities\n"); + goto out_data; + } + + ret = -ENOMEM; + data->sensors = kzalloc(sizeof(struct pmbus_sensor) * data->max_sensors, + GFP_KERNEL); + if (!data->sensors) { + dev_err(&client->dev, "No memory to allocate sensor data\n"); + goto out_data; + } + + data->booleans = kzalloc(sizeof(struct pmbus_boolean) + * data->max_booleans, GFP_KERNEL); + if (!data->booleans) { + dev_err(&client->dev, "No memory to allocate boolean data\n"); + goto out_sensors; + } + + data->labels = kzalloc(sizeof(struct pmbus_label) * data->max_labels, + GFP_KERNEL); + if (!data->labels) { + dev_err(&client->dev, "No memory to allocate label data\n"); + goto out_booleans; + } + + data->attributes = kzalloc(sizeof(struct attribute *) + * data->max_attributes, GFP_KERNEL); + if (!data->attributes) { + dev_err(&client->dev, "No memory to allocate attribute data\n"); + goto out_labels; + } + + pmbus_find_attributes(client, data); + + /* + * If there are no attributes, something is wrong. + * Bail out instead of trying to register nothing. + */ + if (!data->num_attributes) { + dev_err(&client->dev, "No attributes found\n"); + ret = -ENODEV; + goto out_attributes; + } + + /* Register sysfs hooks */ + data->group.attrs = data->attributes; + ret = sysfs_create_group(&client->dev.kobj, &data->group); + if (ret) { + dev_err(&client->dev, "Failed to create sysfs entries\n"); + goto out_attributes; + } + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + dev_err(&client->dev, "Failed to register hwmon device\n"); + goto out_hwmon_device_register; + } + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, &data->group); +out_attributes: + kfree(data->attributes); +out_labels: + kfree(data->labels); +out_booleans: + kfree(data->booleans); +out_sensors: + kfree(data->sensors); +out_data: + kfree(data); + return ret; +} +EXPORT_SYMBOL_GPL(pmbus_do_probe); + +int pmbus_do_remove(struct i2c_client *client) +{ + struct pmbus_data *data = i2c_get_clientdata(client); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, &data->group); + kfree(data->attributes); + kfree(data->labels); + kfree(data->booleans); + kfree(data->sensors); + kfree(data); + return 0; +} +EXPORT_SYMBOL_GPL(pmbus_do_remove); + +MODULE_AUTHOR("Guenter Roeck"); +MODULE_DESCRIPTION("PMBus core driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/sht15.c b/drivers/hwmon/sht15.c index a610e7880fb3..3ba7dd82589a 100644 --- a/drivers/hwmon/sht15.c +++ b/drivers/hwmon/sht15.c @@ -610,7 +610,7 @@ static int __devexit sht15_remove(struct platform_device *pdev) struct sht15_data *data = platform_get_drvdata(pdev); /* Make sure any reads from the device are done and - * prevent new ones beginnning */ + * prevent new ones from beginning */ mutex_lock(&data->read_lock); hwmon_device_unregister(data->hwmon_dev); sysfs_remove_group(&pdev->dev.kobj, &sht15_attr_group); diff --git a/drivers/hwspinlock/Kconfig b/drivers/hwspinlock/Kconfig new file mode 100644 index 000000000000..eb4af28f8567 --- /dev/null +++ b/drivers/hwspinlock/Kconfig @@ -0,0 +1,22 @@ +# +# Generic HWSPINLOCK framework +# + +config HWSPINLOCK + tristate "Generic Hardware Spinlock framework" + help + Say y here to support the generic hardware spinlock framework. + You only need to enable this if you have hardware spinlock module + on your system (usually only relevant if your system has remote slave + coprocessors). + + If unsure, say N. + +config HWSPINLOCK_OMAP + tristate "OMAP Hardware Spinlock device" + depends on HWSPINLOCK && ARCH_OMAP4 + help + Say y here to support the OMAP Hardware Spinlock device (firstly + introduced in OMAP4). + + If unsure, say N. diff --git a/drivers/hwspinlock/Makefile b/drivers/hwspinlock/Makefile new file mode 100644 index 000000000000..5729a3f7ed3d --- /dev/null +++ b/drivers/hwspinlock/Makefile @@ -0,0 +1,6 @@ +# +# Generic Hardware Spinlock framework +# + +obj-$(CONFIG_HWSPINLOCK) += hwspinlock_core.o +obj-$(CONFIG_HWSPINLOCK_OMAP) += omap_hwspinlock.o diff --git a/drivers/hwspinlock/hwspinlock_core.c b/drivers/hwspinlock/hwspinlock_core.c new file mode 100644 index 000000000000..43a62714b4fb --- /dev/null +++ b/drivers/hwspinlock/hwspinlock_core.c @@ -0,0 +1,548 @@ +/* + * Hardware spinlock framework + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: Ohad Ben-Cohen <ohad@wizery.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * 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. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/spinlock.h> +#include <linux/types.h> +#include <linux/err.h> +#include <linux/jiffies.h> +#include <linux/radix-tree.h> +#include <linux/hwspinlock.h> +#include <linux/pm_runtime.h> + +#include "hwspinlock_internal.h" + +/* radix tree tags */ +#define HWSPINLOCK_UNUSED (0) /* tags an hwspinlock as unused */ + +/* + * A radix tree is used to maintain the available hwspinlock instances. + * The tree associates hwspinlock pointers with their integer key id, + * and provides easy-to-use API which makes the hwspinlock core code simple + * and easy to read. + * + * Radix trees are quick on lookups, and reasonably efficient in terms of + * storage, especially with high density usages such as this framework + * requires (a continuous range of integer keys, beginning with zero, is + * used as the ID's of the hwspinlock instances). + * + * The radix tree API supports tagging items in the tree, which this + * framework uses to mark unused hwspinlock instances (see the + * HWSPINLOCK_UNUSED tag above). As a result, the process of querying the + * tree, looking for an unused hwspinlock instance, is now reduced to a + * single radix tree API call. + */ +static RADIX_TREE(hwspinlock_tree, GFP_KERNEL); + +/* + * Synchronization of access to the tree is achieved using this spinlock, + * as the radix-tree API requires that users provide all synchronisation. + */ +static DEFINE_SPINLOCK(hwspinlock_tree_lock); + +/** + * __hwspin_trylock() - attempt to lock a specific hwspinlock + * @hwlock: an hwspinlock which we want to trylock + * @mode: controls whether local interrupts are disabled or not + * @flags: a pointer where the caller's interrupt state will be saved at (if + * requested) + * + * This function attempts to lock an hwspinlock, and will immediately + * fail if the hwspinlock is already taken. + * + * Upon a successful return from this function, preemption (and possibly + * interrupts) is disabled, so the caller must not sleep, and is advised to + * release the hwspinlock as soon as possible. This is required in order to + * minimize remote cores polling on the hardware interconnect. + * + * The user decides whether local interrupts are disabled or not, and if yes, + * whether he wants their previous state to be saved. It is up to the user + * to choose the appropriate @mode of operation, exactly the same way users + * should decide between spin_trylock, spin_trylock_irq and + * spin_trylock_irqsave. + * + * Returns 0 if we successfully locked the hwspinlock or -EBUSY if + * the hwspinlock was already taken. + * This function will never sleep. + */ +int __hwspin_trylock(struct hwspinlock *hwlock, int mode, unsigned long *flags) +{ + int ret; + + BUG_ON(!hwlock); + BUG_ON(!flags && mode == HWLOCK_IRQSTATE); + + /* + * This spin_lock{_irq, _irqsave} serves three purposes: + * + * 1. Disable preemption, in order to minimize the period of time + * in which the hwspinlock is taken. This is important in order + * to minimize the possible polling on the hardware interconnect + * by a remote user of this lock. + * 2. Make the hwspinlock SMP-safe (so we can take it from + * additional contexts on the local host). + * 3. Ensure that in_atomic/might_sleep checks catch potential + * problems with hwspinlock usage (e.g. scheduler checks like + * 'scheduling while atomic' etc.) + */ + if (mode == HWLOCK_IRQSTATE) + ret = spin_trylock_irqsave(&hwlock->lock, *flags); + else if (mode == HWLOCK_IRQ) + ret = spin_trylock_irq(&hwlock->lock); + else + ret = spin_trylock(&hwlock->lock); + + /* is lock already taken by another context on the local cpu ? */ + if (!ret) + return -EBUSY; + + /* try to take the hwspinlock device */ + ret = hwlock->ops->trylock(hwlock); + + /* if hwlock is already taken, undo spin_trylock_* and exit */ + if (!ret) { + if (mode == HWLOCK_IRQSTATE) + spin_unlock_irqrestore(&hwlock->lock, *flags); + else if (mode == HWLOCK_IRQ) + spin_unlock_irq(&hwlock->lock); + else + spin_unlock(&hwlock->lock); + + return -EBUSY; + } + + /* + * We can be sure the other core's memory operations + * are observable to us only _after_ we successfully take + * the hwspinlock, and we must make sure that subsequent memory + * operations (both reads and writes) will not be reordered before + * we actually took the hwspinlock. + * + * Note: the implicit memory barrier of the spinlock above is too + * early, so we need this additional explicit memory barrier. + */ + mb(); + + return 0; +} +EXPORT_SYMBOL_GPL(__hwspin_trylock); + +/** + * __hwspin_lock_timeout() - lock an hwspinlock with timeout limit + * @hwlock: the hwspinlock to be locked + * @timeout: timeout value in msecs + * @mode: mode which controls whether local interrupts are disabled or not + * @flags: a pointer to where the caller's interrupt state will be saved at (if + * requested) + * + * This function locks the given @hwlock. If the @hwlock + * is already taken, the function will busy loop waiting for it to + * be released, but give up after @timeout msecs have elapsed. + * + * Upon a successful return from this function, preemption is disabled + * (and possibly local interrupts, too), so the caller must not sleep, + * and is advised to release the hwspinlock as soon as possible. + * This is required in order to minimize remote cores polling on the + * hardware interconnect. + * + * The user decides whether local interrupts are disabled or not, and if yes, + * whether he wants their previous state to be saved. It is up to the user + * to choose the appropriate @mode of operation, exactly the same way users + * should decide between spin_lock, spin_lock_irq and spin_lock_irqsave. + * + * Returns 0 when the @hwlock was successfully taken, and an appropriate + * error code otherwise (most notably -ETIMEDOUT if the @hwlock is still + * busy after @timeout msecs). The function will never sleep. + */ +int __hwspin_lock_timeout(struct hwspinlock *hwlock, unsigned int to, + int mode, unsigned long *flags) +{ + int ret; + unsigned long expire; + + expire = msecs_to_jiffies(to) + jiffies; + + for (;;) { + /* Try to take the hwspinlock */ + ret = __hwspin_trylock(hwlock, mode, flags); + if (ret != -EBUSY) + break; + + /* + * The lock is already taken, let's check if the user wants + * us to try again + */ + if (time_is_before_eq_jiffies(expire)) + return -ETIMEDOUT; + + /* + * Allow platform-specific relax handlers to prevent + * hogging the interconnect (no sleeping, though) + */ + if (hwlock->ops->relax) + hwlock->ops->relax(hwlock); + } + + return ret; +} +EXPORT_SYMBOL_GPL(__hwspin_lock_timeout); + +/** + * __hwspin_unlock() - unlock a specific hwspinlock + * @hwlock: a previously-acquired hwspinlock which we want to unlock + * @mode: controls whether local interrupts needs to be restored or not + * @flags: previous caller's interrupt state to restore (if requested) + * + * This function will unlock a specific hwspinlock, enable preemption and + * (possibly) enable interrupts or restore their previous state. + * @hwlock must be already locked before calling this function: it is a bug + * to call unlock on a @hwlock that is already unlocked. + * + * The user decides whether local interrupts should be enabled or not, and + * if yes, whether he wants their previous state to be restored. It is up + * to the user to choose the appropriate @mode of operation, exactly the + * same way users decide between spin_unlock, spin_unlock_irq and + * spin_unlock_irqrestore. + * + * The function will never sleep. + */ +void __hwspin_unlock(struct hwspinlock *hwlock, int mode, unsigned long *flags) +{ + BUG_ON(!hwlock); + BUG_ON(!flags && mode == HWLOCK_IRQSTATE); + + /* + * We must make sure that memory operations (both reads and writes), + * done before unlocking the hwspinlock, will not be reordered + * after the lock is released. + * + * That's the purpose of this explicit memory barrier. + * + * Note: the memory barrier induced by the spin_unlock below is too + * late; the other core is going to access memory soon after it will + * take the hwspinlock, and by then we want to be sure our memory + * operations are already observable. + */ + mb(); + + hwlock->ops->unlock(hwlock); + + /* Undo the spin_trylock{_irq, _irqsave} called while locking */ + if (mode == HWLOCK_IRQSTATE) + spin_unlock_irqrestore(&hwlock->lock, *flags); + else if (mode == HWLOCK_IRQ) + spin_unlock_irq(&hwlock->lock); + else + spin_unlock(&hwlock->lock); +} +EXPORT_SYMBOL_GPL(__hwspin_unlock); + +/** + * hwspin_lock_register() - register a new hw spinlock + * @hwlock: hwspinlock to register. + * + * This function should be called from the underlying platform-specific + * implementation, to register a new hwspinlock instance. + * + * Can be called from an atomic context (will not sleep) but not from + * within interrupt context. + * + * Returns 0 on success, or an appropriate error code on failure + */ +int hwspin_lock_register(struct hwspinlock *hwlock) +{ + struct hwspinlock *tmp; + int ret; + + if (!hwlock || !hwlock->ops || + !hwlock->ops->trylock || !hwlock->ops->unlock) { + pr_err("invalid parameters\n"); + return -EINVAL; + } + + spin_lock_init(&hwlock->lock); + + spin_lock(&hwspinlock_tree_lock); + + ret = radix_tree_insert(&hwspinlock_tree, hwlock->id, hwlock); + if (ret) + goto out; + + /* mark this hwspinlock as available */ + tmp = radix_tree_tag_set(&hwspinlock_tree, hwlock->id, + HWSPINLOCK_UNUSED); + + /* self-sanity check which should never fail */ + WARN_ON(tmp != hwlock); + +out: + spin_unlock(&hwspinlock_tree_lock); + return ret; +} +EXPORT_SYMBOL_GPL(hwspin_lock_register); + +/** + * hwspin_lock_unregister() - unregister an hw spinlock + * @id: index of the specific hwspinlock to unregister + * + * This function should be called from the underlying platform-specific + * implementation, to unregister an existing (and unused) hwspinlock. + * + * Can be called from an atomic context (will not sleep) but not from + * within interrupt context. + * + * Returns the address of hwspinlock @id on success, or NULL on failure + */ +struct hwspinlock *hwspin_lock_unregister(unsigned int id) +{ + struct hwspinlock *hwlock = NULL; + int ret; + + spin_lock(&hwspinlock_tree_lock); + + /* make sure the hwspinlock is not in use (tag is set) */ + ret = radix_tree_tag_get(&hwspinlock_tree, id, HWSPINLOCK_UNUSED); + if (ret == 0) { + pr_err("hwspinlock %d still in use (or not present)\n", id); + goto out; + } + + hwlock = radix_tree_delete(&hwspinlock_tree, id); + if (!hwlock) { + pr_err("failed to delete hwspinlock %d\n", id); + goto out; + } + +out: + spin_unlock(&hwspinlock_tree_lock); + return hwlock; +} +EXPORT_SYMBOL_GPL(hwspin_lock_unregister); + +/** + * __hwspin_lock_request() - tag an hwspinlock as used and power it up + * + * This is an internal function that prepares an hwspinlock instance + * before it is given to the user. The function assumes that + * hwspinlock_tree_lock is taken. + * + * Returns 0 or positive to indicate success, and a negative value to + * indicate an error (with the appropriate error code) + */ +static int __hwspin_lock_request(struct hwspinlock *hwlock) +{ + struct hwspinlock *tmp; + int ret; + + /* prevent underlying implementation from being removed */ + if (!try_module_get(hwlock->owner)) { + dev_err(hwlock->dev, "%s: can't get owner\n", __func__); + return -EINVAL; + } + + /* notify PM core that power is now needed */ + ret = pm_runtime_get_sync(hwlock->dev); + if (ret < 0) { + dev_err(hwlock->dev, "%s: can't power on device\n", __func__); + return ret; + } + + /* mark hwspinlock as used, should not fail */ + tmp = radix_tree_tag_clear(&hwspinlock_tree, hwlock->id, + HWSPINLOCK_UNUSED); + + /* self-sanity check that should never fail */ + WARN_ON(tmp != hwlock); + + return ret; +} + +/** + * hwspin_lock_get_id() - retrieve id number of a given hwspinlock + * @hwlock: a valid hwspinlock instance + * + * Returns the id number of a given @hwlock, or -EINVAL if @hwlock is invalid. + */ +int hwspin_lock_get_id(struct hwspinlock *hwlock) +{ + if (!hwlock) { + pr_err("invalid hwlock\n"); + return -EINVAL; + } + + return hwlock->id; +} +EXPORT_SYMBOL_GPL(hwspin_lock_get_id); + +/** + * hwspin_lock_request() - request an hwspinlock + * + * This function should be called by users of the hwspinlock device, + * in order to dynamically assign them an unused hwspinlock. + * Usually the user of this lock will then have to communicate the lock's id + * to the remote core before it can be used for synchronization (to get the + * id of a given hwlock, use hwspin_lock_get_id()). + * + * Can be called from an atomic context (will not sleep) but not from + * within interrupt context (simply because there is no use case for + * that yet). + * + * Returns the address of the assigned hwspinlock, or NULL on error + */ +struct hwspinlock *hwspin_lock_request(void) +{ + struct hwspinlock *hwlock; + int ret; + + spin_lock(&hwspinlock_tree_lock); + + /* look for an unused lock */ + ret = radix_tree_gang_lookup_tag(&hwspinlock_tree, (void **)&hwlock, + 0, 1, HWSPINLOCK_UNUSED); + if (ret == 0) { + pr_warn("a free hwspinlock is not available\n"); + hwlock = NULL; + goto out; + } + + /* sanity check that should never fail */ + WARN_ON(ret > 1); + + /* mark as used and power up */ + ret = __hwspin_lock_request(hwlock); + if (ret < 0) + hwlock = NULL; + +out: + spin_unlock(&hwspinlock_tree_lock); + return hwlock; +} +EXPORT_SYMBOL_GPL(hwspin_lock_request); + +/** + * hwspin_lock_request_specific() - request for a specific hwspinlock + * @id: index of the specific hwspinlock that is requested + * + * This function should be called by users of the hwspinlock module, + * in order to assign them a specific hwspinlock. + * Usually early board code will be calling this function in order to + * reserve specific hwspinlock ids for predefined purposes. + * + * Can be called from an atomic context (will not sleep) but not from + * within interrupt context (simply because there is no use case for + * that yet). + * + * Returns the address of the assigned hwspinlock, or NULL on error + */ +struct hwspinlock *hwspin_lock_request_specific(unsigned int id) +{ + struct hwspinlock *hwlock; + int ret; + + spin_lock(&hwspinlock_tree_lock); + + /* make sure this hwspinlock exists */ + hwlock = radix_tree_lookup(&hwspinlock_tree, id); + if (!hwlock) { + pr_warn("hwspinlock %u does not exist\n", id); + goto out; + } + + /* sanity check (this shouldn't happen) */ + WARN_ON(hwlock->id != id); + + /* make sure this hwspinlock is unused */ + ret = radix_tree_tag_get(&hwspinlock_tree, id, HWSPINLOCK_UNUSED); + if (ret == 0) { + pr_warn("hwspinlock %u is already in use\n", id); + hwlock = NULL; + goto out; + } + + /* mark as used and power up */ + ret = __hwspin_lock_request(hwlock); + if (ret < 0) + hwlock = NULL; + +out: + spin_unlock(&hwspinlock_tree_lock); + return hwlock; +} +EXPORT_SYMBOL_GPL(hwspin_lock_request_specific); + +/** + * hwspin_lock_free() - free a specific hwspinlock + * @hwlock: the specific hwspinlock to free + * + * This function mark @hwlock as free again. + * Should only be called with an @hwlock that was retrieved from + * an earlier call to omap_hwspin_lock_request{_specific}. + * + * Can be called from an atomic context (will not sleep) but not from + * within interrupt context (simply because there is no use case for + * that yet). + * + * Returns 0 on success, or an appropriate error code on failure + */ +int hwspin_lock_free(struct hwspinlock *hwlock) +{ + struct hwspinlock *tmp; + int ret; + + if (!hwlock) { + pr_err("invalid hwlock\n"); + return -EINVAL; + } + + spin_lock(&hwspinlock_tree_lock); + + /* make sure the hwspinlock is used */ + ret = radix_tree_tag_get(&hwspinlock_tree, hwlock->id, + HWSPINLOCK_UNUSED); + if (ret == 1) { + dev_err(hwlock->dev, "%s: hwlock is already free\n", __func__); + dump_stack(); + ret = -EINVAL; + goto out; + } + + /* notify the underlying device that power is not needed */ + ret = pm_runtime_put(hwlock->dev); + if (ret < 0) + goto out; + + /* mark this hwspinlock as available */ + tmp = radix_tree_tag_set(&hwspinlock_tree, hwlock->id, + HWSPINLOCK_UNUSED); + + /* sanity check (this shouldn't happen) */ + WARN_ON(tmp != hwlock); + + module_put(hwlock->owner); + +out: + spin_unlock(&hwspinlock_tree_lock); + return ret; +} +EXPORT_SYMBOL_GPL(hwspin_lock_free); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware spinlock interface"); +MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>"); diff --git a/drivers/hwspinlock/hwspinlock_internal.h b/drivers/hwspinlock/hwspinlock_internal.h new file mode 100644 index 000000000000..69935e6b93e5 --- /dev/null +++ b/drivers/hwspinlock/hwspinlock_internal.h @@ -0,0 +1,61 @@ +/* + * Hardware spinlocks internal header + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: Ohad Ben-Cohen <ohad@wizery.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published + * by the Free Software Foundation. + * + * 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. + */ + +#ifndef __HWSPINLOCK_HWSPINLOCK_H +#define __HWSPINLOCK_HWSPINLOCK_H + +#include <linux/spinlock.h> +#include <linux/device.h> + +/** + * struct hwspinlock_ops - platform-specific hwspinlock handlers + * + * @trylock: make a single attempt to take the lock. returns 0 on + * failure and true on success. may _not_ sleep. + * @unlock: release the lock. always succeed. may _not_ sleep. + * @relax: optional, platform-specific relax handler, called by hwspinlock + * core while spinning on a lock, between two successive + * invocations of @trylock. may _not_ sleep. + */ +struct hwspinlock_ops { + int (*trylock)(struct hwspinlock *lock); + void (*unlock)(struct hwspinlock *lock); + void (*relax)(struct hwspinlock *lock); +}; + +/** + * struct hwspinlock - this struct represents a single hwspinlock instance + * + * @dev: underlying device, will be used to invoke runtime PM api + * @ops: platform-specific hwspinlock handlers + * @id: a global, unique, system-wide, index of the lock. + * @lock: initialized and used by hwspinlock core + * @owner: underlying implementation module, used to maintain module ref count + * + * Note: currently simplicity was opted for, but later we can squeeze some + * memory bytes by grouping the dev, ops and owner members in a single + * per-platform struct, and have all hwspinlocks point at it. + */ +struct hwspinlock { + struct device *dev; + const struct hwspinlock_ops *ops; + int id; + spinlock_t lock; + struct module *owner; +}; + +#endif /* __HWSPINLOCK_HWSPINLOCK_H */ diff --git a/drivers/hwspinlock/omap_hwspinlock.c b/drivers/hwspinlock/omap_hwspinlock.c new file mode 100644 index 000000000000..a8f02734c026 --- /dev/null +++ b/drivers/hwspinlock/omap_hwspinlock.c @@ -0,0 +1,231 @@ +/* + * OMAP hardware spinlock driver + * + * Copyright (C) 2010 Texas Instruments Incorporated - http://www.ti.com + * + * Contact: Simon Que <sque@ti.com> + * Hari Kanigeri <h-kanigeri2@ti.com> + * Ohad Ben-Cohen <ohad@wizery.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 as published by the Free Software Foundation. + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/device.h> +#include <linux/delay.h> +#include <linux/io.h> +#include <linux/bitops.h> +#include <linux/pm_runtime.h> +#include <linux/slab.h> +#include <linux/spinlock.h> +#include <linux/hwspinlock.h> +#include <linux/platform_device.h> + +#include "hwspinlock_internal.h" + +/* Spinlock register offsets */ +#define SYSSTATUS_OFFSET 0x0014 +#define LOCK_BASE_OFFSET 0x0800 + +#define SPINLOCK_NUMLOCKS_BIT_OFFSET (24) + +/* Possible values of SPINLOCK_LOCK_REG */ +#define SPINLOCK_NOTTAKEN (0) /* free */ +#define SPINLOCK_TAKEN (1) /* locked */ + +#define to_omap_hwspinlock(lock) \ + container_of(lock, struct omap_hwspinlock, lock) + +struct omap_hwspinlock { + struct hwspinlock lock; + void __iomem *addr; +}; + +struct omap_hwspinlock_state { + int num_locks; /* Total number of locks in system */ + void __iomem *io_base; /* Mapped base address */ +}; + +static int omap_hwspinlock_trylock(struct hwspinlock *lock) +{ + struct omap_hwspinlock *omap_lock = to_omap_hwspinlock(lock); + + /* attempt to acquire the lock by reading its value */ + return (SPINLOCK_NOTTAKEN == readl(omap_lock->addr)); +} + +static void omap_hwspinlock_unlock(struct hwspinlock *lock) +{ + struct omap_hwspinlock *omap_lock = to_omap_hwspinlock(lock); + + /* release the lock by writing 0 to it */ + writel(SPINLOCK_NOTTAKEN, omap_lock->addr); +} + +/* + * relax the OMAP interconnect while spinning on it. + * + * The specs recommended that the retry delay time will be + * just over half of the time that a requester would be + * expected to hold the lock. + * + * The number below is taken from an hardware specs example, + * obviously it is somewhat arbitrary. + */ +static void omap_hwspinlock_relax(struct hwspinlock *lock) +{ + ndelay(50); +} + +static const struct hwspinlock_ops omap_hwspinlock_ops = { + .trylock = omap_hwspinlock_trylock, + .unlock = omap_hwspinlock_unlock, + .relax = omap_hwspinlock_relax, +}; + +static int __devinit omap_hwspinlock_probe(struct platform_device *pdev) +{ + struct omap_hwspinlock *omap_lock; + struct omap_hwspinlock_state *state; + struct hwspinlock *lock; + struct resource *res; + void __iomem *io_base; + int i, ret; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENODEV; + + state = kzalloc(sizeof(*state), GFP_KERNEL); + if (!state) + return -ENOMEM; + + io_base = ioremap(res->start, resource_size(res)); + if (!io_base) { + ret = -ENOMEM; + goto free_state; + } + + /* Determine number of locks */ + i = readl(io_base + SYSSTATUS_OFFSET); + i >>= SPINLOCK_NUMLOCKS_BIT_OFFSET; + + /* one of the four lsb's must be set, and nothing else */ + if (hweight_long(i & 0xf) != 1 || i > 8) { + ret = -EINVAL; + goto iounmap_base; + } + + state->num_locks = i * 32; + state->io_base = io_base; + + platform_set_drvdata(pdev, state); + + /* + * runtime PM will make sure the clock of this module is + * enabled iff at least one lock is requested + */ + pm_runtime_enable(&pdev->dev); + + for (i = 0; i < state->num_locks; i++) { + omap_lock = kzalloc(sizeof(*omap_lock), GFP_KERNEL); + if (!omap_lock) { + ret = -ENOMEM; + goto free_locks; + } + + omap_lock->lock.dev = &pdev->dev; + omap_lock->lock.owner = THIS_MODULE; + omap_lock->lock.id = i; + omap_lock->lock.ops = &omap_hwspinlock_ops; + omap_lock->addr = io_base + LOCK_BASE_OFFSET + sizeof(u32) * i; + + ret = hwspin_lock_register(&omap_lock->lock); + if (ret) { + kfree(omap_lock); + goto free_locks; + } + } + + return 0; + +free_locks: + while (--i >= 0) { + lock = hwspin_lock_unregister(i); + /* this should't happen, but let's give our best effort */ + if (!lock) { + dev_err(&pdev->dev, "%s: cleanups failed\n", __func__); + continue; + } + omap_lock = to_omap_hwspinlock(lock); + kfree(omap_lock); + } + pm_runtime_disable(&pdev->dev); +iounmap_base: + iounmap(io_base); +free_state: + kfree(state); + return ret; +} + +static int omap_hwspinlock_remove(struct platform_device *pdev) +{ + struct omap_hwspinlock_state *state = platform_get_drvdata(pdev); + struct hwspinlock *lock; + struct omap_hwspinlock *omap_lock; + int i; + + for (i = 0; i < state->num_locks; i++) { + lock = hwspin_lock_unregister(i); + /* this shouldn't happen at this point. if it does, at least + * don't continue with the remove */ + if (!lock) { + dev_err(&pdev->dev, "%s: failed on %d\n", __func__, i); + return -EBUSY; + } + + omap_lock = to_omap_hwspinlock(lock); + kfree(omap_lock); + } + + pm_runtime_disable(&pdev->dev); + iounmap(state->io_base); + kfree(state); + + return 0; +} + +static struct platform_driver omap_hwspinlock_driver = { + .probe = omap_hwspinlock_probe, + .remove = omap_hwspinlock_remove, + .driver = { + .name = "omap_hwspinlock", + }, +}; + +static int __init omap_hwspinlock_init(void) +{ + return platform_driver_register(&omap_hwspinlock_driver); +} +/* board init code might need to reserve hwspinlocks for predefined purposes */ +postcore_initcall(omap_hwspinlock_init); + +static void __exit omap_hwspinlock_exit(void) +{ + platform_driver_unregister(&omap_hwspinlock_driver); +} +module_exit(omap_hwspinlock_exit); + +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("Hardware spinlock driver for OMAP"); +MODULE_AUTHOR("Simon Que <sque@ti.com>"); +MODULE_AUTHOR("Hari Kanigeri <h-kanigeri2@ti.com>"); +MODULE_AUTHOR("Ohad Ben-Cohen <ohad@wizery.com>"); diff --git a/drivers/i2c/Makefile b/drivers/i2c/Makefile index 23ac61e2db39..beee6b2d361d 100644 --- a/drivers/i2c/Makefile +++ b/drivers/i2c/Makefile @@ -10,3 +10,4 @@ obj-$(CONFIG_I2C_MUX) += i2c-mux.o obj-y += algos/ busses/ muxes/ ccflags-$(CONFIG_I2C_DEBUG_CORE) := -DDEBUG +CFLAGS_i2c-core.o := -Wno-deprecated-declarations diff --git a/drivers/i2c/busses/Kconfig b/drivers/i2c/busses/Kconfig index 113505a6434e..a1db4821643c 100644 --- a/drivers/i2c/busses/Kconfig +++ b/drivers/i2c/busses/Kconfig @@ -452,6 +452,16 @@ config I2C_MV64XXX This driver can also be built as a module. If so, the module will be called i2c-mv64xxx. +config I2C_MXS + tristate "Freescale i.MX28 I2C interface" + depends on SOC_IMX28 + help + Say Y here if you want to use the I2C bus controller on + the Freescale i.MX28 processors. + + This driver can also be built as a module. If so, the module + will be called i2c-mxs. + config I2C_NOMADIK tristate "ST-Ericsson Nomadik/Ux500 I2C Controller" depends on PLAT_NOMADIK @@ -523,6 +533,17 @@ config I2C_PNX This driver can also be built as a module. If so, the module will be called i2c-pnx. +config I2C_PUV3 + tristate "PKUnity v3 I2C bus support" + depends on UNICORE32 && ARCH_PUV3 + select I2C_ALGOBIT + help + This driver supports the I2C IP inside the PKUnity-v3 SoC. + This I2C bus controller is under AMBA/AXI bus. + + This driver can also be built as a module. If so, the module + will be called i2c-puv3. + config I2C_PXA tristate "Intel PXA2XX I2C adapter" depends on ARCH_PXA || ARCH_MMP @@ -607,6 +628,13 @@ config I2C_STU300 This driver can also be built as a module. If so, the module will be called i2c-stu300. +config I2C_TEGRA + tristate "NVIDIA Tegra internal I2C controller" + depends on ARCH_TEGRA + help + If you say yes to this option, support will be included for the + I2C controller embedded in NVIDIA Tegra SOCs + config I2C_VERSATILE tristate "ARM Versatile/Realview I2C bus support" depends on ARCH_VERSATILE || ARCH_REALVIEW || ARCH_VEXPRESS @@ -648,6 +676,16 @@ config I2C_EG20T comment "External I2C/SMBus adapter drivers" +config I2C_DIOLAN_U2C + tristate "Diolan U2C-12 USB adapter" + depends on USB + help + If you say yes to this option, support will be included for Diolan + U2C-12, a USB to I2C interface. + + This driver can also be built as a module. If so, the module + will be called i2c-diolan-u2c. + config I2C_PARPORT tristate "Parallel port adapter" depends on PARPORT diff --git a/drivers/i2c/busses/Makefile b/drivers/i2c/busses/Makefile index 9d2d0ec7fb23..8596cfdb43ef 100644 --- a/drivers/i2c/busses/Makefile +++ b/drivers/i2c/busses/Makefile @@ -43,6 +43,7 @@ obj-$(CONFIG_I2C_IOP3XX) += i2c-iop3xx.o obj-$(CONFIG_I2C_IXP2000) += i2c-ixp2000.o obj-$(CONFIG_I2C_MPC) += i2c-mpc.o obj-$(CONFIG_I2C_MV64XXX) += i2c-mv64xxx.o +obj-$(CONFIG_I2C_MXS) += i2c-mxs.o obj-$(CONFIG_I2C_NOMADIK) += i2c-nomadik.o obj-$(CONFIG_I2C_NUC900) += i2c-nuc900.o obj-$(CONFIG_I2C_OCORES) += i2c-ocores.o @@ -51,6 +52,7 @@ obj-$(CONFIG_I2C_PASEMI) += i2c-pasemi.o obj-$(CONFIG_I2C_PCA_PLATFORM) += i2c-pca-platform.o obj-$(CONFIG_I2C_PMCMSP) += i2c-pmcmsp.o obj-$(CONFIG_I2C_PNX) += i2c-pnx.o +obj-$(CONFIG_I2C_PUV3) += i2c-puv3.o obj-$(CONFIG_I2C_PXA) += i2c-pxa.o obj-$(CONFIG_I2C_S3C2410) += i2c-s3c2410.o obj-$(CONFIG_I2C_S6000) += i2c-s6000.o @@ -58,12 +60,14 @@ obj-$(CONFIG_I2C_SH7760) += i2c-sh7760.o obj-$(CONFIG_I2C_SH_MOBILE) += i2c-sh_mobile.o obj-$(CONFIG_I2C_SIMTEC) += i2c-simtec.o obj-$(CONFIG_I2C_STU300) += i2c-stu300.o +obj-$(CONFIG_I2C_TEGRA) += i2c-tegra.o obj-$(CONFIG_I2C_VERSATILE) += i2c-versatile.o obj-$(CONFIG_I2C_OCTEON) += i2c-octeon.o obj-$(CONFIG_I2C_XILINX) += i2c-xiic.o obj-$(CONFIG_I2C_EG20T) += i2c-eg20t.o # External I2C/SMBus adapter drivers +obj-$(CONFIG_I2C_DIOLAN_U2C) += i2c-diolan-u2c.o obj-$(CONFIG_I2C_PARPORT) += i2c-parport.o obj-$(CONFIG_I2C_PARPORT_LIGHT) += i2c-parport-light.o obj-$(CONFIG_I2C_TAOS_EVM) += i2c-taos-evm.o diff --git a/drivers/i2c/busses/i2c-diolan-u2c.c b/drivers/i2c/busses/i2c-diolan-u2c.c new file mode 100644 index 000000000000..76366716a854 --- /dev/null +++ b/drivers/i2c/busses/i2c-diolan-u2c.c @@ -0,0 +1,535 @@ +/* + * Driver for the Diolan u2c-12 USB-I2C adapter + * + * Copyright (c) 2010-2011 Ericsson AB + * + * Derived from: + * i2c-tiny-usb.c + * Copyright (C) 2006-2007 Till Harbaum (Till@Harbaum.org) + * + * 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, version 2. + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/i2c.h> + +#define DRIVER_NAME "i2c-diolan-u2c" + +#define USB_VENDOR_ID_DIOLAN 0x0abf +#define USB_DEVICE_ID_DIOLAN_U2C 0x3370 + +#define DIOLAN_OUT_EP 0x02 +#define DIOLAN_IN_EP 0x84 + +/* commands via USB, must match command ids in the firmware */ +#define CMD_I2C_READ 0x01 +#define CMD_I2C_WRITE 0x02 +#define CMD_I2C_SCAN 0x03 /* Returns list of detected devices */ +#define CMD_I2C_RELEASE_SDA 0x04 +#define CMD_I2C_RELEASE_SCL 0x05 +#define CMD_I2C_DROP_SDA 0x06 +#define CMD_I2C_DROP_SCL 0x07 +#define CMD_I2C_READ_SDA 0x08 +#define CMD_I2C_READ_SCL 0x09 +#define CMD_GET_FW_VERSION 0x0a +#define CMD_GET_SERIAL 0x0b +#define CMD_I2C_START 0x0c +#define CMD_I2C_STOP 0x0d +#define CMD_I2C_REPEATED_START 0x0e +#define CMD_I2C_PUT_BYTE 0x0f +#define CMD_I2C_GET_BYTE 0x10 +#define CMD_I2C_PUT_ACK 0x11 +#define CMD_I2C_GET_ACK 0x12 +#define CMD_I2C_PUT_BYTE_ACK 0x13 +#define CMD_I2C_GET_BYTE_ACK 0x14 +#define CMD_I2C_SET_SPEED 0x1b +#define CMD_I2C_GET_SPEED 0x1c +#define CMD_I2C_SET_CLK_SYNC 0x24 +#define CMD_I2C_GET_CLK_SYNC 0x25 +#define CMD_I2C_SET_CLK_SYNC_TO 0x26 +#define CMD_I2C_GET_CLK_SYNC_TO 0x27 + +#define RESP_OK 0x00 +#define RESP_FAILED 0x01 +#define RESP_BAD_MEMADDR 0x04 +#define RESP_DATA_ERR 0x05 +#define RESP_NOT_IMPLEMENTED 0x06 +#define RESP_NACK 0x07 +#define RESP_TIMEOUT 0x09 + +#define U2C_I2C_SPEED_FAST 0 /* 400 kHz */ +#define U2C_I2C_SPEED_STD 1 /* 100 kHz */ +#define U2C_I2C_SPEED_2KHZ 242 /* 2 kHz, minimum speed */ +#define U2C_I2C_SPEED(f) ((DIV_ROUND_UP(1000000, (f)) - 10) / 2 + 1) + +#define U2C_I2C_FREQ_FAST 400000 +#define U2C_I2C_FREQ_STD 100000 +#define U2C_I2C_FREQ(s) (1000000 / (2 * (s - 1) + 10)) + +#define DIOLAN_USB_TIMEOUT 100 /* in ms */ +#define DIOLAN_SYNC_TIMEOUT 20 /* in ms */ + +#define DIOLAN_OUTBUF_LEN 128 +#define DIOLAN_FLUSH_LEN (DIOLAN_OUTBUF_LEN - 4) +#define DIOLAN_INBUF_LEN 256 /* Maximum supported receive length */ + +/* Structure to hold all of our device specific stuff */ +struct i2c_diolan_u2c { + u8 obuffer[DIOLAN_OUTBUF_LEN]; /* output buffer */ + u8 ibuffer[DIOLAN_INBUF_LEN]; /* input buffer */ + struct usb_device *usb_dev; /* the usb device for this device */ + struct usb_interface *interface;/* the interface for this device */ + struct i2c_adapter adapter; /* i2c related things */ + int olen; /* Output buffer length */ + int ocount; /* Number of enqueued messages */ +}; + +static uint frequency = U2C_I2C_FREQ_STD; /* I2C clock frequency in Hz */ + +module_param(frequency, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(frequency, "I2C clock frequency in hertz"); + +/* usb layer */ + +/* Send command to device, and get response. */ +static int diolan_usb_transfer(struct i2c_diolan_u2c *dev) +{ + int ret = 0; + int actual; + int i; + + if (!dev->olen || !dev->ocount) + return -EINVAL; + + ret = usb_bulk_msg(dev->usb_dev, + usb_sndbulkpipe(dev->usb_dev, DIOLAN_OUT_EP), + dev->obuffer, dev->olen, &actual, + DIOLAN_USB_TIMEOUT); + if (!ret) { + for (i = 0; i < dev->ocount; i++) { + int tmpret; + + tmpret = usb_bulk_msg(dev->usb_dev, + usb_rcvbulkpipe(dev->usb_dev, + DIOLAN_IN_EP), + dev->ibuffer, + sizeof(dev->ibuffer), &actual, + DIOLAN_USB_TIMEOUT); + /* + * Stop command processing if a previous command + * returned an error. + * Note that we still need to retrieve all messages. + */ + if (ret < 0) + continue; + ret = tmpret; + if (ret == 0 && actual > 0) { + switch (dev->ibuffer[actual - 1]) { + case RESP_NACK: + /* + * Return ENXIO if NACK was received as + * response to the address phase, + * EIO otherwise + */ + ret = i == 1 ? -ENXIO : -EIO; + break; + case RESP_TIMEOUT: + ret = -ETIMEDOUT; + break; + case RESP_OK: + /* strip off return code */ + ret = actual - 1; + break; + default: + ret = -EIO; + break; + } + } + } + } + dev->olen = 0; + dev->ocount = 0; + return ret; +} + +static int diolan_write_cmd(struct i2c_diolan_u2c *dev, bool flush) +{ + if (flush || dev->olen >= DIOLAN_FLUSH_LEN) + return diolan_usb_transfer(dev); + return 0; +} + +/* Send command (no data) */ +static int diolan_usb_cmd(struct i2c_diolan_u2c *dev, u8 command, bool flush) +{ + dev->obuffer[dev->olen++] = command; + dev->ocount++; + return diolan_write_cmd(dev, flush); +} + +/* Send command with one byte of data */ +static int diolan_usb_cmd_data(struct i2c_diolan_u2c *dev, u8 command, u8 data, + bool flush) +{ + dev->obuffer[dev->olen++] = command; + dev->obuffer[dev->olen++] = data; + dev->ocount++; + return diolan_write_cmd(dev, flush); +} + +/* Send command with two bytes of data */ +static int diolan_usb_cmd_data2(struct i2c_diolan_u2c *dev, u8 command, u8 d1, + u8 d2, bool flush) +{ + dev->obuffer[dev->olen++] = command; + dev->obuffer[dev->olen++] = d1; + dev->obuffer[dev->olen++] = d2; + dev->ocount++; + return diolan_write_cmd(dev, flush); +} + +/* + * Flush input queue. + * If we don't do this at startup and the controller has queued up + * messages which were not retrieved, it will stop responding + * at some point. + */ +static void diolan_flush_input(struct i2c_diolan_u2c *dev) +{ + int i; + + for (i = 0; i < 10; i++) { + int actual = 0; + int ret; + + ret = usb_bulk_msg(dev->usb_dev, + usb_rcvbulkpipe(dev->usb_dev, DIOLAN_IN_EP), + dev->ibuffer, sizeof(dev->ibuffer), &actual, + DIOLAN_USB_TIMEOUT); + if (ret < 0 || actual == 0) + break; + } + if (i == 10) + dev_err(&dev->interface->dev, "Failed to flush input buffer\n"); +} + +static int diolan_i2c_start(struct i2c_diolan_u2c *dev) +{ + return diolan_usb_cmd(dev, CMD_I2C_START, false); +} + +static int diolan_i2c_repeated_start(struct i2c_diolan_u2c *dev) +{ + return diolan_usb_cmd(dev, CMD_I2C_REPEATED_START, false); +} + +static int diolan_i2c_stop(struct i2c_diolan_u2c *dev) +{ + return diolan_usb_cmd(dev, CMD_I2C_STOP, true); +} + +static int diolan_i2c_get_byte_ack(struct i2c_diolan_u2c *dev, bool ack, + u8 *byte) +{ + int ret; + + ret = diolan_usb_cmd_data(dev, CMD_I2C_GET_BYTE_ACK, ack, true); + if (ret > 0) + *byte = dev->ibuffer[0]; + else if (ret == 0) + ret = -EIO; + + return ret; +} + +static int diolan_i2c_put_byte_ack(struct i2c_diolan_u2c *dev, u8 byte) +{ + return diolan_usb_cmd_data(dev, CMD_I2C_PUT_BYTE_ACK, byte, false); +} + +static int diolan_set_speed(struct i2c_diolan_u2c *dev, u8 speed) +{ + return diolan_usb_cmd_data(dev, CMD_I2C_SET_SPEED, speed, true); +} + +/* Enable or disable clock synchronization (stretching) */ +static int diolan_set_clock_synch(struct i2c_diolan_u2c *dev, bool enable) +{ + return diolan_usb_cmd_data(dev, CMD_I2C_SET_CLK_SYNC, enable, true); +} + +/* Set clock synchronization timeout in ms */ +static int diolan_set_clock_synch_timeout(struct i2c_diolan_u2c *dev, int ms) +{ + int to_val = ms * 10; + + return diolan_usb_cmd_data2(dev, CMD_I2C_SET_CLK_SYNC_TO, + to_val & 0xff, (to_val >> 8) & 0xff, true); +} + +static void diolan_fw_version(struct i2c_diolan_u2c *dev) +{ + int ret; + + ret = diolan_usb_cmd(dev, CMD_GET_FW_VERSION, true); + if (ret >= 2) + dev_info(&dev->interface->dev, + "Diolan U2C firmware version %u.%u\n", + (unsigned int)dev->ibuffer[0], + (unsigned int)dev->ibuffer[1]); +} + +static void diolan_get_serial(struct i2c_diolan_u2c *dev) +{ + int ret; + u32 serial; + + ret = diolan_usb_cmd(dev, CMD_GET_SERIAL, true); + if (ret >= 4) { + serial = le32_to_cpu(*(u32 *)dev->ibuffer); + dev_info(&dev->interface->dev, + "Diolan U2C serial number %u\n", serial); + } +} + +static int diolan_init(struct i2c_diolan_u2c *dev) +{ + int speed, ret; + + if (frequency >= 200000) { + speed = U2C_I2C_SPEED_FAST; + frequency = U2C_I2C_FREQ_FAST; + } else if (frequency >= 100000 || frequency == 0) { + speed = U2C_I2C_SPEED_STD; + frequency = U2C_I2C_FREQ_STD; + } else { + speed = U2C_I2C_SPEED(frequency); + if (speed > U2C_I2C_SPEED_2KHZ) + speed = U2C_I2C_SPEED_2KHZ; + frequency = U2C_I2C_FREQ(speed); + } + + dev_info(&dev->interface->dev, + "Diolan U2C at USB bus %03d address %03d speed %d Hz\n", + dev->usb_dev->bus->busnum, dev->usb_dev->devnum, frequency); + + diolan_flush_input(dev); + diolan_fw_version(dev); + diolan_get_serial(dev); + + /* Set I2C speed */ + ret = diolan_set_speed(dev, speed); + if (ret < 0) + return ret; + + /* Configure I2C clock synchronization */ + ret = diolan_set_clock_synch(dev, speed != U2C_I2C_SPEED_FAST); + if (ret < 0) + return ret; + + if (speed != U2C_I2C_SPEED_FAST) + ret = diolan_set_clock_synch_timeout(dev, DIOLAN_SYNC_TIMEOUT); + + return ret; +} + +/* i2c layer */ + +static int diolan_usb_xfer(struct i2c_adapter *adapter, struct i2c_msg *msgs, + int num) +{ + struct i2c_diolan_u2c *dev = i2c_get_adapdata(adapter); + struct i2c_msg *pmsg; + int i, j; + int ret, sret; + + ret = diolan_i2c_start(dev); + if (ret < 0) + return ret; + + for (i = 0; i < num; i++) { + pmsg = &msgs[i]; + if (i) { + ret = diolan_i2c_repeated_start(dev); + if (ret < 0) + goto abort; + } + if (pmsg->flags & I2C_M_RD) { + ret = + diolan_i2c_put_byte_ack(dev, (pmsg->addr << 1) | 1); + if (ret < 0) + goto abort; + for (j = 0; j < pmsg->len; j++) { + u8 byte; + bool ack = j < pmsg->len - 1; + + /* + * Don't send NACK if this is the first byte + * of a SMBUS_BLOCK message. + */ + if (j == 0 && (pmsg->flags & I2C_M_RECV_LEN)) + ack = true; + + ret = diolan_i2c_get_byte_ack(dev, ack, &byte); + if (ret < 0) + goto abort; + /* + * Adjust count if first received byte is length + */ + if (j == 0 && (pmsg->flags & I2C_M_RECV_LEN)) { + if (byte == 0 + || byte > I2C_SMBUS_BLOCK_MAX) { + ret = -EPROTO; + goto abort; + } + pmsg->len += byte; + } + pmsg->buf[j] = byte; + } + } else { + ret = diolan_i2c_put_byte_ack(dev, pmsg->addr << 1); + if (ret < 0) + goto abort; + for (j = 0; j < pmsg->len; j++) { + ret = diolan_i2c_put_byte_ack(dev, + pmsg->buf[j]); + if (ret < 0) + goto abort; + } + } + } +abort: + sret = diolan_i2c_stop(dev); + if (sret < 0 && ret >= 0) + ret = sret; + return ret; +} + +/* + * Return list of supported functionality. + */ +static u32 diolan_usb_func(struct i2c_adapter *a) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | + I2C_FUNC_SMBUS_READ_BLOCK_DATA | I2C_FUNC_SMBUS_BLOCK_PROC_CALL; +} + +static const struct i2c_algorithm diolan_usb_algorithm = { + .master_xfer = diolan_usb_xfer, + .functionality = diolan_usb_func, +}; + +/* device layer */ + +static const struct usb_device_id diolan_u2c_table[] = { + { USB_DEVICE(USB_VENDOR_ID_DIOLAN, USB_DEVICE_ID_DIOLAN_U2C) }, + { } +}; + +MODULE_DEVICE_TABLE(usb, diolan_u2c_table); + +static void diolan_u2c_free(struct i2c_diolan_u2c *dev) +{ + usb_put_dev(dev->usb_dev); + kfree(dev); +} + +static int diolan_u2c_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct i2c_diolan_u2c *dev; + int ret; + + /* allocate memory for our device state and initialize it */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + dev_err(&interface->dev, "no memory for device state\n"); + ret = -ENOMEM; + goto error; + } + + dev->usb_dev = usb_get_dev(interface_to_usbdev(interface)); + dev->interface = interface; + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + /* setup i2c adapter description */ + dev->adapter.owner = THIS_MODULE; + dev->adapter.class = I2C_CLASS_HWMON; + dev->adapter.algo = &diolan_usb_algorithm; + i2c_set_adapdata(&dev->adapter, dev); + snprintf(dev->adapter.name, sizeof(dev->adapter.name), + DRIVER_NAME " at bus %03d device %03d", + dev->usb_dev->bus->busnum, dev->usb_dev->devnum); + + dev->adapter.dev.parent = &dev->interface->dev; + + /* initialize diolan i2c interface */ + ret = diolan_init(dev); + if (ret < 0) { + dev_err(&interface->dev, "failed to initialize adapter\n"); + goto error_free; + } + + /* and finally attach to i2c layer */ + ret = i2c_add_adapter(&dev->adapter); + if (ret < 0) { + dev_err(&interface->dev, "failed to add I2C adapter\n"); + goto error_free; + } + + dev_dbg(&interface->dev, "connected " DRIVER_NAME "\n"); + + return 0; + +error_free: + usb_set_intfdata(interface, NULL); + diolan_u2c_free(dev); +error: + return ret; +} + +static void diolan_u2c_disconnect(struct usb_interface *interface) +{ + struct i2c_diolan_u2c *dev = usb_get_intfdata(interface); + + i2c_del_adapter(&dev->adapter); + usb_set_intfdata(interface, NULL); + diolan_u2c_free(dev); + + dev_dbg(&interface->dev, "disconnected\n"); +} + +static struct usb_driver diolan_u2c_driver = { + .name = DRIVER_NAME, + .probe = diolan_u2c_probe, + .disconnect = diolan_u2c_disconnect, + .id_table = diolan_u2c_table, +}; + +static int __init diolan_u2c_init(void) +{ + /* register this driver with the USB subsystem */ + return usb_register(&diolan_u2c_driver); +} + +static void __exit diolan_u2c_exit(void) +{ + /* deregister this driver with the USB subsystem */ + usb_deregister(&diolan_u2c_driver); +} + +module_init(diolan_u2c_init); +module_exit(diolan_u2c_exit); + +MODULE_AUTHOR("Guenter Roeck <guenter.roeck@ericsson.com>"); +MODULE_DESCRIPTION(DRIVER_NAME " driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/busses/i2c-mxs.c b/drivers/i2c/busses/i2c-mxs.c new file mode 100644 index 000000000000..8022e2390a5a --- /dev/null +++ b/drivers/i2c/busses/i2c-mxs.c @@ -0,0 +1,412 @@ +/* + * Freescale MXS I2C bus driver + * + * Copyright (C) 2011 Wolfram Sang, Pengutronix e.K. + * + * based on a (non-working) driver which was: + * + * Copyright (C) 2009-2010 Freescale Semiconductor, Inc. All Rights Reserved. + * + * TODO: add dma-support if platform-support for it is available + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + */ + +#include <linux/slab.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/i2c.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/completion.h> +#include <linux/platform_device.h> +#include <linux/jiffies.h> +#include <linux/io.h> + +#include <mach/common.h> + +#define DRIVER_NAME "mxs-i2c" + +#define MXS_I2C_CTRL0 (0x00) +#define MXS_I2C_CTRL0_SET (0x04) + +#define MXS_I2C_CTRL0_SFTRST 0x80000000 +#define MXS_I2C_CTRL0_SEND_NAK_ON_LAST 0x02000000 +#define MXS_I2C_CTRL0_RETAIN_CLOCK 0x00200000 +#define MXS_I2C_CTRL0_POST_SEND_STOP 0x00100000 +#define MXS_I2C_CTRL0_PRE_SEND_START 0x00080000 +#define MXS_I2C_CTRL0_MASTER_MODE 0x00020000 +#define MXS_I2C_CTRL0_DIRECTION 0x00010000 +#define MXS_I2C_CTRL0_XFER_COUNT(v) ((v) & 0x0000FFFF) + +#define MXS_I2C_CTRL1 (0x40) +#define MXS_I2C_CTRL1_SET (0x44) +#define MXS_I2C_CTRL1_CLR (0x48) + +#define MXS_I2C_CTRL1_BUS_FREE_IRQ 0x80 +#define MXS_I2C_CTRL1_DATA_ENGINE_CMPLT_IRQ 0x40 +#define MXS_I2C_CTRL1_NO_SLAVE_ACK_IRQ 0x20 +#define MXS_I2C_CTRL1_OVERSIZE_XFER_TERM_IRQ 0x10 +#define MXS_I2C_CTRL1_EARLY_TERM_IRQ 0x08 +#define MXS_I2C_CTRL1_MASTER_LOSS_IRQ 0x04 +#define MXS_I2C_CTRL1_SLAVE_STOP_IRQ 0x02 +#define MXS_I2C_CTRL1_SLAVE_IRQ 0x01 + +#define MXS_I2C_IRQ_MASK (MXS_I2C_CTRL1_DATA_ENGINE_CMPLT_IRQ | \ + MXS_I2C_CTRL1_NO_SLAVE_ACK_IRQ | \ + MXS_I2C_CTRL1_EARLY_TERM_IRQ | \ + MXS_I2C_CTRL1_MASTER_LOSS_IRQ | \ + MXS_I2C_CTRL1_SLAVE_STOP_IRQ | \ + MXS_I2C_CTRL1_SLAVE_IRQ) + +#define MXS_I2C_QUEUECTRL (0x60) +#define MXS_I2C_QUEUECTRL_SET (0x64) +#define MXS_I2C_QUEUECTRL_CLR (0x68) + +#define MXS_I2C_QUEUECTRL_QUEUE_RUN 0x20 +#define MXS_I2C_QUEUECTRL_PIO_QUEUE_MODE 0x04 + +#define MXS_I2C_QUEUESTAT (0x70) +#define MXS_I2C_QUEUESTAT_RD_QUEUE_EMPTY 0x00002000 + +#define MXS_I2C_QUEUECMD (0x80) + +#define MXS_I2C_QUEUEDATA (0x90) + +#define MXS_I2C_DATA (0xa0) + + +#define MXS_CMD_I2C_SELECT (MXS_I2C_CTRL0_RETAIN_CLOCK | \ + MXS_I2C_CTRL0_PRE_SEND_START | \ + MXS_I2C_CTRL0_MASTER_MODE | \ + MXS_I2C_CTRL0_DIRECTION | \ + MXS_I2C_CTRL0_XFER_COUNT(1)) + +#define MXS_CMD_I2C_WRITE (MXS_I2C_CTRL0_PRE_SEND_START | \ + MXS_I2C_CTRL0_MASTER_MODE | \ + MXS_I2C_CTRL0_DIRECTION) + +#define MXS_CMD_I2C_READ (MXS_I2C_CTRL0_SEND_NAK_ON_LAST | \ + MXS_I2C_CTRL0_MASTER_MODE) + +/** + * struct mxs_i2c_dev - per device, private MXS-I2C data + * + * @dev: driver model device node + * @regs: IO registers pointer + * @cmd_complete: completion object for transaction wait + * @cmd_err: error code for last transaction + * @adapter: i2c subsystem adapter node + */ +struct mxs_i2c_dev { + struct device *dev; + void __iomem *regs; + struct completion cmd_complete; + u32 cmd_err; + struct i2c_adapter adapter; +}; + +/* + * TODO: check if calls to here are really needed. If not, we could get rid of + * mxs_reset_block and the mach-dependency. Needs an I2C analyzer, probably. + */ +static void mxs_i2c_reset(struct mxs_i2c_dev *i2c) +{ + mxs_reset_block(i2c->regs); + writel(MXS_I2C_IRQ_MASK << 8, i2c->regs + MXS_I2C_CTRL1_SET); +} + +static void mxs_i2c_pioq_setup_read(struct mxs_i2c_dev *i2c, u8 addr, int len, + int flags) +{ + u32 data; + + writel(MXS_CMD_I2C_SELECT, i2c->regs + MXS_I2C_QUEUECMD); + + data = (addr << 1) | I2C_SMBUS_READ; + writel(data, i2c->regs + MXS_I2C_DATA); + + data = MXS_CMD_I2C_READ | MXS_I2C_CTRL0_XFER_COUNT(len) | flags; + writel(data, i2c->regs + MXS_I2C_QUEUECMD); +} + +static void mxs_i2c_pioq_setup_write(struct mxs_i2c_dev *i2c, + u8 addr, u8 *buf, int len, int flags) +{ + u32 data; + int i, shifts_left; + + data = MXS_CMD_I2C_WRITE | MXS_I2C_CTRL0_XFER_COUNT(len + 1) | flags; + writel(data, i2c->regs + MXS_I2C_QUEUECMD); + + /* + * We have to copy the slave address (u8) and buffer (arbitrary number + * of u8) into the data register (u32). To achieve that, the u8 are put + * into the MSBs of 'data' which is then shifted for the next u8. When + * apropriate, 'data' is written to MXS_I2C_DATA. So, the first u32 + * looks like this: + * + * 3 2 1 0 + * 10987654|32109876|54321098|76543210 + * --------+--------+--------+-------- + * buffer+2|buffer+1|buffer+0|slave_addr + */ + + data = ((addr << 1) | I2C_SMBUS_WRITE) << 24; + + for (i = 0; i < len; i++) { + data >>= 8; + data |= buf[i] << 24; + if ((i & 3) == 2) + writel(data, i2c->regs + MXS_I2C_DATA); + } + + /* Write out the remaining bytes if any */ + shifts_left = 24 - (i & 3) * 8; + if (shifts_left) + writel(data >> shifts_left, i2c->regs + MXS_I2C_DATA); +} + +/* + * TODO: should be replaceable with a waitqueue and RD_QUEUE_IRQ (setting the + * rd_threshold to 1). Couldn't get this to work, though. + */ +static int mxs_i2c_wait_for_data(struct mxs_i2c_dev *i2c) +{ + unsigned long timeout = jiffies + msecs_to_jiffies(1000); + + while (readl(i2c->regs + MXS_I2C_QUEUESTAT) + & MXS_I2C_QUEUESTAT_RD_QUEUE_EMPTY) { + if (time_after(jiffies, timeout)) + return -ETIMEDOUT; + cond_resched(); + } + + return 0; +} + +static int mxs_i2c_finish_read(struct mxs_i2c_dev *i2c, u8 *buf, int len) +{ + u32 data; + int i; + + for (i = 0; i < len; i++) { + if ((i & 3) == 0) { + if (mxs_i2c_wait_for_data(i2c)) + return -ETIMEDOUT; + data = readl(i2c->regs + MXS_I2C_QUEUEDATA); + } + buf[i] = data & 0xff; + data >>= 8; + } + + return 0; +} + +/* + * Low level master read/write transaction. + */ +static int mxs_i2c_xfer_msg(struct i2c_adapter *adap, struct i2c_msg *msg, + int stop) +{ + struct mxs_i2c_dev *i2c = i2c_get_adapdata(adap); + int ret; + int flags; + + init_completion(&i2c->cmd_complete); + + dev_dbg(i2c->dev, "addr: 0x%04x, len: %d, flags: 0x%x, stop: %d\n", + msg->addr, msg->len, msg->flags, stop); + + if (msg->len == 0) + return -EINVAL; + + flags = stop ? MXS_I2C_CTRL0_POST_SEND_STOP : 0; + + if (msg->flags & I2C_M_RD) + mxs_i2c_pioq_setup_read(i2c, msg->addr, msg->len, flags); + else + mxs_i2c_pioq_setup_write(i2c, msg->addr, msg->buf, msg->len, + flags); + + writel(MXS_I2C_QUEUECTRL_QUEUE_RUN, + i2c->regs + MXS_I2C_QUEUECTRL_SET); + + ret = wait_for_completion_timeout(&i2c->cmd_complete, + msecs_to_jiffies(1000)); + if (ret == 0) + goto timeout; + + if ((!i2c->cmd_err) && (msg->flags & I2C_M_RD)) { + ret = mxs_i2c_finish_read(i2c, msg->buf, msg->len); + if (ret) + goto timeout; + } + + if (i2c->cmd_err == -ENXIO) + mxs_i2c_reset(i2c); + + dev_dbg(i2c->dev, "Done with err=%d\n", i2c->cmd_err); + + return i2c->cmd_err; + +timeout: + dev_dbg(i2c->dev, "Timeout!\n"); + mxs_i2c_reset(i2c); + return -ETIMEDOUT; +} + +static int mxs_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + int i; + int err; + + for (i = 0; i < num; i++) { + err = mxs_i2c_xfer_msg(adap, &msgs[i], i == (num - 1)); + if (err) + return err; + } + + return num; +} + +static u32 mxs_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C | (I2C_FUNC_SMBUS_EMUL & ~I2C_FUNC_SMBUS_QUICK); +} + +static irqreturn_t mxs_i2c_isr(int this_irq, void *dev_id) +{ + struct mxs_i2c_dev *i2c = dev_id; + u32 stat = readl(i2c->regs + MXS_I2C_CTRL1) & MXS_I2C_IRQ_MASK; + + if (!stat) + return IRQ_NONE; + + if (stat & MXS_I2C_CTRL1_NO_SLAVE_ACK_IRQ) + i2c->cmd_err = -ENXIO; + else if (stat & (MXS_I2C_CTRL1_EARLY_TERM_IRQ | + MXS_I2C_CTRL1_MASTER_LOSS_IRQ | + MXS_I2C_CTRL1_SLAVE_STOP_IRQ | MXS_I2C_CTRL1_SLAVE_IRQ)) + /* MXS_I2C_CTRL1_OVERSIZE_XFER_TERM_IRQ is only for slaves */ + i2c->cmd_err = -EIO; + else + i2c->cmd_err = 0; + + complete(&i2c->cmd_complete); + + writel(stat, i2c->regs + MXS_I2C_CTRL1_CLR); + return IRQ_HANDLED; +} + +static const struct i2c_algorithm mxs_i2c_algo = { + .master_xfer = mxs_i2c_xfer, + .functionality = mxs_i2c_func, +}; + +static int __devinit mxs_i2c_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct mxs_i2c_dev *i2c; + struct i2c_adapter *adap; + struct resource *res; + resource_size_t res_size; + int err, irq; + + i2c = devm_kzalloc(dev, sizeof(struct mxs_i2c_dev), GFP_KERNEL); + if (!i2c) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) + return -ENOENT; + + res_size = resource_size(res); + if (!devm_request_mem_region(dev, res->start, res_size, res->name)) + return -EBUSY; + + i2c->regs = devm_ioremap_nocache(dev, res->start, res_size); + if (!i2c->regs) + return -EBUSY; + + irq = platform_get_irq(pdev, 0); + if (irq < 0) + return irq; + + err = devm_request_irq(dev, irq, mxs_i2c_isr, 0, dev_name(dev), i2c); + if (err) + return err; + + i2c->dev = dev; + platform_set_drvdata(pdev, i2c); + + /* Do reset to enforce correct startup after pinmuxing */ + mxs_i2c_reset(i2c); + writel(MXS_I2C_QUEUECTRL_PIO_QUEUE_MODE, + i2c->regs + MXS_I2C_QUEUECTRL_SET); + + adap = &i2c->adapter; + strlcpy(adap->name, "MXS I2C adapter", sizeof(adap->name)); + adap->owner = THIS_MODULE; + adap->algo = &mxs_i2c_algo; + adap->dev.parent = dev; + adap->nr = pdev->id; + i2c_set_adapdata(adap, i2c); + err = i2c_add_numbered_adapter(adap); + if (err) { + dev_err(dev, "Failed to add adapter (%d)\n", err); + writel(MXS_I2C_CTRL0_SFTRST, + i2c->regs + MXS_I2C_CTRL0_SET); + return err; + } + + return 0; +} + +static int __devexit mxs_i2c_remove(struct platform_device *pdev) +{ + struct mxs_i2c_dev *i2c = platform_get_drvdata(pdev); + int ret; + + ret = i2c_del_adapter(&i2c->adapter); + if (ret) + return -EBUSY; + + writel(MXS_I2C_QUEUECTRL_QUEUE_RUN, + i2c->regs + MXS_I2C_QUEUECTRL_CLR); + writel(MXS_I2C_CTRL0_SFTRST, i2c->regs + MXS_I2C_CTRL0_SET); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static struct platform_driver mxs_i2c_driver = { + .driver = { + .name = DRIVER_NAME, + .owner = THIS_MODULE, + }, + .remove = __devexit_p(mxs_i2c_remove), +}; + +static int __init mxs_i2c_init(void) +{ + return platform_driver_probe(&mxs_i2c_driver, mxs_i2c_probe); +} +subsys_initcall(mxs_i2c_init); + +static void __exit mxs_i2c_exit(void) +{ + platform_driver_unregister(&mxs_i2c_driver); +} +module_exit(mxs_i2c_exit); + +MODULE_AUTHOR("Wolfram Sang <w.sang@pengutronix.de>"); +MODULE_DESCRIPTION("MXS I2C Bus Driver"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRIVER_NAME); diff --git a/drivers/i2c/busses/i2c-omap.c b/drivers/i2c/busses/i2c-omap.c index b605ff3a1fa0..829a2a1029f7 100644 --- a/drivers/i2c/busses/i2c-omap.c +++ b/drivers/i2c/busses/i2c-omap.c @@ -847,11 +847,15 @@ complete: dev_err(dev->dev, "Arbitration lost\n"); err |= OMAP_I2C_STAT_AL; } + /* + * ProDB0017052: Clear ARDY bit twice + */ if (stat & (OMAP_I2C_STAT_ARDY | OMAP_I2C_STAT_NACK | OMAP_I2C_STAT_AL)) { omap_i2c_ack_stat(dev, stat & (OMAP_I2C_STAT_RRDY | OMAP_I2C_STAT_RDR | - OMAP_I2C_STAT_XRDY | OMAP_I2C_STAT_XDR)); + OMAP_I2C_STAT_XRDY | OMAP_I2C_STAT_XDR | + OMAP_I2C_STAT_ARDY)); omap_i2c_complete_cmd(dev, err); return IRQ_HANDLED; } @@ -1137,12 +1141,41 @@ omap_i2c_remove(struct platform_device *pdev) return 0; } +#ifdef CONFIG_SUSPEND +static int omap_i2c_suspend(struct device *dev) +{ + if (!pm_runtime_suspended(dev)) + if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_suspend) + dev->bus->pm->runtime_suspend(dev); + + return 0; +} + +static int omap_i2c_resume(struct device *dev) +{ + if (!pm_runtime_suspended(dev)) + if (dev->bus && dev->bus->pm && dev->bus->pm->runtime_resume) + dev->bus->pm->runtime_resume(dev); + + return 0; +} + +static struct dev_pm_ops omap_i2c_pm_ops = { + .suspend = omap_i2c_suspend, + .resume = omap_i2c_resume, +}; +#define OMAP_I2C_PM_OPS (&omap_i2c_pm_ops) +#else +#define OMAP_I2C_PM_OPS NULL +#endif + static struct platform_driver omap_i2c_driver = { .probe = omap_i2c_probe, .remove = omap_i2c_remove, .driver = { .name = "omap_i2c", .owner = THIS_MODULE, + .pm = OMAP_I2C_PM_OPS, }, }; diff --git a/drivers/i2c/busses/i2c-puv3.c b/drivers/i2c/busses/i2c-puv3.c new file mode 100644 index 000000000000..fac673940849 --- /dev/null +++ b/drivers/i2c/busses/i2c-puv3.c @@ -0,0 +1,306 @@ +/* + * I2C driver for PKUnity-v3 SoC + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/err.h> +#include <linux/slab.h> +#include <linux/types.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/io.h> +#include <mach/hardware.h> + +/* + * Poll the i2c status register until the specified bit is set. + * Returns 0 if timed out (100 msec). + */ +static short poll_status(unsigned long bit) +{ + int loop_cntr = 1000; + + if (bit & I2C_STATUS_TFNF) { + do { + udelay(10); + } while (!(readl(I2C_STATUS) & bit) && (--loop_cntr > 0)); + } else { + /* RXRDY handler */ + do { + if (readl(I2C_TAR) == I2C_TAR_EEPROM) + msleep(20); + else + udelay(10); + } while (!(readl(I2C_RXFLR) & 0xf) && (--loop_cntr > 0)); + } + + return (loop_cntr > 0); +} + +static int xfer_read(struct i2c_adapter *adap, unsigned char *buf, int length) +{ + int i2c_reg = *buf; + + /* Read data */ + while (length--) { + if (!poll_status(I2C_STATUS_TFNF)) { + dev_dbg(&adap->dev, "Tx FIFO Not Full timeout\n"); + return -ETIMEDOUT; + } + + /* send addr */ + writel(i2c_reg | I2C_DATACMD_WRITE, I2C_DATACMD); + + /* get ready to next write */ + i2c_reg++; + + /* send read CMD */ + writel(I2C_DATACMD_READ, I2C_DATACMD); + + /* wait until the Rx FIFO have available */ + if (!poll_status(I2C_STATUS_RFNE)) { + dev_dbg(&adap->dev, "RXRDY timeout\n"); + return -ETIMEDOUT; + } + + /* read the data to buf */ + *buf = (readl(I2C_DATACMD) & I2C_DATACMD_DAT_MASK); + buf++; + } + + return 0; +} + +static int xfer_write(struct i2c_adapter *adap, unsigned char *buf, int length) +{ + int i2c_reg = *buf; + + /* Do nothing but storing the reg_num to a static variable */ + if (i2c_reg == -1) { + printk(KERN_WARNING "Error i2c reg\n"); + return -ETIMEDOUT; + } + + if (length == 1) + return 0; + + buf++; + length--; + while (length--) { + /* send addr */ + writel(i2c_reg | I2C_DATACMD_WRITE, I2C_DATACMD); + + /* send write CMD */ + writel(*buf | I2C_DATACMD_WRITE, I2C_DATACMD); + + /* wait until the Rx FIFO have available */ + msleep(20); + + /* read the data to buf */ + i2c_reg++; + buf++; + } + + return 0; +} + +/* + * Generic i2c master transfer entrypoint. + * + */ +static int puv3_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *pmsg, + int num) +{ + int i, ret; + unsigned char swap; + + /* Disable i2c */ + writel(I2C_ENABLE_DISABLE, I2C_ENABLE); + + /* Set the work mode and speed*/ + writel(I2C_CON_MASTER | I2C_CON_SPEED_STD | I2C_CON_SLAVEDISABLE, I2C_CON); + + writel(pmsg->addr, I2C_TAR); + + /* Enable i2c */ + writel(I2C_ENABLE_ENABLE, I2C_ENABLE); + + dev_dbg(&adap->dev, "puv3_i2c_xfer: processing %d messages:\n", num); + + for (i = 0; i < num; i++) { + dev_dbg(&adap->dev, " #%d: %sing %d byte%s %s 0x%02x\n", i, + pmsg->flags & I2C_M_RD ? "read" : "writ", + pmsg->len, pmsg->len > 1 ? "s" : "", + pmsg->flags & I2C_M_RD ? "from" : "to", pmsg->addr); + + if (pmsg->len && pmsg->buf) { /* sanity check */ + if (pmsg->flags & I2C_M_RD) + ret = xfer_read(adap, pmsg->buf, pmsg->len); + else + ret = xfer_write(adap, pmsg->buf, pmsg->len); + + if (ret) + return ret; + + } + dev_dbg(&adap->dev, "transfer complete\n"); + pmsg++; /* next message */ + } + + /* XXX: fixup be16_to_cpu in bq27x00_battery.c */ + if (pmsg->addr == I2C_TAR_PWIC) { + swap = pmsg->buf[0]; + pmsg->buf[0] = pmsg->buf[1]; + pmsg->buf[1] = swap; + } + + return i; +} + +/* + * Return list of supported functionality. + */ +static u32 puv3_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL; +} + +static struct i2c_algorithm puv3_i2c_algorithm = { + .master_xfer = puv3_i2c_xfer, + .functionality = puv3_i2c_func, +}; + +/* + * Main initialization routine. + */ +static int __devinit puv3_i2c_probe(struct platform_device *pdev) +{ + struct i2c_adapter *adapter; + struct resource *mem; + int rc; + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!mem) + return -ENODEV; + + if (!request_mem_region(mem->start, resource_size(mem), "puv3_i2c")) + return -EBUSY; + + adapter = kzalloc(sizeof(struct i2c_adapter), GFP_KERNEL); + if (adapter == NULL) { + dev_err(&pdev->dev, "can't allocate inteface!\n"); + rc = -ENOMEM; + goto fail_nomem; + } + snprintf(adapter->name, sizeof(adapter->name), "PUV3-I2C at 0x%08x", + mem->start); + adapter->algo = &puv3_i2c_algorithm; + adapter->class = I2C_CLASS_HWMON; + adapter->dev.parent = &pdev->dev; + + platform_set_drvdata(pdev, adapter); + + adapter->nr = pdev->id; + rc = i2c_add_numbered_adapter(adapter); + if (rc) { + dev_err(&pdev->dev, "Adapter '%s' registration failed\n", + adapter->name); + goto fail_add_adapter; + } + + dev_info(&pdev->dev, "PKUnity v3 i2c bus adapter.\n"); + return 0; + +fail_add_adapter: + platform_set_drvdata(pdev, NULL); + kfree(adapter); +fail_nomem: + release_mem_region(mem->start, resource_size(mem)); + + return rc; +} + +static int __devexit puv3_i2c_remove(struct platform_device *pdev) +{ + struct i2c_adapter *adapter = platform_get_drvdata(pdev); + struct resource *mem; + int rc; + + rc = i2c_del_adapter(adapter); + if (rc) { + dev_err(&pdev->dev, "Adapter '%s' delete fail\n", + adapter->name); + return rc; + } + + put_device(&pdev->dev); + platform_set_drvdata(pdev, NULL); + + mem = platform_get_resource(pdev, IORESOURCE_MEM, 0); + release_mem_region(mem->start, resource_size(mem)); + + return rc; +} + +#ifdef CONFIG_PM +static int puv3_i2c_suspend(struct platform_device *dev, pm_message_t state) +{ + int poll_count; + /* Disable the IIC */ + writel(I2C_ENABLE_DISABLE, I2C_ENABLE); + for (poll_count = 0; poll_count < 50; poll_count++) { + if (readl(I2C_ENSTATUS) & I2C_ENSTATUS_ENABLE) + udelay(25); + } + + return 0; +} + +static int puv3_i2c_resume(struct platform_device *dev) +{ + return 0 ; +} +#else +#define puv3_i2c_suspend NULL +#define puv3_i2c_resume NULL +#endif + +MODULE_ALIAS("platform:puv3_i2c"); + +static struct platform_driver puv3_i2c_driver = { + .probe = puv3_i2c_probe, + .remove = __devexit_p(puv3_i2c_remove), + .suspend = puv3_i2c_suspend, + .resume = puv3_i2c_resume, + .driver = { + .name = "PKUnity-v3-I2C", + .owner = THIS_MODULE, + } +}; + +static int __init puv3_i2c_init(void) +{ + return platform_driver_register(&puv3_i2c_driver); +} + +static void __exit puv3_i2c_exit(void) +{ + platform_driver_unregister(&puv3_i2c_driver); +} + +module_init(puv3_i2c_init); +module_exit(puv3_i2c_exit); + +MODULE_DESCRIPTION("PKUnity v3 I2C driver"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/busses/i2c-stu300.c b/drivers/i2c/busses/i2c-stu300.c index 495be451d326..266135ddf7fa 100644 --- a/drivers/i2c/busses/i2c-stu300.c +++ b/drivers/i2c/busses/i2c-stu300.c @@ -942,7 +942,7 @@ stu300_probe(struct platform_device *pdev) adap->owner = THIS_MODULE; /* DDC class but actually often used for more generic I2C */ adap->class = I2C_CLASS_DDC; - strncpy(adap->name, "ST Microelectronics DDC I2C adapter", + strlcpy(adap->name, "ST Microelectronics DDC I2C adapter", sizeof(adap->name)); adap->nr = bus_nr; adap->algo = &stu300_algo; diff --git a/drivers/i2c/busses/i2c-tegra.c b/drivers/i2c/busses/i2c-tegra.c new file mode 100644 index 000000000000..3921f664c9c3 --- /dev/null +++ b/drivers/i2c/busses/i2c-tegra.c @@ -0,0 +1,700 @@ +/* + * drivers/i2c/busses/i2c-tegra.c + * + * Copyright (C) 2010 Google, Inc. + * Author: Colin Cross <ccross@android.com> + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/init.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/i2c.h> +#include <linux/io.h> +#include <linux/interrupt.h> +#include <linux/delay.h> +#include <linux/slab.h> +#include <linux/i2c-tegra.h> + +#include <asm/unaligned.h> + +#include <mach/clk.h> + +#define TEGRA_I2C_TIMEOUT (msecs_to_jiffies(1000)) +#define BYTES_PER_FIFO_WORD 4 + +#define I2C_CNFG 0x000 +#define I2C_CNFG_PACKET_MODE_EN (1<<10) +#define I2C_CNFG_NEW_MASTER_FSM (1<<11) +#define I2C_SL_CNFG 0x020 +#define I2C_SL_CNFG_NEWSL (1<<2) +#define I2C_SL_ADDR1 0x02c +#define I2C_TX_FIFO 0x050 +#define I2C_RX_FIFO 0x054 +#define I2C_PACKET_TRANSFER_STATUS 0x058 +#define I2C_FIFO_CONTROL 0x05c +#define I2C_FIFO_CONTROL_TX_FLUSH (1<<1) +#define I2C_FIFO_CONTROL_RX_FLUSH (1<<0) +#define I2C_FIFO_CONTROL_TX_TRIG_SHIFT 5 +#define I2C_FIFO_CONTROL_RX_TRIG_SHIFT 2 +#define I2C_FIFO_STATUS 0x060 +#define I2C_FIFO_STATUS_TX_MASK 0xF0 +#define I2C_FIFO_STATUS_TX_SHIFT 4 +#define I2C_FIFO_STATUS_RX_MASK 0x0F +#define I2C_FIFO_STATUS_RX_SHIFT 0 +#define I2C_INT_MASK 0x064 +#define I2C_INT_STATUS 0x068 +#define I2C_INT_PACKET_XFER_COMPLETE (1<<7) +#define I2C_INT_ALL_PACKETS_XFER_COMPLETE (1<<6) +#define I2C_INT_TX_FIFO_OVERFLOW (1<<5) +#define I2C_INT_RX_FIFO_UNDERFLOW (1<<4) +#define I2C_INT_NO_ACK (1<<3) +#define I2C_INT_ARBITRATION_LOST (1<<2) +#define I2C_INT_TX_FIFO_DATA_REQ (1<<1) +#define I2C_INT_RX_FIFO_DATA_REQ (1<<0) +#define I2C_CLK_DIVISOR 0x06c + +#define DVC_CTRL_REG1 0x000 +#define DVC_CTRL_REG1_INTR_EN (1<<10) +#define DVC_CTRL_REG2 0x004 +#define DVC_CTRL_REG3 0x008 +#define DVC_CTRL_REG3_SW_PROG (1<<26) +#define DVC_CTRL_REG3_I2C_DONE_INTR_EN (1<<30) +#define DVC_STATUS 0x00c +#define DVC_STATUS_I2C_DONE_INTR (1<<30) + +#define I2C_ERR_NONE 0x00 +#define I2C_ERR_NO_ACK 0x01 +#define I2C_ERR_ARBITRATION_LOST 0x02 + +#define PACKET_HEADER0_HEADER_SIZE_SHIFT 28 +#define PACKET_HEADER0_PACKET_ID_SHIFT 16 +#define PACKET_HEADER0_CONT_ID_SHIFT 12 +#define PACKET_HEADER0_PROTOCOL_I2C (1<<4) + +#define I2C_HEADER_HIGHSPEED_MODE (1<<22) +#define I2C_HEADER_CONT_ON_NAK (1<<21) +#define I2C_HEADER_SEND_START_BYTE (1<<20) +#define I2C_HEADER_READ (1<<19) +#define I2C_HEADER_10BIT_ADDR (1<<18) +#define I2C_HEADER_IE_ENABLE (1<<17) +#define I2C_HEADER_REPEAT_START (1<<16) +#define I2C_HEADER_MASTER_ADDR_SHIFT 12 +#define I2C_HEADER_SLAVE_ADDR_SHIFT 1 + +/** + * struct tegra_i2c_dev - per device i2c context + * @dev: device reference for power management + * @adapter: core i2c layer adapter information + * @clk: clock reference for i2c controller + * @i2c_clk: clock reference for i2c bus + * @iomem: memory resource for registers + * @base: ioremapped registers cookie + * @cont_id: i2c controller id, used for for packet header + * @irq: irq number of transfer complete interrupt + * @is_dvc: identifies the DVC i2c controller, has a different register layout + * @msg_complete: transfer completion notifier + * @msg_err: error code for completed message + * @msg_buf: pointer to current message data + * @msg_buf_remaining: size of unsent data in the message buffer + * @msg_read: identifies read transfers + * @bus_clk_rate: current i2c bus clock rate + * @is_suspended: prevents i2c controller accesses after suspend is called + */ +struct tegra_i2c_dev { + struct device *dev; + struct i2c_adapter adapter; + struct clk *clk; + struct clk *i2c_clk; + struct resource *iomem; + void __iomem *base; + int cont_id; + int irq; + int is_dvc; + struct completion msg_complete; + int msg_err; + u8 *msg_buf; + size_t msg_buf_remaining; + int msg_read; + unsigned long bus_clk_rate; + bool is_suspended; +}; + +static void dvc_writel(struct tegra_i2c_dev *i2c_dev, u32 val, unsigned long reg) +{ + writel(val, i2c_dev->base + reg); +} + +static u32 dvc_readl(struct tegra_i2c_dev *i2c_dev, unsigned long reg) +{ + return readl(i2c_dev->base + reg); +} + +/* + * i2c_writel and i2c_readl will offset the register if necessary to talk + * to the I2C block inside the DVC block + */ +static unsigned long tegra_i2c_reg_addr(struct tegra_i2c_dev *i2c_dev, + unsigned long reg) +{ + if (i2c_dev->is_dvc) + reg += (reg >= I2C_TX_FIFO) ? 0x10 : 0x40; + return reg; +} + +static void i2c_writel(struct tegra_i2c_dev *i2c_dev, u32 val, + unsigned long reg) +{ + writel(val, i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg)); +} + +static u32 i2c_readl(struct tegra_i2c_dev *i2c_dev, unsigned long reg) +{ + return readl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg)); +} + +static void i2c_writesl(struct tegra_i2c_dev *i2c_dev, void *data, + unsigned long reg, int len) +{ + writesl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg), data, len); +} + +static void i2c_readsl(struct tegra_i2c_dev *i2c_dev, void *data, + unsigned long reg, int len) +{ + readsl(i2c_dev->base + tegra_i2c_reg_addr(i2c_dev, reg), data, len); +} + +static void tegra_i2c_mask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) +{ + u32 int_mask = i2c_readl(i2c_dev, I2C_INT_MASK); + int_mask &= ~mask; + i2c_writel(i2c_dev, int_mask, I2C_INT_MASK); +} + +static void tegra_i2c_unmask_irq(struct tegra_i2c_dev *i2c_dev, u32 mask) +{ + u32 int_mask = i2c_readl(i2c_dev, I2C_INT_MASK); + int_mask |= mask; + i2c_writel(i2c_dev, int_mask, I2C_INT_MASK); +} + +static int tegra_i2c_flush_fifos(struct tegra_i2c_dev *i2c_dev) +{ + unsigned long timeout = jiffies + HZ; + u32 val = i2c_readl(i2c_dev, I2C_FIFO_CONTROL); + val |= I2C_FIFO_CONTROL_TX_FLUSH | I2C_FIFO_CONTROL_RX_FLUSH; + i2c_writel(i2c_dev, val, I2C_FIFO_CONTROL); + + while (i2c_readl(i2c_dev, I2C_FIFO_CONTROL) & + (I2C_FIFO_CONTROL_TX_FLUSH | I2C_FIFO_CONTROL_RX_FLUSH)) { + if (time_after(jiffies, timeout)) { + dev_warn(i2c_dev->dev, "timeout waiting for fifo flush\n"); + return -ETIMEDOUT; + } + msleep(1); + } + return 0; +} + +static int tegra_i2c_empty_rx_fifo(struct tegra_i2c_dev *i2c_dev) +{ + u32 val; + int rx_fifo_avail; + u8 *buf = i2c_dev->msg_buf; + size_t buf_remaining = i2c_dev->msg_buf_remaining; + int words_to_transfer; + + val = i2c_readl(i2c_dev, I2C_FIFO_STATUS); + rx_fifo_avail = (val & I2C_FIFO_STATUS_RX_MASK) >> + I2C_FIFO_STATUS_RX_SHIFT; + + /* Rounds down to not include partial word at the end of buf */ + words_to_transfer = buf_remaining / BYTES_PER_FIFO_WORD; + if (words_to_transfer > rx_fifo_avail) + words_to_transfer = rx_fifo_avail; + + i2c_readsl(i2c_dev, buf, I2C_RX_FIFO, words_to_transfer); + + buf += words_to_transfer * BYTES_PER_FIFO_WORD; + buf_remaining -= words_to_transfer * BYTES_PER_FIFO_WORD; + rx_fifo_avail -= words_to_transfer; + + /* + * If there is a partial word at the end of buf, handle it manually to + * prevent overwriting past the end of buf + */ + if (rx_fifo_avail > 0 && buf_remaining > 0) { + BUG_ON(buf_remaining > 3); + val = i2c_readl(i2c_dev, I2C_RX_FIFO); + memcpy(buf, &val, buf_remaining); + buf_remaining = 0; + rx_fifo_avail--; + } + + BUG_ON(rx_fifo_avail > 0 && buf_remaining > 0); + i2c_dev->msg_buf_remaining = buf_remaining; + i2c_dev->msg_buf = buf; + return 0; +} + +static int tegra_i2c_fill_tx_fifo(struct tegra_i2c_dev *i2c_dev) +{ + u32 val; + int tx_fifo_avail; + u8 *buf = i2c_dev->msg_buf; + size_t buf_remaining = i2c_dev->msg_buf_remaining; + int words_to_transfer; + + val = i2c_readl(i2c_dev, I2C_FIFO_STATUS); + tx_fifo_avail = (val & I2C_FIFO_STATUS_TX_MASK) >> + I2C_FIFO_STATUS_TX_SHIFT; + + /* Rounds down to not include partial word at the end of buf */ + words_to_transfer = buf_remaining / BYTES_PER_FIFO_WORD; + if (words_to_transfer > tx_fifo_avail) + words_to_transfer = tx_fifo_avail; + + i2c_writesl(i2c_dev, buf, I2C_TX_FIFO, words_to_transfer); + + buf += words_to_transfer * BYTES_PER_FIFO_WORD; + buf_remaining -= words_to_transfer * BYTES_PER_FIFO_WORD; + tx_fifo_avail -= words_to_transfer; + + /* + * If there is a partial word at the end of buf, handle it manually to + * prevent reading past the end of buf, which could cross a page + * boundary and fault. + */ + if (tx_fifo_avail > 0 && buf_remaining > 0) { + BUG_ON(buf_remaining > 3); + memcpy(&val, buf, buf_remaining); + i2c_writel(i2c_dev, val, I2C_TX_FIFO); + buf_remaining = 0; + tx_fifo_avail--; + } + + BUG_ON(tx_fifo_avail > 0 && buf_remaining > 0); + i2c_dev->msg_buf_remaining = buf_remaining; + i2c_dev->msg_buf = buf; + return 0; +} + +/* + * One of the Tegra I2C blocks is inside the DVC (Digital Voltage Controller) + * block. This block is identical to the rest of the I2C blocks, except that + * it only supports master mode, it has registers moved around, and it needs + * some extra init to get it into I2C mode. The register moves are handled + * by i2c_readl and i2c_writel + */ +static void tegra_dvc_init(struct tegra_i2c_dev *i2c_dev) +{ + u32 val = 0; + val = dvc_readl(i2c_dev, DVC_CTRL_REG3); + val |= DVC_CTRL_REG3_SW_PROG; + val |= DVC_CTRL_REG3_I2C_DONE_INTR_EN; + dvc_writel(i2c_dev, val, DVC_CTRL_REG3); + + val = dvc_readl(i2c_dev, DVC_CTRL_REG1); + val |= DVC_CTRL_REG1_INTR_EN; + dvc_writel(i2c_dev, val, DVC_CTRL_REG1); +} + +static int tegra_i2c_init(struct tegra_i2c_dev *i2c_dev) +{ + u32 val; + int err = 0; + + clk_enable(i2c_dev->clk); + + tegra_periph_reset_assert(i2c_dev->clk); + udelay(2); + tegra_periph_reset_deassert(i2c_dev->clk); + + if (i2c_dev->is_dvc) + tegra_dvc_init(i2c_dev); + + val = I2C_CNFG_NEW_MASTER_FSM | I2C_CNFG_PACKET_MODE_EN; + i2c_writel(i2c_dev, val, I2C_CNFG); + i2c_writel(i2c_dev, 0, I2C_INT_MASK); + clk_set_rate(i2c_dev->clk, i2c_dev->bus_clk_rate * 8); + + val = 7 << I2C_FIFO_CONTROL_TX_TRIG_SHIFT | + 0 << I2C_FIFO_CONTROL_RX_TRIG_SHIFT; + i2c_writel(i2c_dev, val, I2C_FIFO_CONTROL); + + if (tegra_i2c_flush_fifos(i2c_dev)) + err = -ETIMEDOUT; + + clk_disable(i2c_dev->clk); + return err; +} + +static irqreturn_t tegra_i2c_isr(int irq, void *dev_id) +{ + u32 status; + const u32 status_err = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST; + struct tegra_i2c_dev *i2c_dev = dev_id; + + status = i2c_readl(i2c_dev, I2C_INT_STATUS); + + if (status == 0) { + dev_warn(i2c_dev->dev, "interrupt with no status\n"); + return IRQ_NONE; + } + + if (unlikely(status & status_err)) { + if (status & I2C_INT_NO_ACK) + i2c_dev->msg_err |= I2C_ERR_NO_ACK; + if (status & I2C_INT_ARBITRATION_LOST) + i2c_dev->msg_err |= I2C_ERR_ARBITRATION_LOST; + complete(&i2c_dev->msg_complete); + goto err; + } + + if (i2c_dev->msg_read && (status & I2C_INT_RX_FIFO_DATA_REQ)) { + if (i2c_dev->msg_buf_remaining) + tegra_i2c_empty_rx_fifo(i2c_dev); + else + BUG(); + } + + if (!i2c_dev->msg_read && (status & I2C_INT_TX_FIFO_DATA_REQ)) { + if (i2c_dev->msg_buf_remaining) + tegra_i2c_fill_tx_fifo(i2c_dev); + else + tegra_i2c_mask_irq(i2c_dev, I2C_INT_TX_FIFO_DATA_REQ); + } + + if ((status & I2C_INT_PACKET_XFER_COMPLETE) && + !i2c_dev->msg_buf_remaining) + complete(&i2c_dev->msg_complete); + + i2c_writel(i2c_dev, status, I2C_INT_STATUS); + if (i2c_dev->is_dvc) + dvc_writel(i2c_dev, DVC_STATUS_I2C_DONE_INTR, DVC_STATUS); + return IRQ_HANDLED; +err: + /* An error occured, mask all interrupts */ + tegra_i2c_mask_irq(i2c_dev, I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST | + I2C_INT_PACKET_XFER_COMPLETE | I2C_INT_TX_FIFO_DATA_REQ | + I2C_INT_RX_FIFO_DATA_REQ); + i2c_writel(i2c_dev, status, I2C_INT_STATUS); + return IRQ_HANDLED; +} + +static int tegra_i2c_xfer_msg(struct tegra_i2c_dev *i2c_dev, + struct i2c_msg *msg, int stop) +{ + u32 packet_header; + u32 int_mask; + int ret; + + tegra_i2c_flush_fifos(i2c_dev); + i2c_writel(i2c_dev, 0xFF, I2C_INT_STATUS); + + if (msg->len == 0) + return -EINVAL; + + i2c_dev->msg_buf = msg->buf; + i2c_dev->msg_buf_remaining = msg->len; + i2c_dev->msg_err = I2C_ERR_NONE; + i2c_dev->msg_read = (msg->flags & I2C_M_RD); + INIT_COMPLETION(i2c_dev->msg_complete); + + packet_header = (0 << PACKET_HEADER0_HEADER_SIZE_SHIFT) | + PACKET_HEADER0_PROTOCOL_I2C | + (i2c_dev->cont_id << PACKET_HEADER0_CONT_ID_SHIFT) | + (1 << PACKET_HEADER0_PACKET_ID_SHIFT); + i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO); + + packet_header = msg->len - 1; + i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO); + + packet_header = msg->addr << I2C_HEADER_SLAVE_ADDR_SHIFT; + packet_header |= I2C_HEADER_IE_ENABLE; + if (msg->flags & I2C_M_TEN) + packet_header |= I2C_HEADER_10BIT_ADDR; + if (msg->flags & I2C_M_IGNORE_NAK) + packet_header |= I2C_HEADER_CONT_ON_NAK; + if (msg->flags & I2C_M_NOSTART) + packet_header |= I2C_HEADER_REPEAT_START; + if (msg->flags & I2C_M_RD) + packet_header |= I2C_HEADER_READ; + i2c_writel(i2c_dev, packet_header, I2C_TX_FIFO); + + if (!(msg->flags & I2C_M_RD)) + tegra_i2c_fill_tx_fifo(i2c_dev); + + int_mask = I2C_INT_NO_ACK | I2C_INT_ARBITRATION_LOST; + if (msg->flags & I2C_M_RD) + int_mask |= I2C_INT_RX_FIFO_DATA_REQ; + else if (i2c_dev->msg_buf_remaining) + int_mask |= I2C_INT_TX_FIFO_DATA_REQ; + tegra_i2c_unmask_irq(i2c_dev, int_mask); + dev_dbg(i2c_dev->dev, "unmasked irq: %02x\n", + i2c_readl(i2c_dev, I2C_INT_MASK)); + + ret = wait_for_completion_timeout(&i2c_dev->msg_complete, TEGRA_I2C_TIMEOUT); + tegra_i2c_mask_irq(i2c_dev, int_mask); + + if (WARN_ON(ret == 0)) { + dev_err(i2c_dev->dev, "i2c transfer timed out\n"); + + tegra_i2c_init(i2c_dev); + return -ETIMEDOUT; + } + + dev_dbg(i2c_dev->dev, "transfer complete: %d %d %d\n", + ret, completion_done(&i2c_dev->msg_complete), i2c_dev->msg_err); + + if (likely(i2c_dev->msg_err == I2C_ERR_NONE)) + return 0; + + tegra_i2c_init(i2c_dev); + if (i2c_dev->msg_err == I2C_ERR_NO_ACK) { + if (msg->flags & I2C_M_IGNORE_NAK) + return 0; + return -EREMOTEIO; + } + + return -EIO; +} + +static int tegra_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg msgs[], + int num) +{ + struct tegra_i2c_dev *i2c_dev = i2c_get_adapdata(adap); + int i; + int ret = 0; + + if (i2c_dev->is_suspended) + return -EBUSY; + + clk_enable(i2c_dev->clk); + for (i = 0; i < num; i++) { + int stop = (i == (num - 1)) ? 1 : 0; + ret = tegra_i2c_xfer_msg(i2c_dev, &msgs[i], stop); + if (ret) + break; + } + clk_disable(i2c_dev->clk); + return ret ?: i; +} + +static u32 tegra_i2c_func(struct i2c_adapter *adap) +{ + return I2C_FUNC_I2C; +} + +static const struct i2c_algorithm tegra_i2c_algo = { + .master_xfer = tegra_i2c_xfer, + .functionality = tegra_i2c_func, +}; + +static int tegra_i2c_probe(struct platform_device *pdev) +{ + struct tegra_i2c_dev *i2c_dev; + struct tegra_i2c_platform_data *pdata = pdev->dev.platform_data; + struct resource *res; + struct resource *iomem; + struct clk *clk; + struct clk *i2c_clk; + void *base; + int irq; + int ret = 0; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no mem resource\n"); + return -EINVAL; + } + iomem = request_mem_region(res->start, resource_size(res), pdev->name); + if (!iomem) { + dev_err(&pdev->dev, "I2C region already claimed\n"); + return -EBUSY; + } + + base = ioremap(iomem->start, resource_size(iomem)); + if (!base) { + dev_err(&pdev->dev, "Cannot ioremap I2C region\n"); + return -ENOMEM; + } + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, "no irq resource\n"); + ret = -EINVAL; + goto err_iounmap; + } + irq = res->start; + + clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(clk)) { + dev_err(&pdev->dev, "missing controller clock"); + ret = PTR_ERR(clk); + goto err_release_region; + } + + i2c_clk = clk_get(&pdev->dev, "i2c"); + if (IS_ERR(i2c_clk)) { + dev_err(&pdev->dev, "missing bus clock"); + ret = PTR_ERR(i2c_clk); + goto err_clk_put; + } + + i2c_dev = kzalloc(sizeof(struct tegra_i2c_dev), GFP_KERNEL); + if (!i2c_dev) { + ret = -ENOMEM; + goto err_i2c_clk_put; + } + + i2c_dev->base = base; + i2c_dev->clk = clk; + i2c_dev->i2c_clk = i2c_clk; + i2c_dev->iomem = iomem; + i2c_dev->adapter.algo = &tegra_i2c_algo; + i2c_dev->irq = irq; + i2c_dev->cont_id = pdev->id; + i2c_dev->dev = &pdev->dev; + i2c_dev->bus_clk_rate = pdata ? pdata->bus_clk_rate : 100000; + + if (pdev->id == 3) + i2c_dev->is_dvc = 1; + init_completion(&i2c_dev->msg_complete); + + platform_set_drvdata(pdev, i2c_dev); + + ret = tegra_i2c_init(i2c_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to initialize i2c controller"); + goto err_free; + } + + ret = request_irq(i2c_dev->irq, tegra_i2c_isr, 0, pdev->name, i2c_dev); + if (ret) { + dev_err(&pdev->dev, "Failed to request irq %i\n", i2c_dev->irq); + goto err_free; + } + + clk_enable(i2c_dev->i2c_clk); + + i2c_set_adapdata(&i2c_dev->adapter, i2c_dev); + i2c_dev->adapter.owner = THIS_MODULE; + i2c_dev->adapter.class = I2C_CLASS_HWMON; + strlcpy(i2c_dev->adapter.name, "Tegra I2C adapter", + sizeof(i2c_dev->adapter.name)); + i2c_dev->adapter.algo = &tegra_i2c_algo; + i2c_dev->adapter.dev.parent = &pdev->dev; + i2c_dev->adapter.nr = pdev->id; + + ret = i2c_add_numbered_adapter(&i2c_dev->adapter); + if (ret) { + dev_err(&pdev->dev, "Failed to add I2C adapter\n"); + goto err_free_irq; + } + + return 0; +err_free_irq: + free_irq(i2c_dev->irq, i2c_dev); +err_free: + kfree(i2c_dev); +err_i2c_clk_put: + clk_put(i2c_clk); +err_clk_put: + clk_put(clk); +err_release_region: + release_mem_region(iomem->start, resource_size(iomem)); +err_iounmap: + iounmap(base); + return ret; +} + +static int tegra_i2c_remove(struct platform_device *pdev) +{ + struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + i2c_del_adapter(&i2c_dev->adapter); + free_irq(i2c_dev->irq, i2c_dev); + clk_put(i2c_dev->i2c_clk); + clk_put(i2c_dev->clk); + release_mem_region(i2c_dev->iomem->start, + resource_size(i2c_dev->iomem)); + iounmap(i2c_dev->base); + kfree(i2c_dev); + return 0; +} + +#ifdef CONFIG_PM +static int tegra_i2c_suspend(struct platform_device *pdev, pm_message_t state) +{ + struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + + i2c_lock_adapter(&i2c_dev->adapter); + i2c_dev->is_suspended = true; + i2c_unlock_adapter(&i2c_dev->adapter); + + return 0; +} + +static int tegra_i2c_resume(struct platform_device *pdev) +{ + struct tegra_i2c_dev *i2c_dev = platform_get_drvdata(pdev); + int ret; + + i2c_lock_adapter(&i2c_dev->adapter); + + ret = tegra_i2c_init(i2c_dev); + + if (ret) { + i2c_unlock_adapter(&i2c_dev->adapter); + return ret; + } + + i2c_dev->is_suspended = false; + + i2c_unlock_adapter(&i2c_dev->adapter); + + return 0; +} +#endif + +static struct platform_driver tegra_i2c_driver = { + .probe = tegra_i2c_probe, + .remove = tegra_i2c_remove, +#ifdef CONFIG_PM + .suspend = tegra_i2c_suspend, + .resume = tegra_i2c_resume, +#endif + .driver = { + .name = "tegra-i2c", + .owner = THIS_MODULE, + }, +}; + +static int __init tegra_i2c_init_driver(void) +{ + return platform_driver_register(&tegra_i2c_driver); +} + +static void __exit tegra_i2c_exit_driver(void) +{ + platform_driver_unregister(&tegra_i2c_driver); +} + +subsys_initcall(tegra_i2c_init_driver); +module_exit(tegra_i2c_exit_driver); + +MODULE_DESCRIPTION("nVidia Tegra2 I2C Bus Controller driver"); +MODULE_AUTHOR("Colin Cross"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/i2c/i2c-boardinfo.c b/drivers/i2c/i2c-boardinfo.c index 7e6a63b57165..3ca2e012e789 100644 --- a/drivers/i2c/i2c-boardinfo.c +++ b/drivers/i2c/i2c-boardinfo.c @@ -1,5 +1,5 @@ /* - * i2c-boardinfo.h - collect pre-declarations of I2C devices + * i2c-boardinfo.c - collect pre-declarations of I2C devices * * 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 diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index f0bd5bcdf563..fc3638794b8c 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -799,6 +799,9 @@ static int i2c_do_add_adapter(struct i2c_driver *driver, /* Let legacy drivers scan this bus for matching devices */ if (driver->attach_adapter) { + dev_warn(&adap->dev, "attach_adapter method is deprecated\n"); + dev_warn(&adap->dev, "Please use another way to instantiate " + "your i2c_client\n"); /* We ignore the return code; if it fails, too bad */ driver->attach_adapter(adap); } @@ -983,6 +986,7 @@ static int i2c_do_del_adapter(struct i2c_driver *driver, if (!driver->detach_adapter) return 0; + dev_warn(&adapter->dev, "detach_adapter method is deprecated\n"); res = driver->detach_adapter(adapter); if (res) dev_err(&adapter->dev, "detach_adapter failed (%d) " @@ -1093,6 +1097,18 @@ EXPORT_SYMBOL(i2c_del_adapter); /* ------------------------------------------------------------------------- */ +int i2c_for_each_dev(void *data, int (*fn)(struct device *, void *)) +{ + int res; + + mutex_lock(&core_lock); + res = bus_for_each_dev(&i2c_bus_type, NULL, data, fn); + mutex_unlock(&core_lock); + + return res; +} +EXPORT_SYMBOL_GPL(i2c_for_each_dev); + static int __process_new_driver(struct device *dev, void *data) { if (dev->type != &i2c_adapter_type) @@ -1136,9 +1152,7 @@ int i2c_register_driver(struct module *owner, struct i2c_driver *driver) INIT_LIST_HEAD(&driver->clients); /* Walk the adapters that are already present */ - mutex_lock(&core_lock); - bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_new_driver); - mutex_unlock(&core_lock); + i2c_for_each_dev(driver, __process_new_driver); return 0; } @@ -1158,9 +1172,7 @@ static int __process_removed_driver(struct device *dev, void *data) */ void i2c_del_driver(struct i2c_driver *driver) { - mutex_lock(&core_lock); - bus_for_each_dev(&i2c_bus_type, NULL, driver, __process_removed_driver); - mutex_unlock(&core_lock); + i2c_for_each_dev(driver, __process_removed_driver); driver_unregister(&driver->driver); pr_debug("i2c-core: driver [%s] unregistered\n", driver->driver.name); @@ -1583,12 +1595,12 @@ i2c_new_probed_device(struct i2c_adapter *adap, } EXPORT_SYMBOL_GPL(i2c_new_probed_device); -struct i2c_adapter *i2c_get_adapter(int id) +struct i2c_adapter *i2c_get_adapter(int nr) { struct i2c_adapter *adapter; mutex_lock(&core_lock); - adapter = idr_find(&i2c_adapter_idr, id); + adapter = idr_find(&i2c_adapter_idr, nr); if (adapter && !try_module_get(adapter->owner)) adapter = NULL; diff --git a/drivers/i2c/i2c-dev.c b/drivers/i2c/i2c-dev.c index cec0f3ba97f8..66ac71800a06 100644 --- a/drivers/i2c/i2c-dev.c +++ b/drivers/i2c/i2c-dev.c @@ -28,6 +28,8 @@ #include <linux/kernel.h> #include <linux/module.h> +#include <linux/device.h> +#include <linux/notifier.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/init.h> @@ -37,16 +39,13 @@ #include <linux/jiffies.h> #include <linux/uaccess.h> -static struct i2c_driver i2cdev_driver; - /* * An i2c_dev represents an i2c_adapter ... an I2C or SMBus master, not a * slave (i2c_client) with which messages will be exchanged. It's coupled * with a character special file which is accessed by user mode drivers. * * The list of i2c_dev structures is parallel to the i2c_adapter lists - * maintained by the driver model, and is updated using notifications - * delivered to the i2cdev_driver. + * maintained by the driver model, and is updated using bus notifications. */ struct i2c_dev { struct list_head list; @@ -491,7 +490,6 @@ static int i2cdev_open(struct inode *inode, struct file *file) return -ENOMEM; } snprintf(client->name, I2C_NAME_SIZE, "i2c-dev %d", adap->nr); - client->driver = &i2cdev_driver; client->adapter = adap; file->private_data = client; @@ -522,19 +520,18 @@ static const struct file_operations i2cdev_fops = { /* ------------------------------------------------------------------------- */ -/* - * The legacy "i2cdev_driver" is used primarily to get notifications when - * I2C adapters are added or removed, so that each one gets an i2c_dev - * and is thus made available to userspace driver code. - */ - static struct class *i2c_dev_class; -static int i2cdev_attach_adapter(struct i2c_adapter *adap) +static int i2cdev_attach_adapter(struct device *dev, void *dummy) { + struct i2c_adapter *adap; struct i2c_dev *i2c_dev; int res; + if (dev->type != &i2c_adapter_type) + return 0; + adap = to_i2c_adapter(dev); + i2c_dev = get_free_i2c_dev(adap); if (IS_ERR(i2c_dev)) return PTR_ERR(i2c_dev); @@ -561,10 +558,15 @@ error: return res; } -static int i2cdev_detach_adapter(struct i2c_adapter *adap) +static int i2cdev_detach_adapter(struct device *dev, void *dummy) { + struct i2c_adapter *adap; struct i2c_dev *i2c_dev; + if (dev->type != &i2c_adapter_type) + return 0; + adap = to_i2c_adapter(dev); + i2c_dev = i2c_dev_get_by_minor(adap->nr); if (!i2c_dev) /* attach_adapter must have failed */ return 0; @@ -577,12 +579,23 @@ static int i2cdev_detach_adapter(struct i2c_adapter *adap) return 0; } -static struct i2c_driver i2cdev_driver = { - .driver = { - .name = "dev_driver", - }, - .attach_adapter = i2cdev_attach_adapter, - .detach_adapter = i2cdev_detach_adapter, +int i2cdev_notifier_call(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + return i2cdev_attach_adapter(dev, NULL); + case BUS_NOTIFY_DEL_DEVICE: + return i2cdev_detach_adapter(dev, NULL); + } + + return 0; +} + +static struct notifier_block i2cdev_notifier = { + .notifier_call = i2cdev_notifier_call, }; /* ------------------------------------------------------------------------- */ @@ -607,10 +620,14 @@ static int __init i2c_dev_init(void) goto out_unreg_chrdev; } - res = i2c_add_driver(&i2cdev_driver); + /* Keep track of adapters which will be added or removed later */ + res = bus_register_notifier(&i2c_bus_type, &i2cdev_notifier); if (res) goto out_unreg_class; + /* Bind to already existing adapters right away */ + i2c_for_each_dev(NULL, i2cdev_attach_adapter); + return 0; out_unreg_class: @@ -624,7 +641,8 @@ out: static void __exit i2c_dev_exit(void) { - i2c_del_driver(&i2cdev_driver); + i2c_for_each_dev(NULL, i2cdev_detach_adapter); + bus_unregister_notifier(&i2c_bus_type, &i2cdev_notifier); class_destroy(i2c_dev_class); unregister_chrdev(I2C_MAJOR, "i2c"); } diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index 1fa091e05690..a46dddf61078 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -62,6 +62,7 @@ #include <linux/notifier.h> #include <linux/cpu.h> #include <asm/mwait.h> +#include <asm/msr.h> #define INTEL_IDLE_VERSION "0.4" #define PREFIX "intel_idle: " @@ -85,6 +86,12 @@ static int intel_idle(struct cpuidle_device *dev, struct cpuidle_state *state); static struct cpuidle_state *cpuidle_state_table; /* + * Hardware C-state auto-demotion may not always be optimal. + * Indicate which enable bits to clear here. + */ +static unsigned long long auto_demotion_disable_flags; + +/* * Set this flag for states where the HW flushes the TLB for us * and so we don't need cross-calls to keep it consistent. * If this flag is set, SW flushes the TLB, so even if the @@ -100,7 +107,7 @@ static struct cpuidle_state *cpuidle_state_table; static struct cpuidle_state nehalem_cstates[MWAIT_MAX_NUM_CSTATES] = { { /* MWAIT C0 */ }, { /* MWAIT C1 */ - .name = "NHM-C1", + .name = "C1-NHM", .desc = "MWAIT 0x00", .driver_data = (void *) 0x00, .flags = CPUIDLE_FLAG_TIME_VALID, @@ -108,7 +115,7 @@ static struct cpuidle_state nehalem_cstates[MWAIT_MAX_NUM_CSTATES] = { .target_residency = 6, .enter = &intel_idle }, { /* MWAIT C2 */ - .name = "NHM-C3", + .name = "C3-NHM", .desc = "MWAIT 0x10", .driver_data = (void *) 0x10, .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED, @@ -116,7 +123,7 @@ static struct cpuidle_state nehalem_cstates[MWAIT_MAX_NUM_CSTATES] = { .target_residency = 80, .enter = &intel_idle }, { /* MWAIT C3 */ - .name = "NHM-C6", + .name = "C6-NHM", .desc = "MWAIT 0x20", .driver_data = (void *) 0x20, .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED, @@ -128,7 +135,7 @@ static struct cpuidle_state nehalem_cstates[MWAIT_MAX_NUM_CSTATES] = { static struct cpuidle_state snb_cstates[MWAIT_MAX_NUM_CSTATES] = { { /* MWAIT C0 */ }, { /* MWAIT C1 */ - .name = "SNB-C1", + .name = "C1-SNB", .desc = "MWAIT 0x00", .driver_data = (void *) 0x00, .flags = CPUIDLE_FLAG_TIME_VALID, @@ -136,7 +143,7 @@ static struct cpuidle_state snb_cstates[MWAIT_MAX_NUM_CSTATES] = { .target_residency = 1, .enter = &intel_idle }, { /* MWAIT C2 */ - .name = "SNB-C3", + .name = "C3-SNB", .desc = "MWAIT 0x10", .driver_data = (void *) 0x10, .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED, @@ -144,7 +151,7 @@ static struct cpuidle_state snb_cstates[MWAIT_MAX_NUM_CSTATES] = { .target_residency = 211, .enter = &intel_idle }, { /* MWAIT C3 */ - .name = "SNB-C6", + .name = "C6-SNB", .desc = "MWAIT 0x20", .driver_data = (void *) 0x20, .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED, @@ -152,7 +159,7 @@ static struct cpuidle_state snb_cstates[MWAIT_MAX_NUM_CSTATES] = { .target_residency = 345, .enter = &intel_idle }, { /* MWAIT C4 */ - .name = "SNB-C7", + .name = "C7-SNB", .desc = "MWAIT 0x30", .driver_data = (void *) 0x30, .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED, @@ -164,7 +171,7 @@ static struct cpuidle_state snb_cstates[MWAIT_MAX_NUM_CSTATES] = { static struct cpuidle_state atom_cstates[MWAIT_MAX_NUM_CSTATES] = { { /* MWAIT C0 */ }, { /* MWAIT C1 */ - .name = "ATM-C1", + .name = "C1-ATM", .desc = "MWAIT 0x00", .driver_data = (void *) 0x00, .flags = CPUIDLE_FLAG_TIME_VALID, @@ -172,7 +179,7 @@ static struct cpuidle_state atom_cstates[MWAIT_MAX_NUM_CSTATES] = { .target_residency = 4, .enter = &intel_idle }, { /* MWAIT C2 */ - .name = "ATM-C2", + .name = "C2-ATM", .desc = "MWAIT 0x10", .driver_data = (void *) 0x10, .flags = CPUIDLE_FLAG_TIME_VALID, @@ -181,7 +188,7 @@ static struct cpuidle_state atom_cstates[MWAIT_MAX_NUM_CSTATES] = { .enter = &intel_idle }, { /* MWAIT C3 */ }, { /* MWAIT C4 */ - .name = "ATM-C4", + .name = "C4-ATM", .desc = "MWAIT 0x30", .driver_data = (void *) 0x30, .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED, @@ -190,7 +197,7 @@ static struct cpuidle_state atom_cstates[MWAIT_MAX_NUM_CSTATES] = { .enter = &intel_idle }, { /* MWAIT C5 */ }, { /* MWAIT C6 */ - .name = "ATM-C6", + .name = "C6-ATM", .desc = "MWAIT 0x52", .driver_data = (void *) 0x52, .flags = CPUIDLE_FLAG_TIME_VALID | CPUIDLE_FLAG_TLB_FLUSHED, @@ -281,6 +288,15 @@ static struct notifier_block setup_broadcast_notifier = { .notifier_call = setup_broadcast_cpuhp_notify, }; +static void auto_demotion_disable(void *dummy) +{ + unsigned long long msr_bits; + + rdmsrl(MSR_NHM_SNB_PKG_CST_CFG_CTL, msr_bits); + msr_bits &= ~auto_demotion_disable_flags; + wrmsrl(MSR_NHM_SNB_PKG_CST_CFG_CTL, msr_bits); +} + /* * intel_idle_probe() */ @@ -324,11 +340,17 @@ static int intel_idle_probe(void) case 0x25: /* Westmere */ case 0x2C: /* Westmere */ cpuidle_state_table = nehalem_cstates; + auto_demotion_disable_flags = + (NHM_C1_AUTO_DEMOTE | NHM_C3_AUTO_DEMOTE); break; case 0x1C: /* 28 - Atom Processor */ + cpuidle_state_table = atom_cstates; + break; + case 0x26: /* 38 - Lincroft Atom Processor */ cpuidle_state_table = atom_cstates; + auto_demotion_disable_flags = ATM_LNC_C6_AUTO_DEMOTE; break; case 0x2A: /* SNB */ @@ -436,6 +458,8 @@ static int intel_idle_cpuidle_devices_init(void) return -EIO; } } + if (auto_demotion_disable_flags) + smp_call_function(auto_demotion_disable, NULL, 1); return 0; } diff --git a/drivers/infiniband/hw/ipath/ipath_user_pages.c b/drivers/infiniband/hw/ipath/ipath_user_pages.c index bab9f74c0665..cfed5399f074 100644 --- a/drivers/infiniband/hw/ipath/ipath_user_pages.c +++ b/drivers/infiniband/hw/ipath/ipath_user_pages.c @@ -53,8 +53,8 @@ static void __ipath_release_user_pages(struct page **p, size_t num_pages, } /* call with current->mm->mmap_sem held */ -static int __get_user_pages(unsigned long start_page, size_t num_pages, - struct page **p, struct vm_area_struct **vma) +static int __ipath_get_user_pages(unsigned long start_page, size_t num_pages, + struct page **p, struct vm_area_struct **vma) { unsigned long lock_limit; size_t got; @@ -165,7 +165,7 @@ int ipath_get_user_pages(unsigned long start_page, size_t num_pages, down_write(¤t->mm->mmap_sem); - ret = __get_user_pages(start_page, num_pages, p, NULL); + ret = __ipath_get_user_pages(start_page, num_pages, p, NULL); up_write(¤t->mm->mmap_sem); diff --git a/drivers/infiniband/hw/qib/qib_mad.c b/drivers/infiniband/hw/qib/qib_mad.c index 5ad224e4a38b..4b9e11cc561b 100644 --- a/drivers/infiniband/hw/qib/qib_mad.c +++ b/drivers/infiniband/hw/qib/qib_mad.c @@ -705,7 +705,7 @@ static int subn_set_portinfo(struct ib_smp *smp, struct ib_device *ibdev, lwe = pip->link_width_enabled; if (lwe) { if (lwe == 0xFF) - lwe = ppd->link_width_supported; + set_link_width_enabled(ppd, ppd->link_width_supported); else if (lwe >= 16 || (lwe & ~ppd->link_width_supported)) smp->status |= IB_SMP_INVALID_FIELD; else if (lwe != ppd->link_width_enabled) @@ -720,7 +720,8 @@ static int subn_set_portinfo(struct ib_smp *smp, struct ib_device *ibdev, * speeds. */ if (lse == 15) - lse = ppd->link_speed_supported; + set_link_speed_enabled(ppd, + ppd->link_speed_supported); else if (lse >= 8 || (lse & ~ppd->link_speed_supported)) smp->status |= IB_SMP_INVALID_FIELD; else if (lse != ppd->link_speed_enabled) @@ -849,7 +850,7 @@ static int subn_set_portinfo(struct ib_smp *smp, struct ib_device *ibdev, if (clientrereg) pip->clientrereg_resv_subnetto |= 0x80; - goto done; + goto get_only; err: smp->status |= IB_SMP_INVALID_FIELD; diff --git a/drivers/infiniband/hw/qib/qib_user_pages.c b/drivers/infiniband/hw/qib/qib_user_pages.c index d7a26c1d4f37..7689e49c13c9 100644 --- a/drivers/infiniband/hw/qib/qib_user_pages.c +++ b/drivers/infiniband/hw/qib/qib_user_pages.c @@ -51,8 +51,8 @@ static void __qib_release_user_pages(struct page **p, size_t num_pages, /* * Call with current->mm->mmap_sem held. */ -static int __get_user_pages(unsigned long start_page, size_t num_pages, - struct page **p, struct vm_area_struct **vma) +static int __qib_get_user_pages(unsigned long start_page, size_t num_pages, + struct page **p, struct vm_area_struct **vma) { unsigned long lock_limit; size_t got; @@ -136,7 +136,7 @@ int qib_get_user_pages(unsigned long start_page, size_t num_pages, down_write(¤t->mm->mmap_sem); - ret = __get_user_pages(start_page, num_pages, p, NULL); + ret = __qib_get_user_pages(start_page, num_pages, p, NULL); up_write(¤t->mm->mmap_sem); diff --git a/drivers/infiniband/ulp/iser/iscsi_iser.c b/drivers/infiniband/ulp/iser/iscsi_iser.c index 7b2fc98e2f2b..8db008de5392 100644 --- a/drivers/infiniband/ulp/iser/iscsi_iser.c +++ b/drivers/infiniband/ulp/iser/iscsi_iser.c @@ -532,6 +532,29 @@ iscsi_iser_conn_get_stats(struct iscsi_cls_conn *cls_conn, struct iscsi_stats *s stats->custom[3].value = conn->fmr_unalign_cnt; } +static int iscsi_iser_get_ep_param(struct iscsi_endpoint *ep, + enum iscsi_param param, char *buf) +{ + struct iser_conn *ib_conn = ep->dd_data; + int len; + + switch (param) { + case ISCSI_PARAM_CONN_PORT: + case ISCSI_PARAM_CONN_ADDRESS: + if (!ib_conn || !ib_conn->cma_id) + return -ENOTCONN; + + return iscsi_conn_get_addr_param((struct sockaddr_storage *) + &ib_conn->cma_id->route.addr.dst_addr, + param, buf); + break; + default: + return -ENOSYS; + } + + return len; +} + static struct iscsi_endpoint * iscsi_iser_ep_connect(struct Scsi_Host *shost, struct sockaddr *dst_addr, int non_blocking) @@ -637,6 +660,8 @@ static struct iscsi_transport iscsi_iser_transport = { ISCSI_MAX_BURST | ISCSI_PDU_INORDER_EN | ISCSI_DATASEQ_INORDER_EN | + ISCSI_CONN_PORT | + ISCSI_CONN_ADDRESS | ISCSI_EXP_STATSN | ISCSI_PERSISTENT_PORT | ISCSI_PERSISTENT_ADDRESS | @@ -659,6 +684,7 @@ static struct iscsi_transport iscsi_iser_transport = { .destroy_conn = iscsi_iser_conn_destroy, .set_param = iscsi_iser_set_param, .get_conn_param = iscsi_conn_get_param, + .get_ep_param = iscsi_iser_get_ep_param, .get_session_param = iscsi_session_get_param, .start_conn = iscsi_iser_conn_start, .stop_conn = iscsi_iser_conn_stop, diff --git a/drivers/input/evdev.c b/drivers/input/evdev.c index c8471a2552e7..7f42d3a454d2 100644 --- a/drivers/input/evdev.c +++ b/drivers/input/evdev.c @@ -321,6 +321,9 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer, struct input_event event; int retval; + if (count < input_event_size()) + return -EINVAL; + retval = mutex_lock_interruptible(&evdev->mutex); if (retval) return retval; @@ -330,17 +333,16 @@ static ssize_t evdev_write(struct file *file, const char __user *buffer, goto out; } - while (retval < count) { - + do { if (input_event_from_user(buffer + retval, &event)) { retval = -EFAULT; goto out; } + retval += input_event_size(); input_inject_event(&evdev->handle, event.type, event.code, event.value); - retval += input_event_size(); - } + } while (retval + input_event_size() <= count); out: mutex_unlock(&evdev->mutex); diff --git a/drivers/input/gameport/gameport.c b/drivers/input/gameport/gameport.c index 23cf8fc933ec..5b8f59d6c3e8 100644 --- a/drivers/input/gameport/gameport.c +++ b/drivers/input/gameport/gameport.c @@ -360,7 +360,7 @@ static int gameport_queue_event(void *object, struct module *owner, event->owner = owner; list_add_tail(&event->node, &gameport_event_list); - schedule_work(&gameport_event_work); + queue_work(system_long_wq, &gameport_event_work); out: spin_unlock_irqrestore(&gameport_event_lock, flags); diff --git a/drivers/input/keyboard/tegra-kbc.c b/drivers/input/keyboard/tegra-kbc.c index ac471b77c18e..99ce9032d08c 100644 --- a/drivers/input/keyboard/tegra-kbc.c +++ b/drivers/input/keyboard/tegra-kbc.c @@ -71,8 +71,9 @@ struct tegra_kbc { spinlock_t lock; unsigned int repoll_dly; unsigned long cp_dly_jiffies; + bool use_fn_map; const struct tegra_kbc_platform_data *pdata; - unsigned short keycode[KBC_MAX_KEY]; + unsigned short keycode[KBC_MAX_KEY * 2]; unsigned short current_keys[KBC_MAX_KPENT]; unsigned int num_pressed_keys; struct timer_list timer; @@ -178,6 +179,40 @@ static const u32 tegra_kbc_default_keymap[] = { KEY(15, 5, KEY_F2), KEY(15, 6, KEY_CAPSLOCK), KEY(15, 7, KEY_F6), + + /* Software Handled Function Keys */ + KEY(20, 0, KEY_KP7), + + KEY(21, 0, KEY_KP9), + KEY(21, 1, KEY_KP8), + KEY(21, 2, KEY_KP4), + KEY(21, 4, KEY_KP1), + + KEY(22, 1, KEY_KPSLASH), + KEY(22, 2, KEY_KP6), + KEY(22, 3, KEY_KP5), + KEY(22, 4, KEY_KP3), + KEY(22, 5, KEY_KP2), + KEY(22, 7, KEY_KP0), + + KEY(27, 1, KEY_KPASTERISK), + KEY(27, 3, KEY_KPMINUS), + KEY(27, 4, KEY_KPPLUS), + KEY(27, 5, KEY_KPDOT), + + KEY(28, 5, KEY_VOLUMEUP), + + KEY(29, 3, KEY_HOME), + KEY(29, 4, KEY_END), + KEY(29, 5, KEY_BRIGHTNESSDOWN), + KEY(29, 6, KEY_VOLUMEDOWN), + KEY(29, 7, KEY_BRIGHTNESSUP), + + KEY(30, 0, KEY_NUMLOCK), + KEY(30, 1, KEY_SCROLLLOCK), + KEY(30, 2, KEY_MUTE), + + KEY(31, 4, KEY_HELP), }; static const struct matrix_keymap_data tegra_kbc_default_keymap_data = { @@ -224,6 +259,7 @@ static void tegra_kbc_report_keys(struct tegra_kbc *kbc) unsigned int i; unsigned int num_down = 0; unsigned long flags; + bool fn_keypress = false; spin_lock_irqsave(&kbc->lock, flags); for (i = 0; i < KBC_MAX_KPENT; i++) { @@ -237,11 +273,28 @@ static void tegra_kbc_report_keys(struct tegra_kbc *kbc) MATRIX_SCAN_CODE(row, col, KBC_ROW_SHIFT); scancodes[num_down] = scancode; - keycodes[num_down++] = kbc->keycode[scancode]; + keycodes[num_down] = kbc->keycode[scancode]; + /* If driver uses Fn map, do not report the Fn key. */ + if ((keycodes[num_down] == KEY_FN) && kbc->use_fn_map) + fn_keypress = true; + else + num_down++; } val >>= 8; } + + /* + * If the platform uses Fn keymaps, translate keys on a Fn keypress. + * Function keycodes are KBC_MAX_KEY apart from the plain keycodes. + */ + if (fn_keypress) { + for (i = 0; i < num_down; i++) { + scancodes[i] += KBC_MAX_KEY; + keycodes[i] = kbc->keycode[scancodes[i]]; + } + } + spin_unlock_irqrestore(&kbc->lock, flags); tegra_kbc_report_released_keys(kbc->idev, @@ -594,8 +647,11 @@ static int __devinit tegra_kbc_probe(struct platform_device *pdev) input_dev->keycode = kbc->keycode; input_dev->keycodesize = sizeof(kbc->keycode[0]); - input_dev->keycodemax = ARRAY_SIZE(kbc->keycode); + input_dev->keycodemax = KBC_MAX_KEY; + if (pdata->use_fn_map) + input_dev->keycodemax *= 2; + kbc->use_fn_map = pdata->use_fn_map; keymap_data = pdata->keymap_data ?: &tegra_kbc_default_keymap_data; matrix_keypad_build_keymap(keymap_data, KBC_ROW_SHIFT, input_dev->keycode, input_dev->keybit); diff --git a/drivers/input/mouse/bcm5974.c b/drivers/input/mouse/bcm5974.c index ee82851afe3e..0efaf667a1c8 100644 --- a/drivers/input/mouse/bcm5974.c +++ b/drivers/input/mouse/bcm5974.c @@ -430,10 +430,6 @@ static int report_tp_state(struct bcm5974 *dev, int size) ptest = int2bound(&c->p, raw_p); origin = raw2int(f->origin); - /* set the integrated button if applicable */ - if (c->tp_type == TYPE2) - ibt = raw2int(dev->tp_data[BUTTON_TYPE2]); - /* while tracking finger still valid, count all fingers */ if (ptest > PRESSURE_LOW && origin) { abs_p = ptest; @@ -452,6 +448,10 @@ static int report_tp_state(struct bcm5974 *dev, int size) } } + /* set the integrated button if applicable */ + if (c->tp_type == TYPE2) + ibt = raw2int(dev->tp_data[BUTTON_TYPE2]); + if (dev->fingers < nmin) dev->fingers = nmin; if (dev->fingers > nmax) diff --git a/drivers/input/mouse/synaptics.h b/drivers/input/mouse/synaptics.h index 25e5d042a72c..7453938bf5ef 100644 --- a/drivers/input/mouse/synaptics.h +++ b/drivers/input/mouse/synaptics.h @@ -51,6 +51,29 @@ #define SYN_EXT_CAP_REQUESTS(c) (((c) & 0x700000) >> 20) #define SYN_CAP_MULTI_BUTTON_NO(ec) (((ec) & 0x00f000) >> 12) #define SYN_CAP_PRODUCT_ID(ec) (((ec) & 0xff0000) >> 16) + +/* + * The following describes response for the 0x0c query. + * + * byte mask name meaning + * ---- ---- ------- ------------ + * 1 0x01 adjustable threshold capacitive button sensitivity + * can be adjusted + * 1 0x02 report max query 0x0d gives max coord reported + * 1 0x04 clearpad sensor is ClearPad product + * 1 0x08 advanced gesture not particularly meaningful + * 1 0x10 clickpad bit 0 1-button ClickPad + * 1 0x60 multifinger mode identifies firmware finger counting + * (not reporting!) algorithm. + * Not particularly meaningful + * 1 0x80 covered pad W clipped to 14, 15 == pad mostly covered + * 2 0x01 clickpad bit 1 2-button ClickPad + * 2 0x02 deluxe LED controls touchpad support LED commands + * ala multimedia control bar + * 2 0x04 reduced filtering firmware does less filtering on + * position data, driver should watch + * for noise. + */ #define SYN_CAP_CLICKPAD(ex0c) ((ex0c) & 0x100000) /* 1-button ClickPad */ #define SYN_CAP_CLICKPAD2BTN(ex0c) ((ex0c) & 0x000100) /* 2-button ClickPad */ #define SYN_CAP_MAX_DIMENSIONS(ex0c) ((ex0c) & 0x020000) diff --git a/drivers/input/serio/ambakmi.c b/drivers/input/serio/ambakmi.c index 92563a681d65..12abc50508e5 100644 --- a/drivers/input/serio/ambakmi.c +++ b/drivers/input/serio/ambakmi.c @@ -107,7 +107,8 @@ static void amba_kmi_close(struct serio *io) clk_disable(kmi->clk); } -static int __devinit amba_kmi_probe(struct amba_device *dev, struct amba_id *id) +static int __devinit amba_kmi_probe(struct amba_device *dev, + const struct amba_id *id) { struct amba_kmi_port *kmi; struct serio *io; diff --git a/drivers/input/serio/i8042-unicore32io.h b/drivers/input/serio/i8042-unicore32io.h new file mode 100644 index 000000000000..73f5cc124a36 --- /dev/null +++ b/drivers/input/serio/i8042-unicore32io.h @@ -0,0 +1,73 @@ +/* + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2011 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#ifndef _I8042_UNICORE32_H +#define _I8042_UNICORE32_H + +#include <mach/hardware.h> + +/* + * Names. + */ +#define I8042_KBD_PHYS_DESC "isa0060/serio0" +#define I8042_AUX_PHYS_DESC "isa0060/serio1" +#define I8042_MUX_PHYS_DESC "isa0060/serio%d" + +/* + * IRQs. + */ +#define I8042_KBD_IRQ IRQ_PS2_KBD +#define I8042_AUX_IRQ IRQ_PS2_AUX + +/* + * Register numbers. + */ +#define I8042_COMMAND_REG PS2_COMMAND +#define I8042_STATUS_REG PS2_STATUS +#define I8042_DATA_REG PS2_DATA + +#define I8042_REGION_START (resource_size_t)(PS2_DATA) +#define I8042_REGION_SIZE (resource_size_t)(16) + +static inline int i8042_read_data(void) +{ + return readb(I8042_DATA_REG); +} + +static inline int i8042_read_status(void) +{ + return readb(I8042_STATUS_REG); +} + +static inline void i8042_write_data(int val) +{ + writeb(val, I8042_DATA_REG); +} + +static inline void i8042_write_command(int val) +{ + writeb(val, I8042_COMMAND_REG); +} + +static inline int i8042_platform_init(void) +{ + if (!request_mem_region(I8042_REGION_START, I8042_REGION_SIZE, "i8042")) + return -EBUSY; + + i8042_reset = 1; + return 0; +} + +static inline void i8042_platform_exit(void) +{ + release_mem_region(I8042_REGION_START, I8042_REGION_SIZE); +} + +#endif /* _I8042_UNICORE32_H */ diff --git a/drivers/input/serio/i8042.h b/drivers/input/serio/i8042.h index ac1d759d0f55..3452708fbe3b 100644 --- a/drivers/input/serio/i8042.h +++ b/drivers/input/serio/i8042.h @@ -26,6 +26,8 @@ #include "i8042-sparcio.h" #elif defined(CONFIG_X86) || defined(CONFIG_IA64) #include "i8042-x86ia64io.h" +#elif defined(CONFIG_UNICORE32) +#include "i8042-unicore32io.h" #else #include "i8042-io.h" #endif diff --git a/drivers/input/serio/serio.c b/drivers/input/serio/serio.c index 7c38d1fbabf2..ba70058e2be3 100644 --- a/drivers/input/serio/serio.c +++ b/drivers/input/serio/serio.c @@ -299,7 +299,7 @@ static int serio_queue_event(void *object, struct module *owner, event->owner = owner; list_add_tail(&event->node, &serio_event_list); - schedule_work(&serio_event_work); + queue_work(system_long_wq, &serio_event_work); out: spin_unlock_irqrestore(&serio_event_lock, flags); diff --git a/drivers/isdn/hardware/eicon/istream.c b/drivers/isdn/hardware/eicon/istream.c index 18f8798442fa..7bd5baa547be 100644 --- a/drivers/isdn/hardware/eicon/istream.c +++ b/drivers/isdn/hardware/eicon/istream.c @@ -62,7 +62,7 @@ void diva_xdi_provide_istream_info (ADAPTER* a, stream interface. If synchronous service was requested, then function does return amount of data written to stream. - 'final' does indicate that pice of data to be written is + 'final' does indicate that piece of data to be written is final part of frame (necessary only by structured datatransfer) return 0 if zero lengh packet was written return -1 if stream is full diff --git a/drivers/md/dm-mpath.c b/drivers/md/dm-mpath.c index b82d28819e2a..4b0b63c290a6 100644 --- a/drivers/md/dm-mpath.c +++ b/drivers/md/dm-mpath.c @@ -1283,24 +1283,22 @@ static int do_end_io(struct multipath *m, struct request *clone, if (!error && !clone->errors) return 0; /* I/O complete */ - if (error == -EOPNOTSUPP) - return error; - - if (clone->cmd_flags & REQ_DISCARD) - /* - * Pass all discard request failures up. - * FIXME: only fail_path if the discard failed due to a - * transport problem. This requires precise understanding - * of the underlying failure (e.g. the SCSI sense). - */ + if (error == -EOPNOTSUPP || error == -EREMOTEIO) return error; if (mpio->pgpath) fail_path(mpio->pgpath); spin_lock_irqsave(&m->lock, flags); - if (!m->nr_valid_paths && !m->queue_if_no_path && !__must_push_back(m)) - r = -EIO; + if (!m->nr_valid_paths) { + if (!m->queue_if_no_path) { + if (!__must_push_back(m)) + r = -EIO; + } else { + if (error == -EBADE) + r = error; + } + } spin_unlock_irqrestore(&m->lock, flags); return r; diff --git a/drivers/md/linear.c b/drivers/md/linear.c index 8a2f767f26d8..0ed7f6bc2a7f 100644 --- a/drivers/md/linear.c +++ b/drivers/md/linear.c @@ -216,7 +216,6 @@ static int linear_run (mddev_t *mddev) if (md_check_no_bitmap(mddev)) return -EINVAL; - mddev->queue->queue_lock = &mddev->queue->__queue_lock; conf = linear_conf(mddev, mddev->raid_disks); if (!conf) diff --git a/drivers/md/md.c b/drivers/md/md.c index 0cc30ecda4c1..818313e277e7 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -553,6 +553,9 @@ static mddev_t * mddev_find(dev_t unit) { mddev_t *mddev, *new = NULL; + if (unit && MAJOR(unit) != MD_MAJOR) + unit &= ~((1<<MdpMinorShift)-1); + retry: spin_lock(&all_mddevs_lock); @@ -4138,10 +4141,10 @@ array_size_store(mddev_t *mddev, const char *buf, size_t len) } mddev->array_sectors = sectors; - set_capacity(mddev->gendisk, mddev->array_sectors); - if (mddev->pers) + if (mddev->pers) { + set_capacity(mddev->gendisk, mddev->array_sectors); revalidate_disk(mddev->gendisk); - + } return len; } @@ -4624,6 +4627,7 @@ static int do_md_run(mddev_t *mddev) } set_capacity(mddev->gendisk, mddev->array_sectors); revalidate_disk(mddev->gendisk); + mddev->changed = 1; kobject_uevent(&disk_to_dev(mddev->gendisk)->kobj, KOBJ_CHANGE); out: return err; @@ -4712,6 +4716,7 @@ static void md_clean(mddev_t *mddev) mddev->sync_speed_min = mddev->sync_speed_max = 0; mddev->recovery = 0; mddev->in_sync = 0; + mddev->changed = 0; mddev->degraded = 0; mddev->safemode = 0; mddev->bitmap_info.offset = 0; @@ -4827,6 +4832,7 @@ static int do_md_stop(mddev_t * mddev, int mode, int is_open) set_capacity(disk, 0); mutex_unlock(&mddev->open_mutex); + mddev->changed = 1; revalidate_disk(disk); if (mddev->ro) @@ -6011,7 +6017,7 @@ static int md_open(struct block_device *bdev, fmode_t mode) atomic_inc(&mddev->openers); mutex_unlock(&mddev->open_mutex); - check_disk_size_change(mddev->gendisk, bdev); + check_disk_change(bdev); out: return err; } @@ -6026,6 +6032,21 @@ static int md_release(struct gendisk *disk, fmode_t mode) return 0; } + +static int md_media_changed(struct gendisk *disk) +{ + mddev_t *mddev = disk->private_data; + + return mddev->changed; +} + +static int md_revalidate(struct gendisk *disk) +{ + mddev_t *mddev = disk->private_data; + + mddev->changed = 0; + return 0; +} static const struct block_device_operations md_fops = { .owner = THIS_MODULE, @@ -6036,6 +6057,8 @@ static const struct block_device_operations md_fops = .compat_ioctl = md_compat_ioctl, #endif .getgeo = md_getgeo, + .media_changed = md_media_changed, + .revalidate_disk= md_revalidate, }; static int md_thread(void * arg) diff --git a/drivers/md/md.h b/drivers/md/md.h index 7e90b8593b2a..12215d437fcc 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -274,6 +274,8 @@ struct mddev_s atomic_t active; /* general refcount */ atomic_t openers; /* number of active opens */ + int changed; /* True if we might need to + * reread partition info */ int degraded; /* whether md should consider * adding a spare */ diff --git a/drivers/md/multipath.c b/drivers/md/multipath.c index 6d7ddf32ef2e..3a62d440e27b 100644 --- a/drivers/md/multipath.c +++ b/drivers/md/multipath.c @@ -435,7 +435,6 @@ static int multipath_run (mddev_t *mddev) * bookkeeping area. [whatever we allocate in multipath_run(), * should be freed in multipath_stop()] */ - mddev->queue->queue_lock = &mddev->queue->__queue_lock; conf = kzalloc(sizeof(multipath_conf_t), GFP_KERNEL); mddev->private = conf; diff --git a/drivers/md/raid0.c b/drivers/md/raid0.c index 637a96855edb..c0ac457f1218 100644 --- a/drivers/md/raid0.c +++ b/drivers/md/raid0.c @@ -361,7 +361,6 @@ static int raid0_run(mddev_t *mddev) if (md_check_no_bitmap(mddev)) return -EINVAL; blk_queue_max_hw_sectors(mddev->queue, mddev->chunk_sectors); - mddev->queue->queue_lock = &mddev->queue->__queue_lock; /* if private is not null, we are here after takeover */ if (mddev->private == NULL) { @@ -670,6 +669,7 @@ static void *raid0_takeover_raid1(mddev_t *mddev) mddev->new_layout = 0; mddev->new_chunk_sectors = 128; /* by default set chunk size to 64k */ mddev->delta_disks = 1 - mddev->raid_disks; + mddev->raid_disks = 1; /* make sure it will be not marked as dirty */ mddev->recovery_cp = MaxSector; diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index a23ffa397ba9..06cd712807d0 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -593,7 +593,10 @@ static int flush_pending_writes(conf_t *conf) if (conf->pending_bio_list.head) { struct bio *bio; bio = bio_list_get(&conf->pending_bio_list); + /* Only take the spinlock to quiet a warning */ + spin_lock(conf->mddev->queue->queue_lock); blk_remove_plug(conf->mddev->queue); + spin_unlock(conf->mddev->queue->queue_lock); spin_unlock_irq(&conf->device_lock); /* flush any pending bitmap writes to * disk before proceeding w/ I/O */ @@ -959,7 +962,7 @@ static int make_request(mddev_t *mddev, struct bio * bio) atomic_inc(&r1_bio->remaining); spin_lock_irqsave(&conf->device_lock, flags); bio_list_add(&conf->pending_bio_list, mbio); - blk_plug_device(mddev->queue); + blk_plug_device_unlocked(mddev->queue); spin_unlock_irqrestore(&conf->device_lock, flags); } r1_bio_write_done(r1_bio, bio->bi_vcnt, behind_pages, behind_pages != NULL); @@ -2021,7 +2024,6 @@ static int run(mddev_t *mddev) if (IS_ERR(conf)) return PTR_ERR(conf); - mddev->queue->queue_lock = &conf->device_lock; list_for_each_entry(rdev, &mddev->disks, same_set) { disk_stack_limits(mddev->gendisk, rdev->bdev, rdev->data_offset << 9); diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 3b607b28741b..747d061d8e05 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -662,7 +662,10 @@ static int flush_pending_writes(conf_t *conf) if (conf->pending_bio_list.head) { struct bio *bio; bio = bio_list_get(&conf->pending_bio_list); + /* Spinlock only taken to quiet a warning */ + spin_lock(conf->mddev->queue->queue_lock); blk_remove_plug(conf->mddev->queue); + spin_unlock(conf->mddev->queue->queue_lock); spin_unlock_irq(&conf->device_lock); /* flush any pending bitmap writes to disk * before proceeding w/ I/O */ @@ -971,7 +974,7 @@ static int make_request(mddev_t *mddev, struct bio * bio) atomic_inc(&r10_bio->remaining); spin_lock_irqsave(&conf->device_lock, flags); bio_list_add(&conf->pending_bio_list, mbio); - blk_plug_device(mddev->queue); + blk_plug_device_unlocked(mddev->queue); spin_unlock_irqrestore(&conf->device_lock, flags); } @@ -2304,8 +2307,6 @@ static int run(mddev_t *mddev) if (!conf) goto out; - mddev->queue->queue_lock = &conf->device_lock; - mddev->thread = conf->thread; conf->thread = NULL; diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index 702812824195..78536fdbd87f 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -5204,7 +5204,6 @@ static int run(mddev_t *mddev) mddev->queue->backing_dev_info.congested_data = mddev; mddev->queue->backing_dev_info.congested_fn = raid5_congested; - mddev->queue->queue_lock = &conf->device_lock; mddev->queue->unplug_fn = raid5_unplug_queue; chunk_size = mddev->chunk_sectors << 9; diff --git a/drivers/media/common/tuners/tuner-types.c b/drivers/media/common/tuners/tuner-types.c index 58a513bcd747..afba6dc5e080 100644 --- a/drivers/media/common/tuners/tuner-types.c +++ b/drivers/media/common/tuners/tuner-types.c @@ -971,6 +971,22 @@ static struct tuner_params tuner_tena_9533_di_params[] = { }, }; +/* ------------ TUNER_TENA_TNF_5337 - Tena tnf5337MFD STD M/N ------------ */ + +static struct tuner_range tuner_tena_tnf_5337_ntsc_ranges[] = { + { 16 * 166.25 /*MHz*/, 0x86, 0x01, }, + { 16 * 466.25 /*MHz*/, 0x86, 0x02, }, + { 16 * 999.99 , 0x86, 0x08, }, +}; + +static struct tuner_params tuner_tena_tnf_5337_params[] = { + { + .type = TUNER_PARAM_TYPE_NTSC, + .ranges = tuner_tena_tnf_5337_ntsc_ranges, + .count = ARRAY_SIZE(tuner_tena_tnf_5337_ntsc_ranges), + }, +}; + /* ------------ TUNER_PHILIPS_FMD1216ME(X)_MK3 - Philips PAL ------------ */ static struct tuner_range tuner_philips_fmd1216me_mk3_pal_ranges[] = { @@ -1842,6 +1858,11 @@ struct tunertype tuners[] = { .params = tuner_philips_fq1236_mk5_params, .count = ARRAY_SIZE(tuner_philips_fq1236_mk5_params), }, + [TUNER_TENA_TNF_5337] = { /* Tena 5337 MFD */ + .name = "Tena TNF5337 MFD", + .params = tuner_tena_tnf_5337_params, + .count = ARRAY_SIZE(tuner_tena_tnf_5337_params), + }, }; EXPORT_SYMBOL(tuners); diff --git a/drivers/media/dvb/dvb-usb/Kconfig b/drivers/media/dvb/dvb-usb/Kconfig index 3d48ba019342..fe4f894183ff 100644 --- a/drivers/media/dvb/dvb-usb/Kconfig +++ b/drivers/media/dvb/dvb-usb/Kconfig @@ -358,3 +358,11 @@ config DVB_USB_LME2510 select DVB_IX2505V if !DVB_FE_CUSTOMISE help Say Y here to support the LME DM04/QQBOX DVB-S USB2.0 . + +config DVB_USB_TECHNISAT_USB2 + tristate "Technisat DVB-S/S2 USB2.0 support" + depends on DVB_USB + select DVB_STB0899 if !DVB_FE_CUSTOMISE + select DVB_STB6100 if !DVB_FE_CUSTOMISE + help + Say Y here to support the Technisat USB2 DVB-S/S2 device diff --git a/drivers/media/dvb/dvb-usb/Makefile b/drivers/media/dvb/dvb-usb/Makefile index 5b1d12f2d591..4bac13da0c39 100644 --- a/drivers/media/dvb/dvb-usb/Makefile +++ b/drivers/media/dvb/dvb-usb/Makefile @@ -91,6 +91,9 @@ obj-$(CONFIG_DVB_USB_AZ6027) += dvb-usb-az6027.o dvb-usb-lmedm04-objs = lmedm04.o obj-$(CONFIG_DVB_USB_LME2510) += dvb-usb-lmedm04.o +dvb-usb-technisat-usb2-objs = technisat-usb2.o +obj-$(CONFIG_DVB_USB_TECHNISAT_USB2) += dvb-usb-technisat-usb2.o + EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core/ -Idrivers/media/dvb/frontends/ # due to tuner-xc3028 EXTRA_CFLAGS += -Idrivers/media/common/tuners diff --git a/drivers/media/dvb/dvb-usb/dib0700.h b/drivers/media/dvb/dvb-usb/dib0700.h index 3537d65c04bc..b2a87f2c2c3e 100644 --- a/drivers/media/dvb/dvb-usb/dib0700.h +++ b/drivers/media/dvb/dvb-usb/dib0700.h @@ -32,6 +32,7 @@ extern int dvb_usb_dib0700_debug; // 1 Byte: 4MSB(1 = enable streaming, 0 = disable streaming) 4LSB(Video Mode: 0 = MPEG2 188Bytes, 1 = Analog) // 2 Byte: MPEG2 mode: 4MSB(1 = Master Mode, 0 = Slave Mode) 4LSB(Channel 1 = bit0, Channel 2 = bit1) // 2 Byte: Analog mode: 4MSB(0 = 625 lines, 1 = 525 lines) 4LSB( " " ) +#define REQUEST_SET_I2C_PARAM 0x10 #define REQUEST_SET_RC 0x11 #define REQUEST_NEW_I2C_READ 0x12 #define REQUEST_NEW_I2C_WRITE 0x13 @@ -61,6 +62,7 @@ extern struct i2c_algorithm dib0700_i2c_algo; extern int dib0700_identify_state(struct usb_device *udev, struct dvb_usb_device_properties *props, struct dvb_usb_device_description **desc, int *cold); extern int dib0700_change_protocol(struct rc_dev *dev, u64 rc_type); +extern int dib0700_set_i2c_speed(struct dvb_usb_device *d, u16 scl_kHz); extern int dib0700_device_count; extern int dvb_usb_dib0700_ir_proto; diff --git a/drivers/media/dvb/dvb-usb/dib0700_core.c b/drivers/media/dvb/dvb-usb/dib0700_core.c index 98ffb40728e3..b79af68c54ae 100644 --- a/drivers/media/dvb/dvb-usb/dib0700_core.c +++ b/drivers/media/dvb/dvb-usb/dib0700_core.c @@ -186,7 +186,7 @@ static int dib0700_i2c_xfer_new(struct i2c_adapter *adap, struct i2c_msg *msg, msg[i].len, USB_CTRL_GET_TIMEOUT); if (result < 0) { - err("i2c read error (status = %d)\n", result); + deb_info("i2c read error (status = %d)\n", result); break; } @@ -215,7 +215,7 @@ static int dib0700_i2c_xfer_new(struct i2c_adapter *adap, struct i2c_msg *msg, 0, 0, buf, msg[i].len + 4, USB_CTRL_GET_TIMEOUT); if (result < 0) { - err("i2c write error (status = %d)\n", result); + deb_info("i2c write error (status = %d)\n", result); break; } } @@ -328,6 +328,31 @@ static int dib0700_set_clock(struct dvb_usb_device *d, u8 en_pll, return dib0700_ctrl_wr(d, b, 10); } +int dib0700_set_i2c_speed(struct dvb_usb_device *d, u16 scl_kHz) +{ + u16 divider; + u8 b[8]; + + if (scl_kHz == 0) + return -EINVAL; + + b[0] = REQUEST_SET_I2C_PARAM; + divider = (u16) (30000 / scl_kHz); + b[2] = (u8) (divider >> 8); + b[3] = (u8) (divider & 0xff); + divider = (u16) (72000 / scl_kHz); + b[4] = (u8) (divider >> 8); + b[5] = (u8) (divider & 0xff); + divider = (u16) (72000 / scl_kHz); /* clock: 72MHz */ + b[6] = (u8) (divider >> 8); + b[7] = (u8) (divider & 0xff); + + deb_info("setting I2C speed: %04x %04x %04x (%d kHz).", + (b[2] << 8) | (b[3]), (b[4] << 8) | b[5], (b[6] << 8) | b[7], scl_kHz); + return dib0700_ctrl_wr(d, b, 8); +} + + int dib0700_ctrl_clock(struct dvb_usb_device *d, u32 clk_MHz, u8 clock_out_gp3) { switch (clk_MHz) { @@ -459,10 +484,20 @@ int dib0700_streaming_ctrl(struct dvb_usb_adapter *adap, int onoff) deb_info("modifying (%d) streaming state for %d\n", onoff, adap->id); - if (onoff) - st->channel_state |= 1 << adap->id; - else - st->channel_state &= ~(1 << adap->id); + st->channel_state &= ~0x3; + if ((adap->stream.props.endpoint != 2) + && (adap->stream.props.endpoint != 3)) { + deb_info("the endpoint number (%i) is not correct, use the adapter id instead", adap->stream.props.endpoint); + if (onoff) + st->channel_state |= 1 << (adap->id); + else + st->channel_state |= 1 << ~(adap->id); + } else { + if (onoff) + st->channel_state |= 1 << (adap->stream.props.endpoint-2); + else + st->channel_state |= 1 << (3-adap->stream.props.endpoint); + } b[2] |= st->channel_state; diff --git a/drivers/media/dvb/dvb-usb/dib0700_devices.c b/drivers/media/dvb/dvb-usb/dib0700_devices.c index defd83964ce2..c6022afeb785 100644 --- a/drivers/media/dvb/dvb-usb/dib0700_devices.c +++ b/drivers/media/dvb/dvb-usb/dib0700_devices.c @@ -12,6 +12,7 @@ #include "dib7000m.h" #include "dib7000p.h" #include "dib8000.h" +#include "dib9000.h" #include "mt2060.h" #include "mt2266.h" #include "tuner-xc2028.h" @@ -29,6 +30,7 @@ MODULE_PARM_DESC(force_lna_activation, "force the activation of Low-Noise-Amplif struct dib0700_adapter_state { int (*set_param_save) (struct dvb_frontend *, struct dvb_frontend_parameters *); + const struct firmware *frontend_firmware; }; /* Hauppauge Nova-T 500 (aka Bristol) @@ -1226,13 +1228,13 @@ static int dib807x_tuner_attach(struct dvb_usb_adapter *adap) static int stk80xx_pid_filter(struct dvb_usb_adapter *adapter, int index, u16 pid, int onoff) { - return dib8000_pid_filter(adapter->fe, index, pid, onoff); + return dib8000_pid_filter(adapter->fe, index, pid, onoff); } static int stk80xx_pid_filter_ctrl(struct dvb_usb_adapter *adapter, - int onoff) + int onoff) { - return dib8000_pid_filter_ctrl(adapter->fe, onoff); + return dib8000_pid_filter_ctrl(adapter->fe, onoff); } /* STK807x */ @@ -1304,11 +1306,11 @@ static int stk807xpvr_frontend_attach1(struct dvb_usb_adapter *adap) /* STK8096GP */ struct dibx000_agc_config dib8090_agc_config[2] = { - { + { BAND_UHF | BAND_VHF | BAND_LBAND | BAND_SBAND, /* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, - * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0, - * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */ + * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0, + * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */ (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0), @@ -1345,12 +1347,12 @@ struct dibx000_agc_config dib8090_agc_config[2] = { 51, 0, - }, - { + }, + { BAND_CBAND, /* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, - * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0, - * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */ + * P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, P_agc_inh_dc_rv_est=0, + * P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */ (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0), @@ -1387,135 +1389,153 @@ struct dibx000_agc_config dib8090_agc_config[2] = { 51, 0, - } + } }; static struct dibx000_bandwidth_config dib8090_pll_config_12mhz = { - 54000, 13500, - 1, 18, 3, 1, 0, - 0, 0, 1, 1, 2, - (3 << 14) | (1 << 12) | (599 << 0), - (0 << 25) | 0, - 20199727, - 12000000, + 54000, 13500, + 1, 18, 3, 1, 0, + 0, 0, 1, 1, 2, + (3 << 14) | (1 << 12) | (599 << 0), + (0 << 25) | 0, + 20199727, + 12000000, }; static int dib8090_get_adc_power(struct dvb_frontend *fe) { - return dib8000_get_adc_power(fe, 1); + return dib8000_get_adc_power(fe, 1); } -static struct dib8000_config dib809x_dib8000_config = { - .output_mpeg2_in_188_bytes = 1, - - .agc_config_count = 2, - .agc = dib8090_agc_config, - .agc_control = dib0090_dcc_freq, - .pll = &dib8090_pll_config_12mhz, - .tuner_is_baseband = 1, - - .gpio_dir = DIB8000_GPIO_DEFAULT_DIRECTIONS, - .gpio_val = DIB8000_GPIO_DEFAULT_VALUES, - .gpio_pwm_pos = DIB8000_GPIO_DEFAULT_PWM_POS, - - .hostbus_diversity = 1, - .div_cfg = 0x31, - .output_mode = OUTMODE_MPEG2_FIFO, - .drives = 0x2d98, - .diversity_delay = 144, - .refclksel = 3, +static struct dib8000_config dib809x_dib8000_config[2] = { + { + .output_mpeg2_in_188_bytes = 1, + + .agc_config_count = 2, + .agc = dib8090_agc_config, + .agc_control = dib0090_dcc_freq, + .pll = &dib8090_pll_config_12mhz, + .tuner_is_baseband = 1, + + .gpio_dir = DIB8000_GPIO_DEFAULT_DIRECTIONS, + .gpio_val = DIB8000_GPIO_DEFAULT_VALUES, + .gpio_pwm_pos = DIB8000_GPIO_DEFAULT_PWM_POS, + + .hostbus_diversity = 1, + .div_cfg = 0x31, + .output_mode = OUTMODE_MPEG2_FIFO, + .drives = 0x2d98, + .diversity_delay = 48, + .refclksel = 3, + }, { + .output_mpeg2_in_188_bytes = 1, + + .agc_config_count = 2, + .agc = dib8090_agc_config, + .agc_control = dib0090_dcc_freq, + .pll = &dib8090_pll_config_12mhz, + .tuner_is_baseband = 1, + + .gpio_dir = DIB8000_GPIO_DEFAULT_DIRECTIONS, + .gpio_val = DIB8000_GPIO_DEFAULT_VALUES, + .gpio_pwm_pos = DIB8000_GPIO_DEFAULT_PWM_POS, + + .hostbus_diversity = 1, + .div_cfg = 0x31, + .output_mode = OUTMODE_DIVERSITY, + .drives = 0x2d08, + .diversity_delay = 1, + .refclksel = 3, + } +}; + +static struct dib0090_wbd_slope dib8090_wbd_table[] = { + /* max freq ; cold slope ; cold offset ; warm slope ; warm offset ; wbd gain */ + { 120, 0, 500, 0, 500, 4 }, /* CBAND */ + { 170, 0, 450, 0, 450, 4 }, /* CBAND */ + { 380, 48, 373, 28, 259, 6 }, /* VHF */ + { 860, 34, 700, 36, 616, 6 }, /* high UHF */ + { 0xFFFF, 34, 700, 36, 616, 6 }, /* default */ }; static struct dib0090_config dib809x_dib0090_config = { - .io.pll_bypass = 1, - .io.pll_range = 1, - .io.pll_prediv = 1, - .io.pll_loopdiv = 20, - .io.adc_clock_ratio = 8, - .io.pll_int_loop_filt = 0, - .io.clock_khz = 12000, - .reset = dib80xx_tuner_reset, - .sleep = dib80xx_tuner_sleep, - .clkouttobamse = 1, - .analog_output = 1, - .i2c_address = DEFAULT_DIB0090_I2C_ADDRESS, - .wbd_vhf_offset = 100, - .wbd_cband_offset = 450, - .use_pwm_agc = 1, - .clkoutdrive = 1, - .get_adc_power = dib8090_get_adc_power, - .freq_offset_khz_uhf = 0, + .io.pll_bypass = 1, + .io.pll_range = 1, + .io.pll_prediv = 1, + .io.pll_loopdiv = 20, + .io.adc_clock_ratio = 8, + .io.pll_int_loop_filt = 0, + .io.clock_khz = 12000, + .reset = dib80xx_tuner_reset, + .sleep = dib80xx_tuner_sleep, + .clkouttobamse = 1, + .analog_output = 1, + .i2c_address = DEFAULT_DIB0090_I2C_ADDRESS, + .use_pwm_agc = 1, + .clkoutdrive = 1, + .get_adc_power = dib8090_get_adc_power, + .freq_offset_khz_uhf = -63, .freq_offset_khz_vhf = -143, + .wbd = dib8090_wbd_table, + .fref_clock_ratio = 6, }; static int dib8096_set_param_override(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) { - struct dvb_usb_adapter *adap = fe->dvb->priv; - struct dib0700_adapter_state *state = adap->priv; - u8 band = BAND_OF_FREQUENCY(fep->frequency/1000); - u16 offset; - int ret = 0; - enum frontend_tune_state tune_state = CT_SHUTDOWN; - u16 ltgain, rf_gain_limit; - - ret = state->set_param_save(fe, fep); - if (ret < 0) - return ret; - - switch (band) { - case BAND_VHF: - offset = 100; - break; - case BAND_UHF: - offset = 550; - break; - default: - offset = 0; - break; - } - offset += (dib0090_get_wbd_offset(fe) * 8 * 18 / 33 + 1) / 2; - dib8000_set_wbd_ref(fe, offset); - - - if (band == BAND_CBAND) { - deb_info("tuning in CBAND - soft-AGC startup\n"); - /* TODO specific wbd target for dib0090 - needed for startup ? */ - dib0090_set_tune_state(fe, CT_AGC_START); - do { - ret = dib0090_gain_control(fe); - msleep(ret); - tune_state = dib0090_get_tune_state(fe); - if (tune_state == CT_AGC_STEP_0) - dib8000_set_gpio(fe, 6, 0, 1); - else if (tune_state == CT_AGC_STEP_1) { - dib0090_get_current_gain(fe, NULL, NULL, &rf_gain_limit, <gain); - if (rf_gain_limit == 0) - dib8000_set_gpio(fe, 6, 0, 0); - } - } while (tune_state < CT_AGC_STOP); - dib0090_pwm_gain_reset(fe); - dib8000_pwm_agc_reset(fe); - dib8000_set_tune_state(fe, CT_DEMOD_START); - } else { - deb_info("not tuning in CBAND - standard AGC startup\n"); - dib0090_pwm_gain_reset(fe); - } + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dib0700_adapter_state *state = adap->priv; + u8 band = BAND_OF_FREQUENCY(fep->frequency/1000); + u16 target; + int ret = 0; + enum frontend_tune_state tune_state = CT_SHUTDOWN; + u16 ltgain, rf_gain_limit; + + ret = state->set_param_save(fe, fep); + if (ret < 0) + return ret; + + target = (dib0090_get_wbd_offset(fe) * 8 * 18 / 33 + 1) / 2; + dib8000_set_wbd_ref(fe, target); + + + if (band == BAND_CBAND) { + deb_info("tuning in CBAND - soft-AGC startup\n"); + dib0090_set_tune_state(fe, CT_AGC_START); + do { + ret = dib0090_gain_control(fe); + msleep(ret); + tune_state = dib0090_get_tune_state(fe); + if (tune_state == CT_AGC_STEP_0) + dib8000_set_gpio(fe, 6, 0, 1); + else if (tune_state == CT_AGC_STEP_1) { + dib0090_get_current_gain(fe, NULL, NULL, &rf_gain_limit, <gain); + if (rf_gain_limit == 0) + dib8000_set_gpio(fe, 6, 0, 0); + } + } while (tune_state < CT_AGC_STOP); + dib0090_pwm_gain_reset(fe); + dib8000_pwm_agc_reset(fe); + dib8000_set_tune_state(fe, CT_DEMOD_START); + } else { + deb_info("not tuning in CBAND - standard AGC startup\n"); + dib0090_pwm_gain_reset(fe); + } - return 0; + return 0; } static int dib809x_tuner_attach(struct dvb_usb_adapter *adap) { - struct dib0700_adapter_state *st = adap->priv; - struct i2c_adapter *tun_i2c = dib8000_get_i2c_master(adap->fe, DIBX000_I2C_INTERFACE_TUNER, 1); + struct dib0700_adapter_state *st = adap->priv; + struct i2c_adapter *tun_i2c = dib8000_get_i2c_master(adap->fe, DIBX000_I2C_INTERFACE_TUNER, 1); - if (dvb_attach(dib0090_register, adap->fe, tun_i2c, &dib809x_dib0090_config) == NULL) - return -ENODEV; + if (dvb_attach(dib0090_register, adap->fe, tun_i2c, &dib809x_dib0090_config) == NULL) + return -ENODEV; - st->set_param_save = adap->fe->ops.tuner_ops.set_params; - adap->fe->ops.tuner_ops.set_params = dib8096_set_param_override; - return 0; + st->set_param_save = adap->fe->ops.tuner_ops.set_params; + adap->fe->ops.tuner_ops.set_params = dib8096_set_param_override; + return 0; } static int stk809x_frontend_attach(struct dvb_usb_adapter *adap) @@ -1537,11 +1557,931 @@ static int stk809x_frontend_attach(struct dvb_usb_adapter *adap) dib8000_i2c_enumeration(&adap->dev->i2c_adap, 1, 18, 0x80); - adap->fe = dvb_attach(dib8000_attach, &adap->dev->i2c_adap, 0x80, &dib809x_dib8000_config); + adap->fe = dvb_attach(dib8000_attach, &adap->dev->i2c_adap, 0x80, &dib809x_dib8000_config[0]); + + return adap->fe == NULL ? -ENODEV : 0; +} + +static int nim8096md_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *st = adap->priv; + struct i2c_adapter *tun_i2c; + struct dvb_frontend *fe_slave = dib8000_get_slave_frontend(adap->fe, 1); + + if (fe_slave) { + tun_i2c = dib8000_get_i2c_master(fe_slave, DIBX000_I2C_INTERFACE_TUNER, 1); + if (dvb_attach(dib0090_register, fe_slave, tun_i2c, &dib809x_dib0090_config) == NULL) + return -ENODEV; + fe_slave->dvb = adap->fe->dvb; + fe_slave->ops.tuner_ops.set_params = dib8096_set_param_override; + } + tun_i2c = dib8000_get_i2c_master(adap->fe, DIBX000_I2C_INTERFACE_TUNER, 1); + if (dvb_attach(dib0090_register, adap->fe, tun_i2c, &dib809x_dib0090_config) == NULL) + return -ENODEV; + + st->set_param_save = adap->fe->ops.tuner_ops.set_params; + adap->fe->ops.tuner_ops.set_params = dib8096_set_param_override; + + return 0; +} + +static int nim8096md_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dvb_frontend *fe_slave; + + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 0); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1); + msleep(1000); + dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1); + + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); + + dib0700_ctrl_clock(adap->dev, 72, 1); + + msleep(20); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1); + + dib8000_i2c_enumeration(&adap->dev->i2c_adap, 2, 18, 0x80); + + adap->fe = dvb_attach(dib8000_attach, &adap->dev->i2c_adap, 0x80, &dib809x_dib8000_config[0]); + if (adap->fe == NULL) + return -ENODEV; + + fe_slave = dvb_attach(dib8000_attach, &adap->dev->i2c_adap, 0x82, &dib809x_dib8000_config[1]); + dib8000_set_slave_frontend(adap->fe, fe_slave); + + return fe_slave == NULL ? -ENODEV : 0; +} + +/* STK9090M */ +static int dib90x0_pid_filter(struct dvb_usb_adapter *adapter, int index, u16 pid, int onoff) +{ + return dib9000_fw_pid_filter(adapter->fe, index, pid, onoff); +} + +static int dib90x0_pid_filter_ctrl(struct dvb_usb_adapter *adapter, int onoff) +{ + return dib9000_fw_pid_filter_ctrl(adapter->fe, onoff); +} + +static int dib90x0_tuner_reset(struct dvb_frontend *fe, int onoff) +{ + return dib9000_set_gpio(fe, 5, 0, !onoff); +} + +static int dib90x0_tuner_sleep(struct dvb_frontend *fe, int onoff) +{ + return dib9000_set_gpio(fe, 0, 0, onoff); +} + +static int dib01x0_pmu_update(struct i2c_adapter *i2c, u16 *data, u8 len) +{ + u8 wb[4] = { 0xc >> 8, 0xc & 0xff, 0, 0 }; + u8 rb[2]; + struct i2c_msg msg[2] = { + {.addr = 0x1e >> 1, .flags = 0, .buf = wb, .len = 2}, + {.addr = 0x1e >> 1, .flags = I2C_M_RD, .buf = rb, .len = 2}, + }; + u8 index_data; + + dibx000_i2c_set_speed(i2c, 250); + + if (i2c_transfer(i2c, msg, 2) != 2) + return -EIO; + + switch (rb[0] << 8 | rb[1]) { + case 0: + deb_info("Found DiB0170 rev1: This version of DiB0170 is not supported any longer.\n"); + return -EIO; + case 1: + deb_info("Found DiB0170 rev2"); + break; + case 2: + deb_info("Found DiB0190 rev2"); + break; + default: + deb_info("DiB01x0 not found"); + return -EIO; + } + + for (index_data = 0; index_data < len; index_data += 2) { + wb[2] = (data[index_data + 1] >> 8) & 0xff; + wb[3] = (data[index_data + 1]) & 0xff; + + if (data[index_data] == 0) { + wb[0] = (data[index_data] >> 8) & 0xff; + wb[1] = (data[index_data]) & 0xff; + msg[0].len = 2; + if (i2c_transfer(i2c, msg, 2) != 2) + return -EIO; + wb[2] |= rb[0]; + wb[3] |= rb[1] & ~(3 << 4); + } + + wb[0] = (data[index_data] >> 8)&0xff; + wb[1] = (data[index_data])&0xff; + msg[0].len = 4; + if (i2c_transfer(i2c, &msg[0], 1) != 1) + return -EIO; + } + return 0; +} + +static struct dib9000_config stk9090m_config = { + .output_mpeg2_in_188_bytes = 1, + .output_mode = OUTMODE_MPEG2_FIFO, + .vcxo_timer = 279620, + .timing_frequency = 20452225, + .demod_clock_khz = 60000, + .xtal_clock_khz = 30000, + .if_drives = (0 << 15) | (1 << 13) | (0 << 12) | (3 << 10) | (0 << 9) | (1 << 7) | (0 << 6) | (0 << 4) | (1 << 3) | (1 << 1) | (0), + .subband = { + 2, + { + { 240, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0008, 0x0000, 0x0008 } }, /* GPIO 3 to 1 for VHF */ + { 890, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0008, 0x0000, 0x0000 } }, /* GPIO 3 to 0 for UHF */ + { 0 }, + }, + }, + .gpio_function = { + { .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_ON, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = (0x10 & ~0x1) | 0x20 }, + { .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_OFF, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = 0 | 0x21 }, + }, +}; + +static struct dib9000_config nim9090md_config[2] = { + { + .output_mpeg2_in_188_bytes = 1, + .output_mode = OUTMODE_MPEG2_FIFO, + .vcxo_timer = 279620, + .timing_frequency = 20452225, + .demod_clock_khz = 60000, + .xtal_clock_khz = 30000, + .if_drives = (0 << 15) | (1 << 13) | (0 << 12) | (3 << 10) | (0 << 9) | (1 << 7) | (0 << 6) | (0 << 4) | (1 << 3) | (1 << 1) | (0), + }, { + .output_mpeg2_in_188_bytes = 1, + .output_mode = OUTMODE_DIVERSITY, + .vcxo_timer = 279620, + .timing_frequency = 20452225, + .demod_clock_khz = 60000, + .xtal_clock_khz = 30000, + .if_drives = (0 << 15) | (1 << 13) | (0 << 12) | (3 << 10) | (0 << 9) | (1 << 7) | (0 << 6) | (0 << 4) | (1 << 3) | (1 << 1) | (0), + .subband = { + 2, + { + { 240, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0006, 0x0000, 0x0006 } }, /* GPIO 1 and 2 to 1 for VHF */ + { 890, { BOARD_GPIO_COMPONENT_DEMOD, BOARD_GPIO_FUNCTION_SUBBAND_GPIO, 0x0006, 0x0000, 0x0000 } }, /* GPIO 1 and 2 to 0 for UHF */ + { 0 }, + }, + }, + .gpio_function = { + { .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_ON, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = (0x10 & ~0x1) | 0x20 }, + { .component = BOARD_GPIO_COMPONENT_DEMOD, .function = BOARD_GPIO_FUNCTION_COMPONENT_OFF, .mask = 0x10 | 0x21, .direction = 0 & ~0x21, .value = 0 | 0x21 }, + }, + } +}; + +static struct dib0090_config dib9090_dib0090_config = { + .io.pll_bypass = 0, + .io.pll_range = 1, + .io.pll_prediv = 1, + .io.pll_loopdiv = 8, + .io.adc_clock_ratio = 8, + .io.pll_int_loop_filt = 0, + .io.clock_khz = 30000, + .reset = dib90x0_tuner_reset, + .sleep = dib90x0_tuner_sleep, + .clkouttobamse = 0, + .analog_output = 0, + .use_pwm_agc = 0, + .clkoutdrive = 0, + .freq_offset_khz_uhf = 0, + .freq_offset_khz_vhf = 0, +}; + +static struct dib0090_config nim9090md_dib0090_config[2] = { + { + .io.pll_bypass = 0, + .io.pll_range = 1, + .io.pll_prediv = 1, + .io.pll_loopdiv = 8, + .io.adc_clock_ratio = 8, + .io.pll_int_loop_filt = 0, + .io.clock_khz = 30000, + .reset = dib90x0_tuner_reset, + .sleep = dib90x0_tuner_sleep, + .clkouttobamse = 1, + .analog_output = 0, + .use_pwm_agc = 0, + .clkoutdrive = 0, + .freq_offset_khz_uhf = 0, + .freq_offset_khz_vhf = 0, + }, { + .io.pll_bypass = 0, + .io.pll_range = 1, + .io.pll_prediv = 1, + .io.pll_loopdiv = 8, + .io.adc_clock_ratio = 8, + .io.pll_int_loop_filt = 0, + .io.clock_khz = 30000, + .reset = dib90x0_tuner_reset, + .sleep = dib90x0_tuner_sleep, + .clkouttobamse = 0, + .analog_output = 0, + .use_pwm_agc = 0, + .clkoutdrive = 0, + .freq_offset_khz_uhf = 0, + .freq_offset_khz_vhf = 0, + } +}; + + +static int stk9090m_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *state = adap->priv; + struct dib0700_state *st = adap->dev->priv; + u32 fw_version; + + /* Make use of the new i2c functions from FW 1.20 */ + dib0700_get_version(adap->dev, NULL, NULL, &fw_version, NULL); + if (fw_version >= 0x10200) + st->fw_use_new_i2c_api = 1; + dib0700_set_i2c_speed(adap->dev, 340); + + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); + + dib0700_ctrl_clock(adap->dev, 72, 1); + + msleep(20); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1); + + dib9000_i2c_enumeration(&adap->dev->i2c_adap, 1, 0x10, 0x80); + + if (request_firmware(&state->frontend_firmware, "dib9090.fw", &adap->dev->udev->dev)) { + deb_info("%s: Upload failed. (file not found?)\n", __func__); + return -ENODEV; + } else { + deb_info("%s: firmware read %Zu bytes.\n", __func__, state->frontend_firmware->size); + } + stk9090m_config.microcode_B_fe_size = state->frontend_firmware->size; + stk9090m_config.microcode_B_fe_buffer = state->frontend_firmware->data; + + adap->fe = dvb_attach(dib9000_attach, &adap->dev->i2c_adap, 0x80, &stk9090m_config); + + return adap->fe == NULL ? -ENODEV : 0; +} + +static int dib9090_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *state = adap->priv; + struct i2c_adapter *i2c = dib9000_get_tuner_interface(adap->fe); + u16 data_dib190[10] = { + 1, 0x1374, + 2, 0x01a2, + 7, 0x0020, + 0, 0x00ef, + 8, 0x0486, + }; + + if (dvb_attach(dib0090_fw_register, adap->fe, i2c, &dib9090_dib0090_config) == NULL) + return -ENODEV; + i2c = dib9000_get_i2c_master(adap->fe, DIBX000_I2C_INTERFACE_GPIO_1_2, 0); + if (dib01x0_pmu_update(i2c, data_dib190, 10) != 0) + return -ENODEV; + dib0700_set_i2c_speed(adap->dev, 2000); + if (dib9000_firmware_post_pll_init(adap->fe) < 0) + return -ENODEV; + release_firmware(state->frontend_firmware); + return 0; +} + +static int nim9090md_frontend_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *state = adap->priv; + struct dib0700_state *st = adap->dev->priv; + struct i2c_adapter *i2c; + struct dvb_frontend *fe_slave; + u32 fw_version; + + /* Make use of the new i2c functions from FW 1.20 */ + dib0700_get_version(adap->dev, NULL, NULL, &fw_version, NULL); + if (fw_version >= 0x10200) + st->fw_use_new_i2c_api = 1; + dib0700_set_i2c_speed(adap->dev, 340); + + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); + + dib0700_ctrl_clock(adap->dev, 72, 1); + + msleep(20); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1); + + if (request_firmware(&state->frontend_firmware, "dib9090.fw", &adap->dev->udev->dev)) { + deb_info("%s: Upload failed. (file not found?)\n", __func__); + return -EIO; + } else { + deb_info("%s: firmware read %Zu bytes.\n", __func__, state->frontend_firmware->size); + } + nim9090md_config[0].microcode_B_fe_size = state->frontend_firmware->size; + nim9090md_config[0].microcode_B_fe_buffer = state->frontend_firmware->data; + nim9090md_config[1].microcode_B_fe_size = state->frontend_firmware->size; + nim9090md_config[1].microcode_B_fe_buffer = state->frontend_firmware->data; + + dib9000_i2c_enumeration(&adap->dev->i2c_adap, 1, 0x20, 0x80); + adap->fe = dvb_attach(dib9000_attach, &adap->dev->i2c_adap, 0x80, &nim9090md_config[0]); + + if (adap->fe == NULL) + return -ENODEV; + + i2c = dib9000_get_i2c_master(adap->fe, DIBX000_I2C_INTERFACE_GPIO_3_4, 0); + dib9000_i2c_enumeration(i2c, 1, 0x12, 0x82); + + fe_slave = dvb_attach(dib9000_attach, i2c, 0x82, &nim9090md_config[1]); + dib9000_set_slave_frontend(adap->fe, fe_slave); + + return fe_slave == NULL ? -ENODEV : 0; +} + +static int nim9090md_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *state = adap->priv; + struct i2c_adapter *i2c; + struct dvb_frontend *fe_slave; + u16 data_dib190[10] = { + 1, 0x5374, + 2, 0x01ae, + 7, 0x0020, + 0, 0x00ef, + 8, 0x0406, + }; + i2c = dib9000_get_tuner_interface(adap->fe); + if (dvb_attach(dib0090_fw_register, adap->fe, i2c, &nim9090md_dib0090_config[0]) == NULL) + return -ENODEV; + i2c = dib9000_get_i2c_master(adap->fe, DIBX000_I2C_INTERFACE_GPIO_1_2, 0); + if (dib01x0_pmu_update(i2c, data_dib190, 10) < 0) + return -ENODEV; + dib0700_set_i2c_speed(adap->dev, 2000); + if (dib9000_firmware_post_pll_init(adap->fe) < 0) + return -ENODEV; + + fe_slave = dib9000_get_slave_frontend(adap->fe, 1); + if (fe_slave != NULL) { + i2c = dib9000_get_component_bus_interface(adap->fe); + dib9000_set_i2c_adapter(fe_slave, i2c); + + i2c = dib9000_get_tuner_interface(fe_slave); + if (dvb_attach(dib0090_fw_register, fe_slave, i2c, &nim9090md_dib0090_config[1]) == NULL) + return -ENODEV; + fe_slave->dvb = adap->fe->dvb; + dib9000_fw_set_component_bus_speed(adap->fe, 2000); + if (dib9000_firmware_post_pll_init(fe_slave) < 0) + return -ENODEV; + } + release_firmware(state->frontend_firmware); + + return 0; +} + +/* NIM7090 */ +struct dib7090p_best_adc { + u32 timf; + u32 pll_loopdiv; + u32 pll_prediv; +}; + +static int dib7090p_get_best_sampling(struct dvb_frontend *fe , struct dib7090p_best_adc *adc) +{ + u8 spur = 0, prediv = 0, loopdiv = 0, min_prediv = 1, max_prediv = 1; + + u16 xtal = 12000; + u32 fcp_min = 1900; /* PLL Minimum Frequency comparator KHz */ + u32 fcp_max = 20000; /* PLL Maximum Frequency comparator KHz */ + u32 fdem_max = 76000; + u32 fdem_min = 69500; + u32 fcp = 0, fs = 0, fdem = 0; + u32 harmonic_id = 0; + + adc->pll_loopdiv = loopdiv; + adc->pll_prediv = prediv; + adc->timf = 0; + + deb_info("bandwidth = %d fdem_min =%d", fe->dtv_property_cache.bandwidth_hz, fdem_min); + + /* Find Min and Max prediv */ + while ((xtal/max_prediv) >= fcp_min) + max_prediv++; + + max_prediv--; + min_prediv = max_prediv; + while ((xtal/min_prediv) <= fcp_max) { + min_prediv--; + if (min_prediv == 1) + break; + } + deb_info("MIN prediv = %d : MAX prediv = %d", min_prediv, max_prediv); + + min_prediv = 2; + + for (prediv = min_prediv ; prediv < max_prediv; prediv++) { + fcp = xtal / prediv; + if (fcp > fcp_min && fcp < fcp_max) { + for (loopdiv = 1 ; loopdiv < 64 ; loopdiv++) { + fdem = ((xtal/prediv) * loopdiv); + fs = fdem / 4; + /* test min/max system restrictions */ + + if ((fdem >= fdem_min) && (fdem <= fdem_max) && (fs >= fe->dtv_property_cache.bandwidth_hz/1000)) { + spur = 0; + /* test fs harmonics positions */ + for (harmonic_id = (fe->dtv_property_cache.frequency / (1000*fs)) ; harmonic_id <= ((fe->dtv_property_cache.frequency / (1000*fs))+1) ; harmonic_id++) { + if (((fs*harmonic_id) >= ((fe->dtv_property_cache.frequency/1000) - (fe->dtv_property_cache.bandwidth_hz/2000))) && ((fs*harmonic_id) <= ((fe->dtv_property_cache.frequency/1000) + (fe->dtv_property_cache.bandwidth_hz/2000)))) { + spur = 1; + break; + } + } + + if (!spur) { + adc->pll_loopdiv = loopdiv; + adc->pll_prediv = prediv; + adc->timf = 2396745143UL/fdem*(1 << 9); + adc->timf += ((2396745143UL%fdem) << 9)/fdem; + deb_info("loopdiv=%i prediv=%i timf=%i", loopdiv, prediv, adc->timf); + break; + } + } + } + } + if (!spur) + break; + } + + + if (adc->pll_loopdiv == 0 && adc->pll_prediv == 0) + return -EINVAL; + else + return 0; +} + +static int dib7090_agc_startup(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) +{ + struct dvb_usb_adapter *adap = fe->dvb->priv; + struct dib0700_adapter_state *state = adap->priv; + struct dibx000_bandwidth_config pll; + u16 target; + struct dib7090p_best_adc adc; + int ret; + + ret = state->set_param_save(fe, fep); + if (ret < 0) + return ret; + + memset(&pll, 0, sizeof(struct dibx000_bandwidth_config)); + dib0090_pwm_gain_reset(fe); + target = (dib0090_get_wbd_offset(fe) * 8 + 1) / 2; + dib7000p_set_wbd_ref(fe, target); + + if (dib7090p_get_best_sampling(fe, &adc) == 0) { + pll.pll_ratio = adc.pll_loopdiv; + pll.pll_prediv = adc.pll_prediv; + + dib7000p_update_pll(fe, &pll); + dib7000p_ctrl_timf(fe, DEMOD_TIMF_SET, adc.timf); + } + return 0; +} + +static struct dib0090_wbd_slope dib7090_wbd_table[] = { + { 380, 81, 850, 64, 540, 4}, + { 860, 51, 866, 21, 375, 4}, + {1700, 0, 250, 0, 100, 6}, + {2600, 0, 250, 0, 100, 6}, + { 0xFFFF, 0, 0, 0, 0, 0}, +}; + +struct dibx000_agc_config dib7090_agc_config[2] = { + { + .band_caps = BAND_UHF, + /* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, + * P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */ + .setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0), + + .inv_gain = 687, + .time_stabiliz = 10, + + .alpha_level = 0, + .thlock = 118, + + .wbd_inv = 0, + .wbd_ref = 1200, + .wbd_sel = 3, + .wbd_alpha = 5, + + .agc1_max = 65535, + .agc1_min = 0, + + .agc2_max = 65535, + .agc2_min = 0, + + .agc1_pt1 = 0, + .agc1_pt2 = 32, + .agc1_pt3 = 114, + .agc1_slope1 = 143, + .agc1_slope2 = 144, + .agc2_pt1 = 114, + .agc2_pt2 = 227, + .agc2_slope1 = 116, + .agc2_slope2 = 117, + + .alpha_mant = 18, + .alpha_exp = 0, + .beta_mant = 20, + .beta_exp = 59, + + .perform_agc_softsplit = 0, + } , { + .band_caps = BAND_FM | BAND_VHF | BAND_CBAND, + /* P_agc_use_sd_mod1=0, P_agc_use_sd_mod2=0, P_agc_freq_pwm_div=1, P_agc_inv_pwm1=0, P_agc_inv_pwm2=0, + * P_agc_inh_dc_rv_est=0, P_agc_time_est=3, P_agc_freeze=0, P_agc_nb_est=5, P_agc_write=0 */ + .setup = (0 << 15) | (0 << 14) | (5 << 11) | (0 << 10) | (0 << 9) | (0 << 8) | (3 << 5) | (0 << 4) | (5 << 1) | (0 << 0), + + .inv_gain = 732, + .time_stabiliz = 10, + + .alpha_level = 0, + .thlock = 118, + + .wbd_inv = 0, + .wbd_ref = 1200, + .wbd_sel = 3, + .wbd_alpha = 5, + + .agc1_max = 65535, + .agc1_min = 0, + + .agc2_max = 65535, + .agc2_min = 0, + + .agc1_pt1 = 0, + .agc1_pt2 = 0, + .agc1_pt3 = 98, + .agc1_slope1 = 0, + .agc1_slope2 = 167, + .agc1_pt1 = 98, + .agc2_pt2 = 255, + .agc2_slope1 = 104, + .agc2_slope2 = 0, + + .alpha_mant = 18, + .alpha_exp = 0, + .beta_mant = 20, + .beta_exp = 59, + + .perform_agc_softsplit = 0, + } +}; + +static struct dibx000_bandwidth_config dib7090_clock_config_12_mhz = { + 60000, 15000, + 1, 5, 0, 0, 0, + 0, 0, 1, 1, 2, + (3 << 14) | (1 << 12) | (524 << 0), + (0 << 25) | 0, + 20452225, + 15000000, +}; + +static struct dib7000p_config nim7090_dib7000p_config = { + .output_mpeg2_in_188_bytes = 1, + .hostbus_diversity = 1, + .tuner_is_baseband = 1, + .update_lna = NULL, + + .agc_config_count = 2, + .agc = dib7090_agc_config, + + .bw = &dib7090_clock_config_12_mhz, + + .gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS, + .gpio_val = DIB7000P_GPIO_DEFAULT_VALUES, + .gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS, + + .pwm_freq_div = 0, + + .agc_control = dib7090_agc_restart, + + .spur_protect = 0, + .disable_sample_and_hold = 0, + .enable_current_mirror = 0, + .diversity_delay = 0, + + .output_mode = OUTMODE_MPEG2_FIFO, + .enMpegOutput = 1, +}; + +static struct dib7000p_config tfe7090pvr_dib7000p_config[2] = { + { + .output_mpeg2_in_188_bytes = 1, + .hostbus_diversity = 1, + .tuner_is_baseband = 1, + .update_lna = NULL, + + .agc_config_count = 2, + .agc = dib7090_agc_config, + + .bw = &dib7090_clock_config_12_mhz, + + .gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS, + .gpio_val = DIB7000P_GPIO_DEFAULT_VALUES, + .gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS, + + .pwm_freq_div = 0, + + .agc_control = dib7090_agc_restart, + + .spur_protect = 0, + .disable_sample_and_hold = 0, + .enable_current_mirror = 0, + .diversity_delay = 0, + + .output_mode = OUTMODE_MPEG2_PAR_GATED_CLK, + .default_i2c_addr = 0x90, + .enMpegOutput = 1, + }, { + .output_mpeg2_in_188_bytes = 1, + .hostbus_diversity = 1, + .tuner_is_baseband = 1, + .update_lna = NULL, + + .agc_config_count = 2, + .agc = dib7090_agc_config, + + .bw = &dib7090_clock_config_12_mhz, + + .gpio_dir = DIB7000P_GPIO_DEFAULT_DIRECTIONS, + .gpio_val = DIB7000P_GPIO_DEFAULT_VALUES, + .gpio_pwm_pos = DIB7000P_GPIO_DEFAULT_PWM_POS, + + .pwm_freq_div = 0, + + .agc_control = dib7090_agc_restart, + + .spur_protect = 0, + .disable_sample_and_hold = 0, + .enable_current_mirror = 0, + .diversity_delay = 0, + + .output_mode = OUTMODE_MPEG2_PAR_GATED_CLK, + .default_i2c_addr = 0x92, + .enMpegOutput = 0, + } +}; + +static const struct dib0090_config nim7090_dib0090_config = { + .io.clock_khz = 12000, + .io.pll_bypass = 0, + .io.pll_range = 0, + .io.pll_prediv = 3, + .io.pll_loopdiv = 6, + .io.adc_clock_ratio = 0, + .io.pll_int_loop_filt = 0, + .reset = dib7090_tuner_sleep, + .sleep = dib7090_tuner_sleep, + + .freq_offset_khz_uhf = 0, + .freq_offset_khz_vhf = 0, + + .get_adc_power = dib7090_get_adc_power, + + .clkouttobamse = 1, + .analog_output = 0, + + .wbd_vhf_offset = 0, + .wbd_cband_offset = 0, + .use_pwm_agc = 1, + .clkoutdrive = 0, + + .fref_clock_ratio = 0, + + .wbd = dib7090_wbd_table, + + .ls_cfg_pad_drv = 0, + .data_tx_drv = 0, + .low_if = NULL, + .in_soc = 1, +}; + +static const struct dib0090_config tfe7090pvr_dib0090_config[2] = { + { + .io.clock_khz = 12000, + .io.pll_bypass = 0, + .io.pll_range = 0, + .io.pll_prediv = 3, + .io.pll_loopdiv = 6, + .io.adc_clock_ratio = 0, + .io.pll_int_loop_filt = 0, + .reset = dib7090_tuner_sleep, + .sleep = dib7090_tuner_sleep, + + .freq_offset_khz_uhf = 50, + .freq_offset_khz_vhf = 70, + + .get_adc_power = dib7090_get_adc_power, + + .clkouttobamse = 1, + .analog_output = 0, + + .wbd_vhf_offset = 0, + .wbd_cband_offset = 0, + .use_pwm_agc = 1, + .clkoutdrive = 0, + + .fref_clock_ratio = 0, + + .wbd = dib7090_wbd_table, + + .ls_cfg_pad_drv = 0, + .data_tx_drv = 0, + .low_if = NULL, + .in_soc = 1, + }, { + .io.clock_khz = 12000, + .io.pll_bypass = 0, + .io.pll_range = 0, + .io.pll_prediv = 3, + .io.pll_loopdiv = 6, + .io.adc_clock_ratio = 0, + .io.pll_int_loop_filt = 0, + .reset = dib7090_tuner_sleep, + .sleep = dib7090_tuner_sleep, + + .freq_offset_khz_uhf = -50, + .freq_offset_khz_vhf = -70, + + .get_adc_power = dib7090_get_adc_power, + + .clkouttobamse = 1, + .analog_output = 0, + + .wbd_vhf_offset = 0, + .wbd_cband_offset = 0, + .use_pwm_agc = 1, + .clkoutdrive = 0, + + .fref_clock_ratio = 0, + + .wbd = dib7090_wbd_table, + + .ls_cfg_pad_drv = 0, + .data_tx_drv = 0, + .low_if = NULL, + .in_soc = 1, + } +}; + +static int nim7090_frontend_attach(struct dvb_usb_adapter *adap) +{ + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); + + msleep(20); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1); + + if (dib7000p_i2c_enumeration(&adap->dev->i2c_adap, 1, 0x10, &nim7090_dib7000p_config) != 0) { + err("%s: dib7000p_i2c_enumeration failed. Cannot continue\n", __func__); + return -ENODEV; + } + adap->fe = dvb_attach(dib7000p_attach, &adap->dev->i2c_adap, 0x80, &nim7090_dib7000p_config); return adap->fe == NULL ? -ENODEV : 0; } +static int nim7090_tuner_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *st = adap->priv; + struct i2c_adapter *tun_i2c = dib7090_get_i2c_tuner(adap->fe); + + if (dvb_attach(dib0090_register, adap->fe, tun_i2c, &nim7090_dib0090_config) == NULL) + return -ENODEV; + + dib7000p_set_gpio(adap->fe, 8, 0, 1); + + st->set_param_save = adap->fe->ops.tuner_ops.set_params; + adap->fe->ops.tuner_ops.set_params = dib7090_agc_startup; + return 0; +} + +static int tfe7090pvr_frontend0_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_state *st = adap->dev->priv; + + /* The TFE7090 requires the dib0700 to not be in master mode */ + st->disable_streaming_master_mode = 1; + + dib0700_set_gpio(adap->dev, GPIO6, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO9, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO4, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO7, GPIO_OUT, 1); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 0); + + msleep(20); + dib0700_set_gpio(adap->dev, GPIO10, GPIO_OUT, 1); + msleep(20); + dib0700_set_gpio(adap->dev, GPIO0, GPIO_OUT, 1); + + /* initialize IC 0 */ + if (dib7000p_i2c_enumeration(&adap->dev->i2c_adap, 1, 0x20, &tfe7090pvr_dib7000p_config[0]) != 0) { + err("%s: dib7000p_i2c_enumeration failed. Cannot continue\n", __func__); + return -ENODEV; + } + + dib0700_set_i2c_speed(adap->dev, 340); + adap->fe = dvb_attach(dib7000p_attach, &adap->dev->i2c_adap, 0x90, &tfe7090pvr_dib7000p_config[0]); + + dib7090_slave_reset(adap->fe); + + if (adap->fe == NULL) + return -ENODEV; + + return 0; +} + +static int tfe7090pvr_frontend1_attach(struct dvb_usb_adapter *adap) +{ + struct i2c_adapter *i2c; + + if (adap->dev->adapter[0].fe == NULL) { + err("the master dib7090 has to be initialized first"); + return -ENODEV; /* the master device has not been initialized */ + } + + i2c = dib7000p_get_i2c_master(adap->dev->adapter[0].fe, DIBX000_I2C_INTERFACE_GPIO_6_7, 1); + if (dib7000p_i2c_enumeration(i2c, 1, 0x10, &tfe7090pvr_dib7000p_config[1]) != 0) { + err("%s: dib7000p_i2c_enumeration failed. Cannot continue\n", __func__); + return -ENODEV; + } + + adap->fe = dvb_attach(dib7000p_attach, i2c, 0x92, &tfe7090pvr_dib7000p_config[1]); + dib0700_set_i2c_speed(adap->dev, 200); + + return adap->fe == NULL ? -ENODEV : 0; +} + +static int tfe7090pvr_tuner0_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *st = adap->priv; + struct i2c_adapter *tun_i2c = dib7090_get_i2c_tuner(adap->fe); + + if (dvb_attach(dib0090_register, adap->fe, tun_i2c, &tfe7090pvr_dib0090_config[0]) == NULL) + return -ENODEV; + + dib7000p_set_gpio(adap->fe, 8, 0, 1); + + st->set_param_save = adap->fe->ops.tuner_ops.set_params; + adap->fe->ops.tuner_ops.set_params = dib7090_agc_startup; + return 0; +} + +static int tfe7090pvr_tuner1_attach(struct dvb_usb_adapter *adap) +{ + struct dib0700_adapter_state *st = adap->priv; + struct i2c_adapter *tun_i2c = dib7090_get_i2c_tuner(adap->fe); + + if (dvb_attach(dib0090_register, adap->fe, tun_i2c, &tfe7090pvr_dib0090_config[1]) == NULL) + return -ENODEV; + + dib7000p_set_gpio(adap->fe, 8, 0, 1); + + st->set_param_save = adap->fe->ops.tuner_ops.set_params; + adap->fe->ops.tuner_ops.set_params = dib7090_agc_startup; + return 0; +} + /* STK7070PD */ static struct dib7000p_config stk7070pd_dib7000p_config[2] = { { @@ -1839,6 +2779,11 @@ struct usb_device_id dib0700_usb_id_table[] = { { USB_DEVICE(USB_VID_PINNACLE, USB_PID_PINNACLE_PCTV282E) }, { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_STK8096GP) }, { USB_DEVICE(USB_VID_ELGATO, USB_PID_ELGATO_EYETV_DIVERSITY) }, + { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_NIM9090M) }, +/* 70 */{ USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_NIM8096MD) }, + { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_NIM9090MD) }, + { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_NIM7090) }, + { USB_DEVICE(USB_VID_DIBCOM, USB_PID_DIBCOM_TFE7090PVR) }, { 0 } /* Terminating entry */ }; MODULE_DEVICE_TABLE(usb, dib0700_usb_id_table); @@ -2602,6 +3547,205 @@ struct dvb_usb_device_properties dib0700_devices[] = { RC_TYPE_NEC, .change_protocol = dib0700_change_protocol, }, + }, { DIB0700_DEFAULT_DEVICE_PROPERTIES, + .num_adapters = 1, + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER | + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = dib90x0_pid_filter, + .pid_filter_ctrl = dib90x0_pid_filter_ctrl, + .frontend_attach = stk9090m_frontend_attach, + .tuner_attach = dib9090_tuner_attach, + + DIB0700_DEFAULT_STREAMING_CONFIG(0x02), + + .size_of_priv = + sizeof(struct dib0700_adapter_state), + }, + }, + + .num_device_descs = 1, + .devices = { + { "DiBcom STK9090M reference design", + { &dib0700_usb_id_table[69], NULL }, + { NULL }, + }, + }, + + .rc.core = { + .rc_interval = DEFAULT_RC_INTERVAL, + .rc_codes = RC_MAP_DIB0700_RC5_TABLE, + .module_name = "dib0700", + .rc_query = dib0700_rc_query_old_firmware, + .allowed_protos = RC_TYPE_RC5 | + RC_TYPE_RC6 | + RC_TYPE_NEC, + .change_protocol = dib0700_change_protocol, + }, + }, { DIB0700_DEFAULT_DEVICE_PROPERTIES, + .num_adapters = 1, + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER | + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = stk80xx_pid_filter, + .pid_filter_ctrl = stk80xx_pid_filter_ctrl, + .frontend_attach = nim8096md_frontend_attach, + .tuner_attach = nim8096md_tuner_attach, + + DIB0700_DEFAULT_STREAMING_CONFIG(0x02), + + .size_of_priv = + sizeof(struct dib0700_adapter_state), + }, + }, + + .num_device_descs = 1, + .devices = { + { "DiBcom NIM8096MD reference design", + { &dib0700_usb_id_table[70], NULL }, + { NULL }, + }, + }, + + .rc.core = { + .rc_interval = DEFAULT_RC_INTERVAL, + .rc_codes = RC_MAP_DIB0700_RC5_TABLE, + .module_name = "dib0700", + .rc_query = dib0700_rc_query_old_firmware, + .allowed_protos = RC_TYPE_RC5 | + RC_TYPE_RC6 | + RC_TYPE_NEC, + .change_protocol = dib0700_change_protocol, + }, + }, { DIB0700_DEFAULT_DEVICE_PROPERTIES, + .num_adapters = 1, + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER | + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = dib90x0_pid_filter, + .pid_filter_ctrl = dib90x0_pid_filter_ctrl, + .frontend_attach = nim9090md_frontend_attach, + .tuner_attach = nim9090md_tuner_attach, + + DIB0700_DEFAULT_STREAMING_CONFIG(0x02), + + .size_of_priv = + sizeof(struct dib0700_adapter_state), + }, + }, + + .num_device_descs = 1, + .devices = { + { "DiBcom NIM9090MD reference design", + { &dib0700_usb_id_table[71], NULL }, + { NULL }, + }, + }, + + .rc.core = { + .rc_interval = DEFAULT_RC_INTERVAL, + .rc_codes = RC_MAP_DIB0700_RC5_TABLE, + .module_name = "dib0700", + .rc_query = dib0700_rc_query_old_firmware, + .allowed_protos = RC_TYPE_RC5 | + RC_TYPE_RC6 | + RC_TYPE_NEC, + .change_protocol = dib0700_change_protocol, + }, + }, { DIB0700_DEFAULT_DEVICE_PROPERTIES, + .num_adapters = 1, + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER | + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = stk70x0p_pid_filter, + .pid_filter_ctrl = stk70x0p_pid_filter_ctrl, + .frontend_attach = nim7090_frontend_attach, + .tuner_attach = nim7090_tuner_attach, + + DIB0700_DEFAULT_STREAMING_CONFIG(0x02), + + .size_of_priv = + sizeof(struct dib0700_adapter_state), + }, + }, + + .num_device_descs = 1, + .devices = { + { "DiBcom NIM7090 reference design", + { &dib0700_usb_id_table[72], NULL }, + { NULL }, + }, + }, + + .rc.core = { + .rc_interval = DEFAULT_RC_INTERVAL, + .rc_codes = RC_MAP_DIB0700_RC5_TABLE, + .module_name = "dib0700", + .rc_query = dib0700_rc_query_old_firmware, + .allowed_protos = RC_TYPE_RC5 | + RC_TYPE_RC6 | + RC_TYPE_NEC, + .change_protocol = dib0700_change_protocol, + }, + }, { DIB0700_DEFAULT_DEVICE_PROPERTIES, + .num_adapters = 2, + .adapter = { + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER | + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = stk70x0p_pid_filter, + .pid_filter_ctrl = stk70x0p_pid_filter_ctrl, + .frontend_attach = tfe7090pvr_frontend0_attach, + .tuner_attach = tfe7090pvr_tuner0_attach, + + DIB0700_DEFAULT_STREAMING_CONFIG(0x03), + + .size_of_priv = + sizeof(struct dib0700_adapter_state), + }, + { + .caps = DVB_USB_ADAP_HAS_PID_FILTER | + DVB_USB_ADAP_PID_FILTER_CAN_BE_TURNED_OFF, + .pid_filter_count = 32, + .pid_filter = stk70x0p_pid_filter, + .pid_filter_ctrl = stk70x0p_pid_filter_ctrl, + .frontend_attach = tfe7090pvr_frontend1_attach, + .tuner_attach = tfe7090pvr_tuner1_attach, + + DIB0700_DEFAULT_STREAMING_CONFIG(0x02), + + .size_of_priv = + sizeof(struct dib0700_adapter_state), + }, + }, + + .num_device_descs = 1, + .devices = { + { "DiBcom TFE7090PVR reference design", + { &dib0700_usb_id_table[73], NULL }, + { NULL }, + }, + }, + + .rc.core = { + .rc_interval = DEFAULT_RC_INTERVAL, + .rc_codes = RC_MAP_DIB0700_RC5_TABLE, + .module_name = "dib0700", + .rc_query = dib0700_rc_query_old_firmware, + .allowed_protos = RC_TYPE_RC5 | + RC_TYPE_RC6 | + RC_TYPE_NEC, + .change_protocol = dib0700_change_protocol, + }, }, }; diff --git a/drivers/media/dvb/dvb-usb/dvb-usb-ids.h b/drivers/media/dvb/dvb-usb/dvb-usb-ids.h index 1a6310b61923..b71540d7e070 100644 --- a/drivers/media/dvb/dvb-usb/dvb-usb-ids.h +++ b/drivers/media/dvb/dvb-usb/dvb-usb-ids.h @@ -106,8 +106,13 @@ #define USB_PID_DIBCOM_STK807XP 0x1f90 #define USB_PID_DIBCOM_STK807XPVR 0x1f98 #define USB_PID_DIBCOM_STK8096GP 0x1fa0 +#define USB_PID_DIBCOM_NIM8096MD 0x1fa8 #define USB_PID_DIBCOM_ANCHOR_2135_COLD 0x2131 #define USB_PID_DIBCOM_STK7770P 0x1e80 +#define USB_PID_DIBCOM_NIM7090 0x1bb2 +#define USB_PID_DIBCOM_TFE7090PVR 0x1bb4 +#define USB_PID_DIBCOM_NIM9090M 0x2383 +#define USB_PID_DIBCOM_NIM9090MD 0x2384 #define USB_PID_DPOSH_M9206_COLD 0x9206 #define USB_PID_DPOSH_M9206_WARM 0xa090 #define USB_PID_E3C_EC168 0x1689 @@ -312,4 +317,5 @@ #define USB_PID_TERRATEC_DVBS2CI_V2 0x10ac #define USB_PID_TECHNISAT_USB2_HDCI_V1 0x0001 #define USB_PID_TECHNISAT_USB2_HDCI_V2 0x0002 +#define USB_PID_TECHNISAT_USB2_DVB_S2 0x0500 #endif diff --git a/drivers/media/dvb/dvb-usb/dvb-usb-remote.c b/drivers/media/dvb/dvb-usb/dvb-usb-remote.c index 23005b3cf30b..f511418b144a 100644 --- a/drivers/media/dvb/dvb-usb/dvb-usb-remote.c +++ b/drivers/media/dvb/dvb-usb/dvb-usb-remote.c @@ -246,7 +246,7 @@ static int rc_core_dvb_usb_remote_init(struct dvb_usb_device *d) dev->map_name = d->props.rc.core.rc_codes; dev->change_protocol = d->props.rc.core.change_protocol; dev->allowed_protos = d->props.rc.core.allowed_protos; - dev->driver_type = RC_DRIVER_SCANCODE; + dev->driver_type = d->props.rc.core.driver_type; usb_to_input_id(d->udev, &dev->input_id); dev->input_name = "IR-receiver inside an USB DVB receiver"; dev->input_phys = d->rc_phys; diff --git a/drivers/media/dvb/dvb-usb/dvb-usb.h b/drivers/media/dvb/dvb-usb/dvb-usb.h index 65fa9268e7f7..76a80968482a 100644 --- a/drivers/media/dvb/dvb-usb/dvb-usb.h +++ b/drivers/media/dvb/dvb-usb/dvb-usb.h @@ -181,6 +181,7 @@ struct dvb_rc_legacy { * @rc_codes: name of rc codes table * @protocol: type of protocol(s) currently used by the driver * @allowed_protos: protocol(s) supported by the driver + * @driver_type: Used to point if a device supports raw mode * @change_protocol: callback to change protocol * @rc_query: called to query an event event. * @rc_interval: time in ms between two queries. @@ -190,6 +191,7 @@ struct dvb_rc { char *rc_codes; u64 protocol; u64 allowed_protos; + enum rc_driver_type driver_type; int (*change_protocol)(struct rc_dev *dev, u64 rc_type); char *module_name; int (*rc_query) (struct dvb_usb_device *d); diff --git a/drivers/media/dvb/dvb-usb/technisat-usb2.c b/drivers/media/dvb/dvb-usb/technisat-usb2.c new file mode 100644 index 000000000000..08f8842ad280 --- /dev/null +++ b/drivers/media/dvb/dvb-usb/technisat-usb2.c @@ -0,0 +1,807 @@ +/* + * Linux driver for Technisat DVB-S/S2 USB 2.0 device + * + * Copyright (C) 2010 Patrick Boettcher, + * Kernel Labs Inc. PO Box 745, St James, NY 11780 + * + * Development was sponsored by Technisat Digital UK Limited, whose + * registered office is Witan Gate House 500 - 600 Witan Gate West, + * Milton Keynes, MK9 1SH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * THIS PROGRAM IS PROVIDED "AS IS" AND BOTH THE COPYRIGHT HOLDER AND + * TECHNISAT DIGITAL UK LTD DISCLAIM ALL WARRANTIES WITH REGARD TO + * THIS PROGRAM INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. NEITHER THE COPYRIGHT HOLDER + * NOR TECHNISAT DIGITAL UK LIMITED SHALL BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS PROGRAM. See the + * GNU General Public License for more details. + */ + +#define DVB_USB_LOG_PREFIX "technisat-usb2" +#include "dvb-usb.h" + +#include "stv6110x.h" +#include "stv090x.h" + +/* module parameters */ +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, + "set debugging level (bit-mask: 1=info,2=eeprom,4=i2c,8=rc)." \ + DVB_USB_DEBUG_STATUS); + +/* disables all LED control command and + * also does not start the signal polling thread */ +static int disable_led_control; +module_param(disable_led_control, int, 0444); +MODULE_PARM_DESC(disable_led_control, + "disable LED control of the device " + "(default: 0 - LED control is active)."); + +/* device private data */ +struct technisat_usb2_state { + struct dvb_usb_device *dev; + struct delayed_work green_led_work; + u8 power_state; + + u16 last_scan_code; +}; + +/* debug print helpers */ +#define deb_info(args...) dprintk(debug, 0x01, args) +#define deb_eeprom(args...) dprintk(debug, 0x02, args) +#define deb_i2c(args...) dprintk(debug, 0x04, args) +#define deb_rc(args...) dprintk(debug, 0x08, args) + +/* vendor requests */ +#define SET_IFCLK_TO_EXTERNAL_TSCLK_VENDOR_REQUEST 0xB3 +#define SET_FRONT_END_RESET_VENDOR_REQUEST 0xB4 +#define GET_VERSION_INFO_VENDOR_REQUEST 0xB5 +#define SET_GREEN_LED_VENDOR_REQUEST 0xB6 +#define SET_RED_LED_VENDOR_REQUEST 0xB7 +#define GET_IR_DATA_VENDOR_REQUEST 0xB8 +#define SET_LED_TIMER_DIVIDER_VENDOR_REQUEST 0xB9 +#define SET_USB_REENUMERATION 0xBA + +/* i2c-access methods */ +#define I2C_SPEED_100KHZ_BIT 0x40 + +#define I2C_STATUS_NAK 7 +#define I2C_STATUS_OK 8 + +static int technisat_usb2_i2c_access(struct usb_device *udev, + u8 device_addr, u8 *tx, u8 txlen, u8 *rx, u8 rxlen) +{ + u8 b[64]; + int ret, actual_length; + + deb_i2c("i2c-access: %02x, tx: ", device_addr); + debug_dump(tx, txlen, deb_i2c); + deb_i2c(" "); + + if (txlen > 62) { + err("i2c TX buffer can't exceed 62 bytes (dev 0x%02x)", + device_addr); + txlen = 62; + } + if (rxlen > 62) { + err("i2c RX buffer can't exceed 62 bytes (dev 0x%02x)", + device_addr); + txlen = 62; + } + + b[0] = I2C_SPEED_100KHZ_BIT; + b[1] = device_addr << 1; + + if (rx != NULL) { + b[0] |= rxlen; + b[1] |= 1; + } + + memcpy(&b[2], tx, txlen); + ret = usb_bulk_msg(udev, + usb_sndbulkpipe(udev, 0x01), + b, 2 + txlen, + NULL, 1000); + + if (ret < 0) { + err("i2c-error: out failed %02x = %d", device_addr, ret); + return -ENODEV; + } + + ret = usb_bulk_msg(udev, + usb_rcvbulkpipe(udev, 0x01), + b, 64, &actual_length, 1000); + if (ret < 0) { + err("i2c-error: in failed %02x = %d", device_addr, ret); + return -ENODEV; + } + + if (b[0] != I2C_STATUS_OK) { + err("i2c-error: %02x = %d", device_addr, b[0]); + /* handle tuner-i2c-nak */ + if (!(b[0] == I2C_STATUS_NAK && + device_addr == 0x60 + /* && device_is_technisat_usb2 */)) + return -ENODEV; + } + + deb_i2c("status: %d, ", b[0]); + + if (rx != NULL) { + memcpy(rx, &b[2], rxlen); + + deb_i2c("rx (%d): ", rxlen); + debug_dump(rx, rxlen, deb_i2c); + } + + deb_i2c("\n"); + + return 0; +} + +static int technisat_usb2_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msg, + int num) +{ + int ret = 0, i; + struct dvb_usb_device *d = i2c_get_adapdata(adap); + + /* Ensure nobody else hits the i2c bus while we're sending our + sequence of messages, (such as the remote control thread) */ + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + for (i = 0; i < num; i++) { + if (i+1 < num && msg[i+1].flags & I2C_M_RD) { + ret = technisat_usb2_i2c_access(d->udev, msg[i+1].addr, + msg[i].buf, msg[i].len, + msg[i+1].buf, msg[i+1].len); + if (ret != 0) + break; + i++; + } else { + ret = technisat_usb2_i2c_access(d->udev, msg[i].addr, + msg[i].buf, msg[i].len, + NULL, 0); + if (ret != 0) + break; + } + } + + if (ret == 0) + ret = i; + + mutex_unlock(&d->i2c_mutex); + + return ret; +} + +static u32 technisat_usb2_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm technisat_usb2_i2c_algo = { + .master_xfer = technisat_usb2_i2c_xfer, + .functionality = technisat_usb2_i2c_func, +}; + +#if 0 +static void technisat_usb2_frontend_reset(struct usb_device *udev) +{ + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SET_FRONT_END_RESET_VENDOR_REQUEST, + USB_TYPE_VENDOR | USB_DIR_OUT, + 10, 0, + NULL, 0, 500); +} +#endif + +/* LED control */ +enum technisat_usb2_led_state { + LED_OFF, + LED_BLINK, + LED_ON, + LED_UNDEFINED +}; + +static int technisat_usb2_set_led(struct dvb_usb_device *d, int red, enum technisat_usb2_led_state state) +{ + int ret; + + u8 led[8] = { + red ? SET_RED_LED_VENDOR_REQUEST : SET_GREEN_LED_VENDOR_REQUEST, + 0 + }; + + if (disable_led_control && state != LED_OFF) + return 0; + + switch (state) { + case LED_ON: + led[1] = 0x82; + break; + case LED_BLINK: + led[1] = 0x82; + if (red) { + led[2] = 0x02; + led[3] = 10; + led[4] = 10; + } else { + led[2] = 0xff; + led[3] = 50; + led[4] = 50; + } + led[5] = 1; + break; + + default: + case LED_OFF: + led[1] = 0x80; + break; + } + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0), + red ? SET_RED_LED_VENDOR_REQUEST : SET_GREEN_LED_VENDOR_REQUEST, + USB_TYPE_VENDOR | USB_DIR_OUT, + 0, 0, + led, sizeof(led), 500); + + mutex_unlock(&d->i2c_mutex); + return ret; +} + +static int technisat_usb2_set_led_timer(struct dvb_usb_device *d, u8 red, u8 green) +{ + int ret; + u8 b = 0; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + + ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0), + SET_LED_TIMER_DIVIDER_VENDOR_REQUEST, + USB_TYPE_VENDOR | USB_DIR_OUT, + (red << 8) | green, 0, + &b, 1, 500); + + mutex_unlock(&d->i2c_mutex); + + return ret; +} + +static void technisat_usb2_green_led_control(struct work_struct *work) +{ + struct technisat_usb2_state *state = + container_of(work, struct technisat_usb2_state, green_led_work.work); + struct dvb_frontend *fe = state->dev->adapter[0].fe; + + if (state->power_state == 0) + goto schedule; + + if (fe != NULL) { + enum fe_status status; + + if (fe->ops.read_status(fe, &status) != 0) + goto schedule; + + if (status & FE_HAS_LOCK) { + u32 ber; + + if (fe->ops.read_ber(fe, &ber) != 0) + goto schedule; + + if (ber > 1000) + technisat_usb2_set_led(state->dev, 0, LED_BLINK); + else + technisat_usb2_set_led(state->dev, 0, LED_ON); + } else + technisat_usb2_set_led(state->dev, 0, LED_OFF); + } + +schedule: + schedule_delayed_work(&state->green_led_work, + msecs_to_jiffies(500)); +} + +/* method to find out whether the firmware has to be downloaded or not */ +static int technisat_usb2_identify_state(struct usb_device *udev, + struct dvb_usb_device_properties *props, + struct dvb_usb_device_description **desc, int *cold) +{ + int ret; + u8 version[3]; + + /* first select the interface */ + if (usb_set_interface(udev, 0, 1) != 0) + err("could not set alternate setting to 0"); + else + info("set alternate setting"); + + *cold = 0; /* by default do not download a firmware - just in case something is wrong */ + + ret = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + GET_VERSION_INFO_VENDOR_REQUEST, + USB_TYPE_VENDOR | USB_DIR_IN, + 0, 0, + version, sizeof(version), 500); + + if (ret < 0) + *cold = 1; + else { + info("firmware version: %d.%d", version[1], version[2]); + *cold = 0; + } + + return 0; +} + +/* power control */ +static int technisat_usb2_power_ctrl(struct dvb_usb_device *d, int level) +{ + struct technisat_usb2_state *state = d->priv; + + state->power_state = level; + + if (disable_led_control) + return 0; + + /* green led is turned off in any case - will be turned on when tuning */ + technisat_usb2_set_led(d, 0, LED_OFF); + /* red led is turned on all the time */ + technisat_usb2_set_led(d, 1, LED_ON); + return 0; +} + +/* mac address reading - from the eeprom */ +#if 0 +static void technisat_usb2_eeprom_dump(struct dvb_usb_device *d) +{ + u8 reg; + u8 b[16]; + int i, j; + + /* full EEPROM dump */ + for (j = 0; j < 256 * 4; j += 16) { + reg = j; + if (technisat_usb2_i2c_access(d->udev, 0x50 + j / 256, ®, 1, b, 16) != 0) + break; + + deb_eeprom("EEPROM: %01x%02x: ", j / 256, reg); + for (i = 0; i < 16; i++) + deb_eeprom("%02x ", b[i]); + deb_eeprom("\n"); + } +} +#endif + +static u8 technisat_usb2_calc_lrc(const u8 *b, u16 length) +{ + u8 lrc = 0; + while (--length) + lrc ^= *b++; + return lrc; +} + +static int technisat_usb2_eeprom_lrc_read(struct dvb_usb_device *d, + u16 offset, u8 *b, u16 length, u8 tries) +{ + u8 bo = offset & 0xff; + struct i2c_msg msg[] = { + { + .addr = 0x50 | ((offset >> 8) & 0x3), + .buf = &bo, + .len = 1 + }, { + .addr = 0x50 | ((offset >> 8) & 0x3), + .flags = I2C_M_RD, + .buf = b, + .len = length + } + }; + + while (tries--) { + int status; + + if (i2c_transfer(&d->i2c_adap, msg, 2) != 2) + break; + + status = + technisat_usb2_calc_lrc(b, length - 1) == b[length - 1]; + + if (status) + return 0; + } + + return -EREMOTEIO; +} + +#define EEPROM_MAC_START 0x3f8 +#define EEPROM_MAC_TOTAL 8 +static int technisat_usb2_read_mac_address(struct dvb_usb_device *d, + u8 mac[]) +{ + u8 buf[EEPROM_MAC_TOTAL]; + + if (technisat_usb2_eeprom_lrc_read(d, EEPROM_MAC_START, + buf, EEPROM_MAC_TOTAL, 4) != 0) + return -ENODEV; + + memcpy(mac, buf, 6); + return 0; +} + +/* frontend attach */ +static int technisat_usb2_set_voltage(struct dvb_frontend *fe, + fe_sec_voltage_t voltage) +{ + int i; + u8 gpio[3] = { 0 }; /* 0 = 2, 1 = 3, 2 = 4 */ + + gpio[2] = 1; /* high - voltage ? */ + + switch (voltage) { + case SEC_VOLTAGE_13: + gpio[0] = 1; + break; + case SEC_VOLTAGE_18: + gpio[0] = 1; + gpio[1] = 1; + break; + default: + case SEC_VOLTAGE_OFF: + break; + } + + for (i = 0; i < 3; i++) + if (stv090x_set_gpio(fe, i+2, 0, gpio[i], 0) != 0) + return -EREMOTEIO; + return 0; +} + +static struct stv090x_config technisat_usb2_stv090x_config = { + .device = STV0903, + .demod_mode = STV090x_SINGLE, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 8000000, + .address = 0x68, + + .ts1_mode = STV090x_TSMODE_DVBCI, + .ts1_clk = 13400000, + .ts1_tei = 1, + + .repeater_level = STV090x_RPTLEVEL_64, + + .tuner_bbgain = 6, +}; + +static struct stv6110x_config technisat_usb2_stv6110x_config = { + .addr = 0x60, + .refclk = 16000000, + .clk_div = 2, +}; + +static int technisat_usb2_frontend_attach(struct dvb_usb_adapter *a) +{ + struct usb_device *udev = a->dev->udev; + int ret; + + a->fe = dvb_attach(stv090x_attach, &technisat_usb2_stv090x_config, + &a->dev->i2c_adap, STV090x_DEMODULATOR_0); + + if (a->fe) { + struct stv6110x_devctl *ctl; + + ctl = dvb_attach(stv6110x_attach, + a->fe, + &technisat_usb2_stv6110x_config, + &a->dev->i2c_adap); + + if (ctl) { + technisat_usb2_stv090x_config.tuner_init = ctl->tuner_init; + technisat_usb2_stv090x_config.tuner_sleep = ctl->tuner_sleep; + technisat_usb2_stv090x_config.tuner_set_mode = ctl->tuner_set_mode; + technisat_usb2_stv090x_config.tuner_set_frequency = ctl->tuner_set_frequency; + technisat_usb2_stv090x_config.tuner_get_frequency = ctl->tuner_get_frequency; + technisat_usb2_stv090x_config.tuner_set_bandwidth = ctl->tuner_set_bandwidth; + technisat_usb2_stv090x_config.tuner_get_bandwidth = ctl->tuner_get_bandwidth; + technisat_usb2_stv090x_config.tuner_set_bbgain = ctl->tuner_set_bbgain; + technisat_usb2_stv090x_config.tuner_get_bbgain = ctl->tuner_get_bbgain; + technisat_usb2_stv090x_config.tuner_set_refclk = ctl->tuner_set_refclk; + technisat_usb2_stv090x_config.tuner_get_status = ctl->tuner_get_status; + + /* call the init function once to initialize + tuner's clock output divider and demod's + master clock */ + if (a->fe->ops.init) + a->fe->ops.init(a->fe); + + if (mutex_lock_interruptible(&a->dev->i2c_mutex) < 0) + return -EAGAIN; + + ret = usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + SET_IFCLK_TO_EXTERNAL_TSCLK_VENDOR_REQUEST, + USB_TYPE_VENDOR | USB_DIR_OUT, + 0, 0, + NULL, 0, 500); + mutex_unlock(&a->dev->i2c_mutex); + + if (ret != 0) + err("could not set IF_CLK to external"); + + a->fe->ops.set_voltage = technisat_usb2_set_voltage; + + /* if everything was successful assign a nice name to the frontend */ + strlcpy(a->fe->ops.info.name, a->dev->desc->name, + sizeof(a->fe->ops.info.name)); + } else { + dvb_frontend_detach(a->fe); + a->fe = NULL; + } + } + + technisat_usb2_set_led_timer(a->dev, 1, 1); + + return a->fe == NULL ? -ENODEV : 0; +} + +/* Remote control */ + +/* the device is giving providing raw IR-signals to the host mapping + * it only to one remote control is just the default implementation + */ +#define NOMINAL_IR_BIT_TRANSITION_TIME_US 889 +#define NOMINAL_IR_BIT_TIME_US (2 * NOMINAL_IR_BIT_TRANSITION_TIME_US) + +#define FIRMWARE_CLOCK_TICK 83333 +#define FIRMWARE_CLOCK_DIVISOR 256 + +#define IR_PERCENT_TOLERANCE 15 + +#define NOMINAL_IR_BIT_TRANSITION_TICKS ((NOMINAL_IR_BIT_TRANSITION_TIME_US * 1000 * 1000) / FIRMWARE_CLOCK_TICK) +#define NOMINAL_IR_BIT_TRANSITION_TICK_COUNT (NOMINAL_IR_BIT_TRANSITION_TICKS / FIRMWARE_CLOCK_DIVISOR) + +#define NOMINAL_IR_BIT_TIME_TICKS ((NOMINAL_IR_BIT_TIME_US * 1000 * 1000) / FIRMWARE_CLOCK_TICK) +#define NOMINAL_IR_BIT_TIME_TICK_COUNT (NOMINAL_IR_BIT_TIME_TICKS / FIRMWARE_CLOCK_DIVISOR) + +#define MINIMUM_IR_BIT_TRANSITION_TICK_COUNT (NOMINAL_IR_BIT_TRANSITION_TICK_COUNT - ((NOMINAL_IR_BIT_TRANSITION_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100)) +#define MAXIMUM_IR_BIT_TRANSITION_TICK_COUNT (NOMINAL_IR_BIT_TRANSITION_TICK_COUNT + ((NOMINAL_IR_BIT_TRANSITION_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100)) + +#define MINIMUM_IR_BIT_TIME_TICK_COUNT (NOMINAL_IR_BIT_TIME_TICK_COUNT - ((NOMINAL_IR_BIT_TIME_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100)) +#define MAXIMUM_IR_BIT_TIME_TICK_COUNT (NOMINAL_IR_BIT_TIME_TICK_COUNT + ((NOMINAL_IR_BIT_TIME_TICK_COUNT * IR_PERCENT_TOLERANCE) / 100)) + +static int technisat_usb2_get_ir(struct dvb_usb_device *d) +{ + u8 buf[62], *b; + int ret; + struct ir_raw_event ev; + + buf[0] = GET_IR_DATA_VENDOR_REQUEST; + buf[1] = 0x08; + buf[2] = 0x8f; + buf[3] = MINIMUM_IR_BIT_TRANSITION_TICK_COUNT; + buf[4] = MAXIMUM_IR_BIT_TIME_TICK_COUNT; + + if (mutex_lock_interruptible(&d->i2c_mutex) < 0) + return -EAGAIN; + ret = usb_control_msg(d->udev, usb_sndctrlpipe(d->udev, 0), + GET_IR_DATA_VENDOR_REQUEST, + USB_TYPE_VENDOR | USB_DIR_OUT, + 0, 0, + buf, 5, 500); + if (ret < 0) + goto unlock; + + buf[1] = 0; + buf[2] = 0; + ret = usb_control_msg(d->udev, usb_rcvctrlpipe(d->udev, 0), + GET_IR_DATA_VENDOR_REQUEST, + USB_TYPE_VENDOR | USB_DIR_IN, + 0x8080, 0, + buf, sizeof(buf), 500); + +unlock: + mutex_unlock(&d->i2c_mutex); + + if (ret < 0) + return ret; + + if (ret == 1) + return 0; /* no key pressed */ + + /* decoding */ + b = buf+1; + +#if 0 + deb_rc("RC: %d ", ret); + debug_dump(b, ret, deb_rc); +#endif + + ev.pulse = 0; + while (1) { + ev.pulse = !ev.pulse; + ev.duration = (*b * FIRMWARE_CLOCK_DIVISOR * FIRMWARE_CLOCK_TICK) / 1000; + ir_raw_event_store(d->rc_dev, &ev); + + b++; + if (*b == 0xff) { + ev.pulse = 0; + ev.duration = 888888*2; + ir_raw_event_store(d->rc_dev, &ev); + break; + } + } + + ir_raw_event_handle(d->rc_dev); + + return 1; +} + +static int technisat_usb2_rc_query(struct dvb_usb_device *d) +{ + int ret = technisat_usb2_get_ir(d); + + if (ret < 0) + return ret; + + if (ret == 0) + return 0; + + if (!disable_led_control) + technisat_usb2_set_led(d, 1, LED_BLINK); + + return 0; +} + +/* DVB-USB and USB stuff follows */ +static struct usb_device_id technisat_usb2_id_table[] = { + { USB_DEVICE(USB_VID_TECHNISAT, USB_PID_TECHNISAT_USB2_DVB_S2) }, + { 0 } /* Terminating entry */ +}; + +/* device description */ +static struct dvb_usb_device_properties technisat_usb2_devices = { + .caps = DVB_USB_IS_AN_I2C_ADAPTER, + + .usb_ctrl = CYPRESS_FX2, + + .identify_state = technisat_usb2_identify_state, + .firmware = "dvb-usb-SkyStar_USB_HD_FW_v17_63.HEX.fw", + + .size_of_priv = sizeof(struct technisat_usb2_state), + + .i2c_algo = &technisat_usb2_i2c_algo, + + .power_ctrl = technisat_usb2_power_ctrl, + .read_mac_address = technisat_usb2_read_mac_address, + + .num_adapters = 1, + .adapter = { + { + .frontend_attach = technisat_usb2_frontend_attach, + + .stream = { + .type = USB_ISOC, + .count = 8, + .endpoint = 0x2, + .u = { + .isoc = { + .framesperurb = 32, + .framesize = 2048, + .interval = 3, + } + } + }, + + .size_of_priv = 0, + }, + }, + + .num_device_descs = 1, + .devices = { + { "Technisat SkyStar USB HD (DVB-S/S2)", + { &technisat_usb2_id_table[0], NULL }, + { NULL }, + }, + }, + + .rc.core = { + .rc_interval = 100, + .rc_codes = RC_MAP_TECHNISAT_USB2, + .module_name = "technisat-usb2", + .rc_query = technisat_usb2_rc_query, + .allowed_protos = RC_TYPE_ALL, + .driver_type = RC_DRIVER_IR_RAW, + } +}; + +static int technisat_usb2_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct dvb_usb_device *dev; + + if (dvb_usb_device_init(intf, &technisat_usb2_devices, THIS_MODULE, + &dev, adapter_nr) != 0) + return -ENODEV; + + if (dev) { + struct technisat_usb2_state *state = dev->priv; + state->dev = dev; + + if (!disable_led_control) { + INIT_DELAYED_WORK(&state->green_led_work, + technisat_usb2_green_led_control); + schedule_delayed_work(&state->green_led_work, + msecs_to_jiffies(500)); + } + } + + return 0; +} + +static void technisat_usb2_disconnect(struct usb_interface *intf) +{ + struct dvb_usb_device *dev = usb_get_intfdata(intf); + + /* work and stuff was only created when the device is is hot-state */ + if (dev != NULL) { + struct technisat_usb2_state *state = dev->priv; + if (state != NULL) { + cancel_delayed_work_sync(&state->green_led_work); + flush_scheduled_work(); + } + } + + dvb_usb_device_exit(intf); +} + +static struct usb_driver technisat_usb2_driver = { + .name = "dvb_usb_technisat_usb2", + .probe = technisat_usb2_probe, + .disconnect = technisat_usb2_disconnect, + .id_table = technisat_usb2_id_table, +}; + +/* module stuff */ +static int __init technisat_usb2_module_init(void) +{ + int result = usb_register(&technisat_usb2_driver); + if (result) { + err("usb_register failed. Code %d", result); + return result; + } + + return 0; +} + +static void __exit technisat_usb2_module_exit(void) +{ + usb_deregister(&technisat_usb2_driver); +} + +module_init(technisat_usb2_module_init); +module_exit(technisat_usb2_module_exit); + +MODULE_AUTHOR("Patrick Boettcher <pboettcher@kernellabs.com>"); +MODULE_DESCRIPTION("Driver for Technisat DVB-S/S2 USB 2.0 device"); +MODULE_VERSION("1.0"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/Kconfig b/drivers/media/dvb/frontends/Kconfig index b8519ba511e5..a20c1532726a 100644 --- a/drivers/media/dvb/frontends/Kconfig +++ b/drivers/media/dvb/frontends/Kconfig @@ -349,6 +349,14 @@ config DVB_DIB7000P A DVB-T tuner module. Designed for mobile usage. Say Y when you want to support this frontend. +config DVB_DIB9000 + tristate "DiBcom 9000" + depends on DVB_CORE && I2C + default m if DVB_FE_CUSTOMISE + help + A DVB-T tuner module. Designed for mobile usage. Say Y when you want + to support this frontend. + config DVB_TDA10048 tristate "Philips TDA10048HN based" depends on DVB_CORE && I2C diff --git a/drivers/media/dvb/frontends/Makefile b/drivers/media/dvb/frontends/Makefile index b1d9525aa7e3..31dd201c57ce 100644 --- a/drivers/media/dvb/frontends/Makefile +++ b/drivers/media/dvb/frontends/Makefile @@ -24,6 +24,7 @@ obj-$(CONFIG_DVB_DIB3000MC) += dib3000mc.o dibx000_common.o obj-$(CONFIG_DVB_DIB7000M) += dib7000m.o dibx000_common.o obj-$(CONFIG_DVB_DIB7000P) += dib7000p.o dibx000_common.o obj-$(CONFIG_DVB_DIB8000) += dib8000.o dibx000_common.o +obj-$(CONFIG_DVB_DIB9000) += dib9000.o dibx000_common.o obj-$(CONFIG_DVB_MT312) += mt312.o obj-$(CONFIG_DVB_VES1820) += ves1820.o obj-$(CONFIG_DVB_VES1X93) += ves1x93.o diff --git a/drivers/media/dvb/frontends/dib0090.c b/drivers/media/dvb/frontends/dib0090.c index 65240b7801e8..52ff1a252a90 100644 --- a/drivers/media/dvb/frontends/dib0090.c +++ b/drivers/media/dvb/frontends/dib0090.c @@ -45,6 +45,7 @@ MODULE_PARM_DESC(debug, "turn on debugging (default: 0)"); } \ } while (0) +#define CONFIG_SYS_DVBT #define CONFIG_SYS_ISDBT #define CONFIG_BAND_CBAND #define CONFIG_BAND_VHF @@ -76,6 +77,34 @@ MODULE_PARM_DESC(debug, "turn on debugging (default: 0)"); #define EN_SBD 0x44E9 #define EN_CAB 0x88E9 +/* Calibration defines */ +#define DC_CAL 0x1 +#define WBD_CAL 0x2 +#define TEMP_CAL 0x4 +#define CAPTRIM_CAL 0x8 + +#define KROSUS_PLL_LOCKED 0x800 +#define KROSUS 0x2 + +/* Use those defines to identify SOC version */ +#define SOC 0x02 +#define SOC_7090_P1G_11R1 0x82 +#define SOC_7090_P1G_21R1 0x8a +#define SOC_8090_P1G_11R1 0x86 +#define SOC_8090_P1G_21R1 0x8e + +/* else use thos ones to check */ +#define P1A_B 0x0 +#define P1C 0x1 +#define P1D_E_F 0x3 +#define P1G 0x7 +#define P1G_21R2 0xf + +#define MP001 0x1 /* Single 9090/8096 */ +#define MP005 0x4 /* Single Sband */ +#define MP008 0x6 /* Dual diversity VHF-UHF-LBAND */ +#define MP009 0x7 /* Dual diversity 29098 CBAND-UHF-LBAND-SBAND */ + #define pgm_read_word(w) (*w) struct dc_calibration; @@ -84,7 +113,7 @@ struct dib0090_tuning { u32 max_freq; /* for every frequency less than or equal to that field: this information is correct */ u8 switch_trim; u8 lna_tune; - u8 lna_bias; + u16 lna_bias; u16 v2i; u16 mix; u16 load; @@ -99,13 +128,19 @@ struct dib0090_pll { u8 topresc; }; +struct dib0090_identity { + u8 version; + u8 product; + u8 p1g; + u8 in_soc; +}; + struct dib0090_state { struct i2c_adapter *i2c; struct dvb_frontend *fe; const struct dib0090_config *config; u8 current_band; - u16 revision; enum frontend_tune_state tune_state; u32 current_rf; @@ -143,7 +178,26 @@ struct dib0090_state { u8 tuner_is_tuned; u8 agc_freeze; - u8 reset; + struct dib0090_identity identity; + + u32 rf_request; + u8 current_standard; + + u8 calibrate; + u32 rest; + u16 bias; + s16 temperature; + + u8 wbd_calibration_gain; + const struct dib0090_wbd_slope *current_wbd_table; + u16 wbdmux; +}; + +struct dib0090_fw_state { + struct i2c_adapter *i2c; + struct dvb_frontend *fe; + struct dib0090_identity identity; + const struct dib0090_config *config; }; static u16 dib0090_read_reg(struct dib0090_state *state, u8 reg) @@ -171,6 +225,28 @@ static int dib0090_write_reg(struct dib0090_state *state, u32 reg, u16 val) return 0; } +static u16 dib0090_fw_read_reg(struct dib0090_fw_state *state, u8 reg) +{ + u8 b[2]; + struct i2c_msg msg = {.addr = reg, .flags = I2C_M_RD, .buf = b, .len = 2 }; + if (i2c_transfer(state->i2c, &msg, 1) != 1) { + printk(KERN_WARNING "DiB0090 I2C read failed\n"); + return 0; + } + return (b[0] << 8) | b[1]; +} + +static int dib0090_fw_write_reg(struct dib0090_fw_state *state, u8 reg, u16 val) +{ + u8 b[2] = { val >> 8, val & 0xff }; + struct i2c_msg msg = {.addr = reg, .flags = 0, .buf = b, .len = 2 }; + if (i2c_transfer(state->i2c, &msg, 1) != 1) { + printk(KERN_WARNING "DiB0090 I2C write failed\n"); + return -EREMOTEIO; + } + return 0; +} + #define HARD_RESET(state) do { if (cfg->reset) { if (cfg->sleep) cfg->sleep(fe, 0); msleep(10); cfg->reset(fe, 1); msleep(10); cfg->reset(fe, 0); msleep(10); } } while (0) #define ADC_TARGET -220 #define GAIN_ALPHA 5 @@ -183,89 +259,327 @@ static void dib0090_write_regs(struct dib0090_state *state, u8 r, const u16 * b, } while (--c); } -static u16 dib0090_identify(struct dvb_frontend *fe) +static int dib0090_identify(struct dvb_frontend *fe) { struct dib0090_state *state = fe->tuner_priv; u16 v; + struct dib0090_identity *identity = &state->identity; v = dib0090_read_reg(state, 0x1a); -#ifdef FIRMWARE_FIREFLY - /* pll is not locked locked */ - if (!(v & 0x800)) - dprintk("FE%d : Identification : pll is not yet locked", fe->id); -#endif + identity->p1g = 0; + identity->in_soc = 0; + + dprintk("Tuner identification (Version = 0x%04x)", v); /* without PLL lock info */ - v &= 0x3ff; - dprintk("P/V: %04x:", v); + v &= ~KROSUS_PLL_LOCKED; - if ((v >> 8) & 0xf) - dprintk("FE%d : Product ID = 0x%x : KROSUS", fe->id, (v >> 8) & 0xf); - else - return 0xff; - - v &= 0xff; - if (((v >> 5) & 0x7) == 0x1) - dprintk("FE%d : MP001 : 9090/8096", fe->id); - else if (((v >> 5) & 0x7) == 0x4) - dprintk("FE%d : MP005 : Single Sband", fe->id); - else if (((v >> 5) & 0x7) == 0x6) - dprintk("FE%d : MP008 : diversity VHF-UHF-LBAND", fe->id); - else if (((v >> 5) & 0x7) == 0x7) - dprintk("FE%d : MP009 : diversity 29098 CBAND-UHF-LBAND-SBAND", fe->id); - else - return 0xff; - - /* revision only */ - if ((v & 0x1f) == 0x3) - dprintk("FE%d : P1-D/E/F detected", fe->id); - else if ((v & 0x1f) == 0x1) - dprintk("FE%d : P1C detected", fe->id); - else if ((v & 0x1f) == 0x0) { -#ifdef CONFIG_TUNER_DIB0090_P1B_SUPPORT - dprintk("FE%d : P1-A/B detected: using previous driver - support will be removed soon", fe->id); - dib0090_p1b_register(fe); -#else - dprintk("FE%d : P1-A/B detected: driver is deactivated - not available", fe->id); - return 0xff; -#endif + identity->version = v & 0xff; + identity->product = (v >> 8) & 0xf; + + if (identity->product != KROSUS) + goto identification_error; + + if ((identity->version & 0x3) == SOC) { + identity->in_soc = 1; + switch (identity->version) { + case SOC_8090_P1G_11R1: + dprintk("SOC 8090 P1-G11R1 Has been detected"); + identity->p1g = 1; + break; + case SOC_8090_P1G_21R1: + dprintk("SOC 8090 P1-G21R1 Has been detected"); + identity->p1g = 1; + break; + case SOC_7090_P1G_11R1: + dprintk("SOC 7090 P1-G11R1 Has been detected"); + identity->p1g = 1; + break; + case SOC_7090_P1G_21R1: + dprintk("SOC 7090 P1-G21R1 Has been detected"); + identity->p1g = 1; + break; + default: + goto identification_error; + } + } else { + switch ((identity->version >> 5) & 0x7) { + case MP001: + dprintk("MP001 : 9090/8096"); + break; + case MP005: + dprintk("MP005 : Single Sband"); + break; + case MP008: + dprintk("MP008 : diversity VHF-UHF-LBAND"); + break; + case MP009: + dprintk("MP009 : diversity 29098 CBAND-UHF-LBAND-SBAND"); + break; + default: + goto identification_error; + } + + switch (identity->version & 0x1f) { + case P1G_21R2: + dprintk("P1G_21R2 detected"); + identity->p1g = 1; + break; + case P1G: + dprintk("P1G detected"); + identity->p1g = 1; + break; + case P1D_E_F: + dprintk("P1D/E/F detected"); + break; + case P1C: + dprintk("P1C detected"); + break; + case P1A_B: + dprintk("P1-A/B detected: driver is deactivated - not available"); + goto identification_error; + break; + default: + goto identification_error; + } } - return v; + return 0; + +identification_error: + return -EIO; +} + +static int dib0090_fw_identify(struct dvb_frontend *fe) +{ + struct dib0090_fw_state *state = fe->tuner_priv; + struct dib0090_identity *identity = &state->identity; + + u16 v = dib0090_fw_read_reg(state, 0x1a); + identity->p1g = 0; + identity->in_soc = 0; + + dprintk("FE: Tuner identification (Version = 0x%04x)", v); + + /* without PLL lock info */ + v &= ~KROSUS_PLL_LOCKED; + + identity->version = v & 0xff; + identity->product = (v >> 8) & 0xf; + + if (identity->product != KROSUS) + goto identification_error; + + if ((identity->version & 0x3) == SOC) { + identity->in_soc = 1; + switch (identity->version) { + case SOC_8090_P1G_11R1: + dprintk("SOC 8090 P1-G11R1 Has been detected"); + identity->p1g = 1; + break; + case SOC_8090_P1G_21R1: + dprintk("SOC 8090 P1-G21R1 Has been detected"); + identity->p1g = 1; + break; + case SOC_7090_P1G_11R1: + dprintk("SOC 7090 P1-G11R1 Has been detected"); + identity->p1g = 1; + break; + case SOC_7090_P1G_21R1: + dprintk("SOC 7090 P1-G21R1 Has been detected"); + identity->p1g = 1; + break; + default: + goto identification_error; + } + } else { + switch ((identity->version >> 5) & 0x7) { + case MP001: + dprintk("MP001 : 9090/8096"); + break; + case MP005: + dprintk("MP005 : Single Sband"); + break; + case MP008: + dprintk("MP008 : diversity VHF-UHF-LBAND"); + break; + case MP009: + dprintk("MP009 : diversity 29098 CBAND-UHF-LBAND-SBAND"); + break; + default: + goto identification_error; + } + + switch (identity->version & 0x1f) { + case P1G_21R2: + dprintk("P1G_21R2 detected"); + identity->p1g = 1; + break; + case P1G: + dprintk("P1G detected"); + identity->p1g = 1; + break; + case P1D_E_F: + dprintk("P1D/E/F detected"); + break; + case P1C: + dprintk("P1C detected"); + break; + case P1A_B: + dprintk("P1-A/B detected: driver is deactivated - not available"); + goto identification_error; + break; + default: + goto identification_error; + } + } + + return 0; + +identification_error: + return -EIO;; } static void dib0090_reset_digital(struct dvb_frontend *fe, const struct dib0090_config *cfg) { struct dib0090_state *state = fe->tuner_priv; + u16 PllCfg, i, v; HARD_RESET(state); - dib0090_write_reg(state, 0x24, EN_PLL); + dib0090_write_reg(state, 0x24, EN_PLL | EN_CRYSTAL); dib0090_write_reg(state, 0x1b, EN_DIGCLK | EN_PLL | EN_CRYSTAL); /* PLL, DIG_CLK and CRYSTAL remain */ - /* adcClkOutRatio=8->7, release reset */ - dib0090_write_reg(state, 0x20, ((cfg->io.adc_clock_ratio - 1) << 11) | (0 << 10) | (1 << 9) | (1 << 8) | (0 << 4) | 0); + if (!cfg->in_soc) { + /* adcClkOutRatio=8->7, release reset */ + dib0090_write_reg(state, 0x20, ((cfg->io.adc_clock_ratio - 1) << 11) | (0 << 10) | (1 << 9) | (1 << 8) | (0 << 4) | 0); + if (cfg->clkoutdrive != 0) + dib0090_write_reg(state, 0x23, (0 << 15) | ((!cfg->analog_output) << 14) | (2 << 10) | (1 << 9) | (0 << 8) + | (cfg->clkoutdrive << 5) | (cfg->clkouttobamse << 4) | (0 << 2) | (0)); + else + dib0090_write_reg(state, 0x23, (0 << 15) | ((!cfg->analog_output) << 14) | (2 << 10) | (1 << 9) | (0 << 8) + | (7 << 5) | (cfg->clkouttobamse << 4) | (0 << 2) | (0)); + } + + /* Read Pll current config * */ + PllCfg = dib0090_read_reg(state, 0x21); + + /** Reconfigure PLL if current setting is different from default setting **/ + if ((PllCfg & 0x1FFF) != ((cfg->io.pll_range << 12) | (cfg->io.pll_loopdiv << 6) | (cfg->io.pll_prediv)) && (!cfg->in_soc) + && !cfg->io.pll_bypass) { + + /* Set Bypass mode */ + PllCfg |= (1 << 15); + dib0090_write_reg(state, 0x21, PllCfg); + + /* Set Reset Pll */ + PllCfg &= ~(1 << 13); + dib0090_write_reg(state, 0x21, PllCfg); + + /*** Set new Pll configuration in bypass and reset state ***/ + PllCfg = (1 << 15) | (0 << 13) | (cfg->io.pll_range << 12) | (cfg->io.pll_loopdiv << 6) | (cfg->io.pll_prediv); + dib0090_write_reg(state, 0x21, PllCfg); + + /* Remove Reset Pll */ + PllCfg |= (1 << 13); + dib0090_write_reg(state, 0x21, PllCfg); + + /*** Wait for PLL lock ***/ + i = 100; + do { + v = !!(dib0090_read_reg(state, 0x1a) & 0x800); + if (v) + break; + } while (--i); + + if (i == 0) { + dprintk("Pll: Unable to lock Pll"); + return; + } + + /* Finally Remove Bypass mode */ + PllCfg &= ~(1 << 15); + dib0090_write_reg(state, 0x21, PllCfg); + } + + if (cfg->io.pll_bypass) { + PllCfg |= (cfg->io.pll_bypass << 15); + dib0090_write_reg(state, 0x21, PllCfg); + } +} + +static int dib0090_fw_reset_digital(struct dvb_frontend *fe, const struct dib0090_config *cfg) +{ + struct dib0090_fw_state *state = fe->tuner_priv; + u16 PllCfg; + u16 v; + int i; + + dprintk("fw reset digital"); + HARD_RESET(state); + + dib0090_fw_write_reg(state, 0x24, EN_PLL | EN_CRYSTAL); + dib0090_fw_write_reg(state, 0x1b, EN_DIGCLK | EN_PLL | EN_CRYSTAL); /* PLL, DIG_CLK and CRYSTAL remain */ + + dib0090_fw_write_reg(state, 0x20, + ((cfg->io.adc_clock_ratio - 1) << 11) | (0 << 10) | (1 << 9) | (1 << 8) | (cfg->data_tx_drv << 4) | cfg->ls_cfg_pad_drv); + + v = (0 << 15) | ((!cfg->analog_output) << 14) | (1 << 9) | (0 << 8) | (cfg->clkouttobamse << 4) | (0 << 2) | (0); if (cfg->clkoutdrive != 0) - dib0090_write_reg(state, 0x23, - (0 << 15) | ((!cfg->analog_output) << 14) | (1 << 10) | (1 << 9) | (0 << 8) | (cfg->clkoutdrive << 5) | (cfg-> - clkouttobamse - << 4) | (0 - << - 2) - | (0)); + v |= cfg->clkoutdrive << 5; else - dib0090_write_reg(state, 0x23, - (0 << 15) | ((!cfg->analog_output) << 14) | (1 << 10) | (1 << 9) | (0 << 8) | (7 << 5) | (cfg-> - clkouttobamse << 4) | (0 - << - 2) - | (0)); + v |= 7 << 5; + + v |= 2 << 10; + dib0090_fw_write_reg(state, 0x23, v); + + /* Read Pll current config * */ + PllCfg = dib0090_fw_read_reg(state, 0x21); + + /** Reconfigure PLL if current setting is different from default setting **/ + if ((PllCfg & 0x1FFF) != ((cfg->io.pll_range << 12) | (cfg->io.pll_loopdiv << 6) | (cfg->io.pll_prediv)) && !cfg->io.pll_bypass) { - /* enable pll, de-activate reset, ratio: 2/1 = 60MHz */ - dib0090_write_reg(state, 0x21, - (cfg->io.pll_bypass << 15) | (1 << 13) | (cfg->io.pll_range << 12) | (cfg->io.pll_loopdiv << 6) | (cfg->io.pll_prediv)); + /* Set Bypass mode */ + PllCfg |= (1 << 15); + dib0090_fw_write_reg(state, 0x21, PllCfg); + /* Set Reset Pll */ + PllCfg &= ~(1 << 13); + dib0090_fw_write_reg(state, 0x21, PllCfg); + + /*** Set new Pll configuration in bypass and reset state ***/ + PllCfg = (1 << 15) | (0 << 13) | (cfg->io.pll_range << 12) | (cfg->io.pll_loopdiv << 6) | (cfg->io.pll_prediv); + dib0090_fw_write_reg(state, 0x21, PllCfg); + + /* Remove Reset Pll */ + PllCfg |= (1 << 13); + dib0090_fw_write_reg(state, 0x21, PllCfg); + + /*** Wait for PLL lock ***/ + i = 100; + do { + v = !!(dib0090_fw_read_reg(state, 0x1a) & 0x800); + if (v) + break; + } while (--i); + + if (i == 0) { + dprintk("Pll: Unable to lock Pll"); + return -EIO; + } + + /* Finally Remove Bypass mode */ + PllCfg &= ~(1 << 15); + dib0090_fw_write_reg(state, 0x21, PllCfg); + } + + if (cfg->io.pll_bypass) { + PllCfg |= (cfg->io.pll_bypass << 15); + dib0090_fw_write_reg(state, 0x21, PllCfg); + } + + return dib0090_fw_identify(fe); } static int dib0090_wakeup(struct dvb_frontend *fe) @@ -273,6 +587,9 @@ static int dib0090_wakeup(struct dvb_frontend *fe) struct dib0090_state *state = fe->tuner_priv; if (state->config->sleep) state->config->sleep(fe, 0); + + /* enable dataTX in case we have been restarted in the wrong moment */ + dib0090_write_reg(state, 0x23, dib0090_read_reg(state, 0x23) | (1 << 14)); return 0; } @@ -292,8 +609,75 @@ void dib0090_dcc_freq(struct dvb_frontend *fe, u8 fast) else dib0090_write_reg(state, 0x04, 1); } + EXPORT_SYMBOL(dib0090_dcc_freq); +static const u16 bb_ramp_pwm_normal_socs[] = { + 550, /* max BB gain in 10th of dB */ + (1 << 9) | 8, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> BB_RAMP2 */ + 440, + (4 << 9) | 0, /* BB_RAMP3 = 26dB */ + (0 << 9) | 208, /* BB_RAMP4 */ + (4 << 9) | 208, /* BB_RAMP5 = 29dB */ + (0 << 9) | 440, /* BB_RAMP6 */ +}; + +static const u16 rf_ramp_pwm_cband_7090[] = { + 280, /* max RF gain in 10th of dB */ + 18, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> RF_RAMP2 */ + 504, /* ramp_max = maximum X used on the ramp */ + (29 << 10) | 364, /* RF_RAMP5, LNA 1 = 8dB */ + (0 << 10) | 504, /* RF_RAMP6, LNA 1 */ + (60 << 10) | 228, /* RF_RAMP7, LNA 2 = 7.7dB */ + (0 << 10) | 364, /* RF_RAMP8, LNA 2 */ + (34 << 10) | 109, /* GAIN_4_1, LNA 3 = 6.8dB */ + (0 << 10) | 228, /* GAIN_4_2, LNA 3 */ + (37 << 10) | 0, /* RF_RAMP3, LNA 4 = 6.2dB */ + (0 << 10) | 109, /* RF_RAMP4, LNA 4 */ +}; + +static const u16 rf_ramp_pwm_cband_8090[] = { + 345, /* max RF gain in 10th of dB */ + 29, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> RF_RAMP2 */ + 1000, /* ramp_max = maximum X used on the ramp */ + (35 << 10) | 772, /* RF_RAMP3, LNA 1 = 8dB */ + (0 << 10) | 1000, /* RF_RAMP4, LNA 1 */ + (58 << 10) | 496, /* RF_RAMP5, LNA 2 = 9.5dB */ + (0 << 10) | 772, /* RF_RAMP6, LNA 2 */ + (27 << 10) | 200, /* RF_RAMP7, LNA 3 = 10.5dB */ + (0 << 10) | 496, /* RF_RAMP8, LNA 3 */ + (40 << 10) | 0, /* GAIN_4_1, LNA 4 = 7dB */ + (0 << 10) | 200, /* GAIN_4_2, LNA 4 */ +}; + +static const u16 rf_ramp_pwm_uhf_7090[] = { + 407, /* max RF gain in 10th of dB */ + 13, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> RF_RAMP2 */ + 529, /* ramp_max = maximum X used on the ramp */ + (23 << 10) | 0, /* RF_RAMP3, LNA 1 = 14.7dB */ + (0 << 10) | 176, /* RF_RAMP4, LNA 1 */ + (63 << 10) | 400, /* RF_RAMP5, LNA 2 = 8dB */ + (0 << 10) | 529, /* RF_RAMP6, LNA 2 */ + (48 << 10) | 316, /* RF_RAMP7, LNA 3 = 6.8dB */ + (0 << 10) | 400, /* RF_RAMP8, LNA 3 */ + (29 << 10) | 176, /* GAIN_4_1, LNA 4 = 11.5dB */ + (0 << 10) | 316, /* GAIN_4_2, LNA 4 */ +}; + +static const u16 rf_ramp_pwm_uhf_8090[] = { + 388, /* max RF gain in 10th of dB */ + 26, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> RF_RAMP2 */ + 1008, /* ramp_max = maximum X used on the ramp */ + (11 << 10) | 0, /* RF_RAMP3, LNA 1 = 14.7dB */ + (0 << 10) | 369, /* RF_RAMP4, LNA 1 */ + (41 << 10) | 809, /* RF_RAMP5, LNA 2 = 8dB */ + (0 << 10) | 1008, /* RF_RAMP6, LNA 2 */ + (27 << 10) | 659, /* RF_RAMP7, LNA 3 = 6dB */ + (0 << 10) | 809, /* RF_RAMP8, LNA 3 */ + (14 << 10) | 369, /* GAIN_4_1, LNA 4 = 11.5dB */ + (0 << 10) | 659, /* GAIN_4_2, LNA 4 */ +}; + static const u16 rf_ramp_pwm_cband[] = { 0, /* max RF gain in 10th of dB */ 0, /* ramp_slope = 1dB of gain -> clock_ticks_per_db = clk_khz / ramp_slope -> 0x2b */ @@ -326,6 +710,16 @@ static const u16 rf_ramp_uhf[] = { 0, 0, 127, /* CBAND : 0.0 dB */ }; +static const u16 rf_ramp_cband_broadmatching[] = /* for p1G only */ +{ + 314, /* Calibrated at 200MHz order has been changed g4-g3-g2-g1 */ + 84, 314, 127, /* LNA1 */ + 80, 230, 255, /* LNA2 */ + 80, 150, 127, /* LNA3 It was measured 12dB, do not lock if 120 */ + 70, 70, 127, /* LNA4 */ + 0, 0, 127, /* CBAND */ +}; + static const u16 rf_ramp_cband[] = { 332, /* max RF gain in 10th of dB */ 132, 252, 127, /* LNA1, dB */ @@ -380,8 +774,8 @@ static const u16 bb_ramp_pwm_normal[] = { }; struct slope { - int16_t range; - int16_t slope; + s16 range; + s16 slope; }; static u16 slopes_to_scale(const struct slope *slopes, u8 num, s16 val) { @@ -597,19 +991,39 @@ void dib0090_pwm_gain_reset(struct dvb_frontend *fe) #endif #ifdef CONFIG_BAND_CBAND if (state->current_band == BAND_CBAND) { - dib0090_set_rframp_pwm(state, rf_ramp_pwm_cband); - dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal); + if (state->identity.in_soc) { + dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal_socs); + if (state->identity.version == SOC_8090_P1G_11R1 || state->identity.version == SOC_8090_P1G_21R1) + dib0090_set_rframp_pwm(state, rf_ramp_pwm_cband_8090); + else if (state->identity.version == SOC_7090_P1G_11R1 || state->identity.version == SOC_7090_P1G_21R1) + dib0090_set_rframp_pwm(state, rf_ramp_pwm_cband_7090); + } else { + dib0090_set_rframp_pwm(state, rf_ramp_pwm_cband); + dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal); + } } else #endif #ifdef CONFIG_BAND_VHF if (state->current_band == BAND_VHF) { - dib0090_set_rframp_pwm(state, rf_ramp_pwm_vhf); - dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal); + if (state->identity.in_soc) { + dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal_socs); + } else { + dib0090_set_rframp_pwm(state, rf_ramp_pwm_vhf); + dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal); + } } else #endif { - dib0090_set_rframp_pwm(state, rf_ramp_pwm_uhf); - dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal); + if (state->identity.in_soc) { + if (state->identity.version == SOC_8090_P1G_11R1 || state->identity.version == SOC_8090_P1G_21R1) + dib0090_set_rframp_pwm(state, rf_ramp_pwm_uhf_8090); + else if (state->identity.version == SOC_7090_P1G_11R1 || state->identity.version == SOC_7090_P1G_21R1) + dib0090_set_rframp_pwm(state, rf_ramp_pwm_uhf_7090); + dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal_socs); + } else { + dib0090_set_rframp_pwm(state, rf_ramp_pwm_uhf); + dib0090_set_bbramp_pwm(state, bb_ramp_pwm_normal); + } } if (state->rf_ramp[0] != 0) @@ -617,11 +1031,21 @@ void dib0090_pwm_gain_reset(struct dvb_frontend *fe) else dib0090_write_reg(state, 0x32, (0 << 11)); + dib0090_write_reg(state, 0x04, 0x01); dib0090_write_reg(state, 0x39, (1 << 10)); } } + EXPORT_SYMBOL(dib0090_pwm_gain_reset); +static u32 dib0090_get_slow_adc_val(struct dib0090_state *state) +{ + u16 adc_val = dib0090_read_reg(state, 0x1d); + if (state->identity.in_soc) + adc_val >>= 2; + return adc_val; +} + int dib0090_gain_control(struct dvb_frontend *fe) { struct dib0090_state *state = fe->tuner_priv; @@ -643,18 +1067,21 @@ int dib0090_gain_control(struct dvb_frontend *fe) } else #endif #ifdef CONFIG_BAND_VHF - if (state->current_band == BAND_VHF) { + if (state->current_band == BAND_VHF && !state->identity.p1g) { dib0090_set_rframp(state, rf_ramp_vhf); dib0090_set_bbramp(state, bb_ramp_boost); } else #endif #ifdef CONFIG_BAND_CBAND - if (state->current_band == BAND_CBAND) { + if (state->current_band == BAND_CBAND && !state->identity.p1g) { dib0090_set_rframp(state, rf_ramp_cband); dib0090_set_bbramp(state, bb_ramp_boost); } else #endif - { + if ((state->current_band == BAND_CBAND || state->current_band == BAND_VHF) && state->identity.p1g) { + dib0090_set_rframp(state, rf_ramp_cband_broadmatching); + dib0090_set_bbramp(state, bb_ramp_boost); + } else { dib0090_set_rframp(state, rf_ramp_uhf); dib0090_set_bbramp(state, bb_ramp_boost); } @@ -669,17 +1096,25 @@ int dib0090_gain_control(struct dvb_frontend *fe) *tune_state = CT_AGC_STEP_0; } else if (!state->agc_freeze) { - s16 wbd; + s16 wbd = 0, i, cnt; int adc; - wbd_val = dib0090_read_reg(state, 0x1d); + wbd_val = dib0090_get_slow_adc_val(state); - /* read and calc the wbd power */ - wbd = dib0090_wbd_to_db(state, wbd_val); + if (*tune_state == CT_AGC_STEP_0) + cnt = 5; + else + cnt = 1; + + for (i = 0; i < cnt; i++) { + wbd_val = dib0090_get_slow_adc_val(state); + wbd += dib0090_wbd_to_db(state, wbd_val); + } + wbd /= cnt; wbd_error = state->wbd_target - wbd; if (*tune_state == CT_AGC_STEP_0) { - if (wbd_error < 0 && state->rf_gain_limit > 0) { + if (wbd_error < 0 && state->rf_gain_limit > 0 && !state->identity.p1g) { #ifdef CONFIG_BAND_CBAND /* in case of CBAND tune reduce first the lt_gain2 before adjusting the RF gain */ u8 ltg2 = (state->rf_lt_def >> 10) & 0x7; @@ -700,39 +1135,39 @@ int dib0090_gain_control(struct dvb_frontend *fe) adc_error = (s16) (((s32) ADC_TARGET) - adc); #ifdef CONFIG_STANDARD_DAB if (state->fe->dtv_property_cache.delivery_system == STANDARD_DAB) - adc_error += 130; + adc_error -= 10; #endif #ifdef CONFIG_STANDARD_DVBT if (state->fe->dtv_property_cache.delivery_system == STANDARD_DVBT && - (state->fe->dtv_property_cache.modulation == QAM_64 || state->fe->dtv_property_cache.modulation == QAM_16)) + (state->fe->dtv_property_cache.modulation == QAM_64 || state->fe->dtv_property_cache.modulation == QAM_16)) adc_error += 60; #endif #ifdef CONFIG_SYS_ISDBT if ((state->fe->dtv_property_cache.delivery_system == SYS_ISDBT) && (((state->fe->dtv_property_cache.layer[0].segment_count > - 0) - && - ((state->fe->dtv_property_cache.layer[0].modulation == - QAM_64) - || (state->fe->dtv_property_cache.layer[0]. - modulation == QAM_16))) - || - ((state->fe->dtv_property_cache.layer[1].segment_count > - 0) - && - ((state->fe->dtv_property_cache.layer[1].modulation == - QAM_64) - || (state->fe->dtv_property_cache.layer[1]. - modulation == QAM_16))) - || - ((state->fe->dtv_property_cache.layer[2].segment_count > - 0) - && - ((state->fe->dtv_property_cache.layer[2].modulation == - QAM_64) - || (state->fe->dtv_property_cache.layer[2]. - modulation == QAM_16))) - ) - ) + 0) + && + ((state->fe->dtv_property_cache.layer[0].modulation == + QAM_64) + || (state->fe->dtv_property_cache. + layer[0].modulation == QAM_16))) + || + ((state->fe->dtv_property_cache.layer[1].segment_count > + 0) + && + ((state->fe->dtv_property_cache.layer[1].modulation == + QAM_64) + || (state->fe->dtv_property_cache. + layer[1].modulation == QAM_16))) + || + ((state->fe->dtv_property_cache.layer[2].segment_count > + 0) + && + ((state->fe->dtv_property_cache.layer[2].modulation == + QAM_64) + || (state->fe->dtv_property_cache. + layer[2].modulation == QAM_16))) + ) + ) adc_error += 60; #endif @@ -760,9 +1195,9 @@ int dib0090_gain_control(struct dvb_frontend *fe) } #ifdef DEBUG_AGC dprintk - ("FE: %d, tune state %d, ADC = %3ddB (ADC err %3d) WBD %3ddB (WBD err %3d, WBD val SADC: %4d), RFGainLimit (TOP): %3d, signal: %3ddBm", - (u32) fe->id, (u32) *tune_state, (u32) adc, (u32) adc_error, (u32) wbd, (u32) wbd_error, (u32) wbd_val, - (u32) state->rf_gain_limit >> WBD_ALPHA, (s32) 200 + adc - (state->current_gain >> GAIN_ALPHA)); + ("tune state %d, ADC = %3ddB (ADC err %3d) WBD %3ddB (WBD err %3d, WBD val SADC: %4d), RFGainLimit (TOP): %3d, signal: %3ddBm", + (u32) *tune_state, (u32) adc, (u32) adc_error, (u32) wbd, (u32) wbd_error, (u32) wbd_val, + (u32) state->rf_gain_limit >> WBD_ALPHA, (s32) 200 + adc - (state->current_gain >> GAIN_ALPHA)); #endif } @@ -771,6 +1206,7 @@ int dib0090_gain_control(struct dvb_frontend *fe) dib0090_gain_apply(state, adc_error, wbd_error, apply_gain_immediatly); return ret; } + EXPORT_SYMBOL(dib0090_gain_control); void dib0090_get_current_gain(struct dvb_frontend *fe, u16 * rf, u16 * bb, u16 * rf_gain_limit, u16 * rflt) @@ -785,13 +1221,47 @@ void dib0090_get_current_gain(struct dvb_frontend *fe, u16 * rf, u16 * bb, u16 * if (rflt) *rflt = (state->rf_lt_def >> 10) & 0x7; } + EXPORT_SYMBOL(dib0090_get_current_gain); -u16 dib0090_get_wbd_offset(struct dvb_frontend *tuner) +u16 dib0090_get_wbd_offset(struct dvb_frontend *fe) { - struct dib0090_state *st = tuner->tuner_priv; - return st->wbd_offset; + struct dib0090_state *state = fe->tuner_priv; + u32 f_MHz = state->fe->dtv_property_cache.frequency / 1000000; + s32 current_temp = state->temperature; + s32 wbd_thot, wbd_tcold; + const struct dib0090_wbd_slope *wbd = state->current_wbd_table; + + while (f_MHz > wbd->max_freq) + wbd++; + + dprintk("using wbd-table-entry with max freq %d", wbd->max_freq); + + if (current_temp < 0) + current_temp = 0; + if (current_temp > 128) + current_temp = 128; + + state->wbdmux &= ~(7 << 13); + if (wbd->wbd_gain != 0) + state->wbdmux |= (wbd->wbd_gain << 13); + else + state->wbdmux |= (4 << 13); + + dib0090_write_reg(state, 0x10, state->wbdmux); + + wbd_thot = wbd->offset_hot - (((u32) wbd->slope_hot * f_MHz) >> 6); + wbd_tcold = wbd->offset_cold - (((u32) wbd->slope_cold * f_MHz) >> 6); + + wbd_tcold += ((wbd_thot - wbd_tcold) * current_temp) >> 7; + + state->wbd_target = dib0090_wbd_to_db(state, state->wbd_offset + wbd_tcold); + dprintk("wbd-target: %d dB", (u32) state->wbd_target); + dprintk("wbd offset applied is %d", wbd_tcold); + + return state->wbd_offset + wbd_tcold; } + EXPORT_SYMBOL(dib0090_get_wbd_offset); static const u16 dib0090_defaults[] = { @@ -801,7 +1271,7 @@ static const u16 dib0090_defaults[] = { 0x99a0, 0x6008, 0x0000, - 0x8acb, + 0x8bcb, 0x0000, 0x0405, 0x0000, @@ -829,8 +1299,6 @@ static const u16 dib0090_defaults[] = { 1, 0x39, 0x0000, - 1, 0x1b, - EN_IQADC | EN_BB | EN_BIAS | EN_DIGCLK | EN_PLL | EN_CRYSTAL, 2, 0x1e, 0x07FF, 0x0007, @@ -844,50 +1312,125 @@ static const u16 dib0090_defaults[] = { 0 }; -static int dib0090_reset(struct dvb_frontend *fe) -{ - struct dib0090_state *state = fe->tuner_priv; - u16 l, r, *n; +static const u16 dib0090_p1g_additionnal_defaults[] = { + 1, 0x05, + 0xabcd, - dib0090_reset_digital(fe, state->config); - state->revision = dib0090_identify(fe); + 1, 0x11, + 0x00b4, - /* Revision definition */ - if (state->revision == 0xff) - return -EINVAL; -#ifdef EFUSE - else if ((state->revision & 0x1f) >= 3) /* Update the efuse : Only available for KROSUS > P1C */ - dib0090_set_EFUSE(state); -#endif + 1, 0x1c, + 0xfffd, -#ifdef CONFIG_TUNER_DIB0090_P1B_SUPPORT - if (!(state->revision & 0x1)) /* it is P1B - reset is already done */ - return 0; -#endif + 1, 0x40, + 0x108, + 0 +}; + +static void dib0090_set_default_config(struct dib0090_state *state, const u16 * n) +{ + u16 l, r; - /* Upload the default values */ - n = (u16 *) dib0090_defaults; l = pgm_read_word(n++); while (l) { r = pgm_read_word(n++); do { - /* DEBUG_TUNER */ - /* dprintk("%d, %d, %d", l, r, pgm_read_word(n)); */ dib0090_write_reg(state, r, pgm_read_word(n++)); r++; } while (--l); l = pgm_read_word(n++); } +} + +#define CAP_VALUE_MIN (u8) 9 +#define CAP_VALUE_MAX (u8) 40 +#define HR_MIN (u8) 25 +#define HR_MAX (u8) 40 +#define POLY_MIN (u8) 0 +#define POLY_MAX (u8) 8 + +void dib0090_set_EFUSE(struct dib0090_state *state) +{ + u8 c, h, n; + u16 e2, e4; + u16 cal; + + e2 = dib0090_read_reg(state, 0x26); + e4 = dib0090_read_reg(state, 0x28); + + if ((state->identity.version == P1D_E_F) || + (state->identity.version == P1G) || (e2 == 0xffff)) { + + dib0090_write_reg(state, 0x22, 0x10); + cal = (dib0090_read_reg(state, 0x22) >> 6) & 0x3ff; + + if ((cal < 670) || (cal == 1023)) + cal = 850; + n = 165 - ((cal * 10)>>6) ; + e2 = e4 = (3<<12) | (34<<6) | (n); + } + + if (e2 != e4) + e2 &= e4; /* Remove the redundancy */ + + if (e2 != 0xffff) { + c = e2 & 0x3f; + n = (e2 >> 12) & 0xf; + h = (e2 >> 6) & 0x3f; + + if ((c >= CAP_VALUE_MAX) || (c <= CAP_VALUE_MIN)) + c = 32; + if ((h >= HR_MAX) || (h <= HR_MIN)) + h = 34; + if ((n >= POLY_MAX) || (n <= POLY_MIN)) + n = 3; + + dib0090_write_reg(state, 0x13, (h << 10)) ; + e2 = (n<<11) | ((h>>2)<<6) | (c); + dib0090_write_reg(state, 0x2, e2) ; /* Load the BB_2 */ + } +} + +static int dib0090_reset(struct dvb_frontend *fe) +{ + struct dib0090_state *state = fe->tuner_priv; + + dib0090_reset_digital(fe, state->config); + if (dib0090_identify(fe) < 0) + return -EIO; + +#ifdef CONFIG_TUNER_DIB0090_P1B_SUPPORT + if (!(state->identity.version & 0x1)) /* it is P1B - reset is already done */ + return 0; +#endif + + if (!state->identity.in_soc) { + if ((dib0090_read_reg(state, 0x1a) >> 5) & 0x2) + dib0090_write_reg(state, 0x1b, (EN_IQADC | EN_BB | EN_BIAS | EN_DIGCLK | EN_PLL | EN_CRYSTAL)); + else + dib0090_write_reg(state, 0x1b, (EN_DIGCLK | EN_PLL | EN_CRYSTAL)); + } + + dib0090_set_default_config(state, dib0090_defaults); + + if (state->identity.in_soc) + dib0090_write_reg(state, 0x18, 0x2910); /* charge pump current = 0 */ + + if (state->identity.p1g) + dib0090_set_default_config(state, dib0090_p1g_additionnal_defaults); + + /* Update the efuse : Only available for KROSUS > P1C and SOC as well*/ + if (((state->identity.version & 0x1f) >= P1D_E_F) || (state->identity.in_soc)) + dib0090_set_EFUSE(state); /* Congigure in function of the crystal */ if (state->config->io.clock_khz >= 24000) - l = 1; + dib0090_write_reg(state, 0x14, 1); else - l = 2; - dib0090_write_reg(state, 0x14, l); + dib0090_write_reg(state, 0x14, 2); dprintk("Pll lock : %d", (dib0090_read_reg(state, 0x1a) >> 11) & 0x1); - state->reset = 3; /* enable iq-offset-calibration and wbd-calibration when tuning next time */ + state->calibrate = DC_CAL | WBD_CAL | TEMP_CAL; /* enable iq-offset-calibration and wbd-calibration when tuning next time */ return 0; } @@ -927,11 +1470,11 @@ static int dib0090_get_offset(struct dib0090_state *state, enum frontend_tune_st } struct dc_calibration { - uint8_t addr; - uint8_t offset; - uint8_t pga:1; - uint16_t bb1; - uint8_t i:1; + u8 addr; + u8 offset; + u8 pga:1; + u16 bb1; + u8 i:1; }; static const struct dc_calibration dc_table[] = { @@ -944,6 +1487,17 @@ static const struct dc_calibration dc_table[] = { {0}, }; +static const struct dc_calibration dc_p1g_table[] = { + /* Step1 BB gain1= 26 with boost 1, gain 2 = 0 */ + /* addr ; trim reg offset ; pga ; CTRL_BB1 value ; i or q */ + {0x06, 5, 1, (1 << 13) | (0 << 8) | (15 << 3), 1}, + {0x07, 11, 1, (1 << 13) | (0 << 8) | (15 << 3), 0}, + /* Step 2 BB gain 1 = 26 with boost = 1 & gain 2 = 29 */ + {0x06, 0, 0, (1 << 13) | (29 << 8) | (15 << 3), 1}, + {0x06, 10, 0, (1 << 13) | (29 << 8) | (15 << 3), 0}, + {0}, +}; + static void dib0090_set_trim(struct dib0090_state *state) { u16 *val; @@ -962,41 +1516,45 @@ static void dib0090_set_trim(struct dib0090_state *state) static int dib0090_dc_offset_calibration(struct dib0090_state *state, enum frontend_tune_state *tune_state) { int ret = 0; + u16 reg; switch (*tune_state) { - case CT_TUNER_START: - /* init */ - dprintk("Internal DC calibration"); - - /* the LNA is off */ - dib0090_write_reg(state, 0x24, 0x02ed); + dprintk("Start DC offset calibration"); /* force vcm2 = 0.8V */ state->bb6 = 0; state->bb7 = 0x040d; + /* the LNA AND LO are off */ + reg = dib0090_read_reg(state, 0x24) & 0x0ffb; /* shutdown lna and lo */ + dib0090_write_reg(state, 0x24, reg); + + state->wbdmux = dib0090_read_reg(state, 0x10); + dib0090_write_reg(state, 0x10, (state->wbdmux & ~(0xff << 3)) | (0x7 << 3) | 0x3); + dib0090_write_reg(state, 0x23, dib0090_read_reg(state, 0x23) & ~(1 << 14)); + state->dc = dc_table; + if (state->identity.p1g) + state->dc = dc_p1g_table; *tune_state = CT_TUNER_STEP_0; /* fall through */ case CT_TUNER_STEP_0: + dprintk("Sart/continue DC calibration for %s path", (state->dc->i == 1) ? "I" : "Q"); dib0090_write_reg(state, 0x01, state->dc->bb1); dib0090_write_reg(state, 0x07, state->bb7 | (state->dc->i << 7)); state->step = 0; - state->min_adc_diff = 1023; - *tune_state = CT_TUNER_STEP_1; ret = 50; break; case CT_TUNER_STEP_1: dib0090_set_trim(state); - *tune_state = CT_TUNER_STEP_2; break; @@ -1007,7 +1565,13 @@ static int dib0090_dc_offset_calibration(struct dib0090_state *state, enum front break; case CT_TUNER_STEP_5: /* found an offset */ - dprintk("FE%d: IQC read=%d, current=%x", state->fe->id, (u32) state->adc_diff, state->step); + dprintk("adc_diff = %d, current step= %d", (u32) state->adc_diff, state->step); + if (state->step == 0 && state->adc_diff < 0) { + state->min_adc_diff = -1023; + dprintk("Change of sign of the minimum adc diff"); + } + + dprintk("adc_diff = %d, min_adc_diff = %d current_step = %d", state->adc_diff, state->min_adc_diff, state->step); /* first turn for this frequency */ if (state->step == 0) { @@ -1017,20 +1581,21 @@ static int dib0090_dc_offset_calibration(struct dib0090_state *state, enum front state->step = 0x10; } - state->adc_diff = ABS(state->adc_diff); - - if (state->adc_diff < state->min_adc_diff && steps(state->step) < 15) { /* stop search when the delta to 0 is increasing */ + /* Look for a change of Sign in the Adc_diff.min_adc_diff is used to STORE the setp N-1 */ + if ((state->adc_diff & 0x8000) == (state->min_adc_diff & 0x8000) && steps(state->step) < 15) { + /* stop search when the delta the sign is changing and Steps =15 and Step=0 is force for continuance */ state->step++; state->min_adc_diff = state->adc_diff; *tune_state = CT_TUNER_STEP_1; } else { - /* the minimum was what we have seen in the step before */ - state->step--; - dib0090_set_trim(state); + if (ABS(state->adc_diff) > ABS(state->min_adc_diff)) { + dprintk("Since adc_diff N = %d > adc_diff step N-1 = %d, Come back one step", state->adc_diff, state->min_adc_diff); + state->step--; + } - dprintk("FE%d: BB Offset Cal, BBreg=%hd,Offset=%hd,Value Set=%hd", state->fe->id, state->dc->addr, state->adc_diff, - state->step); + dib0090_set_trim(state); + dprintk("BB Offset Cal, BBreg=%hd,Offset=%hd,Value Set=%hd", state->dc->addr, state->adc_diff, state->step); state->dc++; if (state->dc->addr == 0) /* done */ @@ -1045,7 +1610,7 @@ static int dib0090_dc_offset_calibration(struct dib0090_state *state, enum front dib0090_write_reg(state, 0x07, state->bb7 & ~0x0008); dib0090_write_reg(state, 0x1f, 0x7); *tune_state = CT_TUNER_START; /* reset done -> real tuning can now begin */ - state->reset &= ~0x1; + state->calibrate &= ~DC_CAL; default: break; } @@ -1054,21 +1619,43 @@ static int dib0090_dc_offset_calibration(struct dib0090_state *state, enum front static int dib0090_wbd_calibration(struct dib0090_state *state, enum frontend_tune_state *tune_state) { + u8 wbd_gain; + const struct dib0090_wbd_slope *wbd = state->current_wbd_table; + switch (*tune_state) { case CT_TUNER_START: - /* WBD-mode=log, Bias=2, Gain=6, Testmode=1, en=1, WBDMUX=1 */ - dib0090_write_reg(state, 0x10, 0xdb09 | (1 << 10)); - dib0090_write_reg(state, 0x24, EN_UHF & 0x0fff); + while (state->current_rf / 1000 > wbd->max_freq) + wbd++; + if (wbd->wbd_gain != 0) + wbd_gain = wbd->wbd_gain; + else { + wbd_gain = 4; +#if defined(CONFIG_BAND_LBAND) || defined(CONFIG_BAND_SBAND) + if ((state->current_band == BAND_LBAND) || (state->current_band == BAND_SBAND)) + wbd_gain = 2; +#endif + } + + if (wbd_gain == state->wbd_calibration_gain) { /* the WBD calibration has already been done */ + *tune_state = CT_TUNER_START; + state->calibrate &= ~WBD_CAL; + return 0; + } + + dib0090_write_reg(state, 0x10, 0x1b81 | (1 << 10) | (wbd_gain << 13) | (1 << 3)); + dib0090_write_reg(state, 0x24, ((EN_UHF & 0x0fff) | (1 << 1))); *tune_state = CT_TUNER_STEP_0; + state->wbd_calibration_gain = wbd_gain; return 90; /* wait for the WBDMUX to switch and for the ADC to sample */ + case CT_TUNER_STEP_0: - state->wbd_offset = dib0090_read_reg(state, 0x1d); + state->wbd_offset = dib0090_get_slow_adc_val(state); dprintk("WBD calibration offset = %d", state->wbd_offset); - *tune_state = CT_TUNER_START; /* reset done -> real tuning can now begin */ - state->reset &= ~0x2; + state->calibrate &= ~WBD_CAL; break; + default: break; } @@ -1092,6 +1679,15 @@ static void dib0090_set_bandwidth(struct dib0090_state *state) state->bb_1_def |= tmp; dib0090_write_reg(state, 0x01, state->bb_1_def); /* be sure that we have the right bb-filter */ + + dib0090_write_reg(state, 0x03, 0x6008); /* = 0x6008 : vcm3_trim = 1 ; filter2_gm1_trim = 8 ; filter2_cutoff_freq = 0 */ + dib0090_write_reg(state, 0x04, 0x1); /* 0 = 1KHz ; 1 = 50Hz ; 2 = 150Hz ; 3 = 50KHz ; 4 = servo fast */ + if (state->identity.in_soc) { + dib0090_write_reg(state, 0x05, 0x9bcf); /* attenuator_ibias_tri = 2 ; input_stage_ibias_tr = 1 ; nc = 11 ; ext_gm_trim = 1 ; obuf_ibias_trim = 4 ; filter13_gm2_ibias_t = 15 */ + } else { + dib0090_write_reg(state, 0x02, (5 << 11) | (8 << 6) | (22 & 0x3f)); /* 22 = cap_value */ + dib0090_write_reg(state, 0x05, 0xabcd); /* = 0xabcd : attenuator_ibias_tri = 2 ; input_stage_ibias_tr = 2 ; nc = 11 ; ext_gm_trim = 1 ; obuf_ibias_trim = 4 ; filter13_gm2_ibias_t = 13 */ + } } static const struct dib0090_pll dib0090_pll_table[] = { @@ -1180,6 +1776,255 @@ static const struct dib0090_tuning dib0090_tuning_table[] = { #endif }; +static const struct dib0090_tuning dib0090_p1g_tuning_table[] = { +#ifdef CONFIG_BAND_CBAND + {170000, 4, 1, 0x820f, 0x300, 0x2d22, 0x82cb, EN_CAB}, +#endif +#ifdef CONFIG_BAND_VHF + {184000, 1, 1, 15, 0x300, 0x4d12, 0xb94e, EN_VHF}, + {227000, 1, 3, 15, 0x300, 0x4d12, 0xb94e, EN_VHF}, + {380000, 1, 7, 15, 0x300, 0x4d12, 0xb94e, EN_VHF}, +#endif +#ifdef CONFIG_BAND_UHF + {510000, 2, 0, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {540000, 2, 1, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {600000, 2, 3, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {630000, 2, 4, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {680000, 2, 5, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {720000, 2, 6, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {900000, 2, 7, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, +#endif +#ifdef CONFIG_BAND_LBAND + {1500000, 4, 0, 20, 0x300, 0x1912, 0x82c9, EN_LBD}, + {1600000, 4, 1, 20, 0x300, 0x1912, 0x82c9, EN_LBD}, + {1800000, 4, 3, 20, 0x300, 0x1912, 0x82c9, EN_LBD}, +#endif +#ifdef CONFIG_BAND_SBAND + {2300000, 1, 4, 20, 0x300, 0x2d2A, 0x82c7, EN_SBD}, + {2900000, 1, 7, 20, 0x280, 0x2deb, 0x8347, EN_SBD}, +#endif +}; + +static const struct dib0090_pll dib0090_p1g_pll_table[] = { +#ifdef CONFIG_BAND_CBAND + {57000, 0, 11, 48, 6}, + {70000, 1, 11, 48, 6}, + {86000, 0, 10, 32, 4}, + {105000, 1, 10, 32, 4}, + {115000, 0, 9, 24, 6}, + {140000, 1, 9, 24, 6}, + {170000, 0, 8, 16, 4}, +#endif +#ifdef CONFIG_BAND_VHF + {200000, 1, 8, 16, 4}, + {230000, 0, 7, 12, 6}, + {280000, 1, 7, 12, 6}, + {340000, 0, 6, 8, 4}, + {380000, 1, 6, 8, 4}, + {455000, 0, 5, 6, 6}, +#endif +#ifdef CONFIG_BAND_UHF + {580000, 1, 5, 6, 6}, + {680000, 0, 4, 4, 4}, + {860000, 1, 4, 4, 4}, +#endif +#ifdef CONFIG_BAND_LBAND + {1800000, 1, 2, 2, 4}, +#endif +#ifdef CONFIG_BAND_SBAND + {2900000, 0, 1, 1, 6}, +#endif +}; + +static const struct dib0090_tuning dib0090_p1g_tuning_table_fm_vhf_on_cband[] = { +#ifdef CONFIG_BAND_CBAND + {184000, 4, 3, 0x4187, 0x2c0, 0x2d22, 0x81cb, EN_CAB}, + {227000, 4, 3, 0x4187, 0x2c0, 0x2d22, 0x81cb, EN_CAB}, + {380000, 4, 3, 0x4187, 0x2c0, 0x2d22, 0x81cb, EN_CAB}, +#endif +#ifdef CONFIG_BAND_UHF + {520000, 2, 0, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {550000, 2, 2, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {650000, 2, 3, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {750000, 2, 5, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {850000, 2, 6, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, + {900000, 2, 7, 15, 0x300, 0x1d12, 0xb9ce, EN_UHF}, +#endif +#ifdef CONFIG_BAND_LBAND + {1500000, 4, 0, 20, 0x300, 0x1912, 0x82c9, EN_LBD}, + {1600000, 4, 1, 20, 0x300, 0x1912, 0x82c9, EN_LBD}, + {1800000, 4, 3, 20, 0x300, 0x1912, 0x82c9, EN_LBD}, +#endif +#ifdef CONFIG_BAND_SBAND + {2300000, 1, 4, 20, 0x300, 0x2d2A, 0x82c7, EN_SBD}, + {2900000, 1, 7, 20, 0x280, 0x2deb, 0x8347, EN_SBD}, +#endif +}; + +static const struct dib0090_tuning dib0090_tuning_table_cband_7090[] = { +#ifdef CONFIG_BAND_CBAND + {300000, 4, 3, 0x018F, 0x2c0, 0x2d22, 0xb9ce, EN_CAB}, + {380000, 4, 10, 0x018F, 0x2c0, 0x2d22, 0xb9ce, EN_CAB}, + {570000, 4, 10, 0x8190, 0x2c0, 0x2d22, 0xb9ce, EN_CAB}, + {858000, 4, 5, 0x8190, 0x2c0, 0x2d22, 0xb9ce, EN_CAB}, +#endif +}; + +static int dib0090_captrim_search(struct dib0090_state *state, enum frontend_tune_state *tune_state) +{ + int ret = 0; + u16 lo4 = 0xe900; + + s16 adc_target; + u16 adc; + s8 step_sign; + u8 force_soft_search = 0; + + if (state->identity.version == SOC_8090_P1G_11R1 || state->identity.version == SOC_8090_P1G_21R1) + force_soft_search = 1; + + if (*tune_state == CT_TUNER_START) { + dprintk("Start Captrim search : %s", (force_soft_search == 1) ? "FORCE SOFT SEARCH" : "AUTO"); + dib0090_write_reg(state, 0x10, 0x2B1); + dib0090_write_reg(state, 0x1e, 0x0032); + + if (!state->tuner_is_tuned) { + /* prepare a complete captrim */ + if (!state->identity.p1g || force_soft_search) + state->step = state->captrim = state->fcaptrim = 64; + + state->current_rf = state->rf_request; + } else { /* we are already tuned to this frequency - the configuration is correct */ + if (!state->identity.p1g || force_soft_search) { + /* do a minimal captrim even if the frequency has not changed */ + state->step = 4; + state->captrim = state->fcaptrim = dib0090_read_reg(state, 0x18) & 0x7f; + } + } + state->adc_diff = 3000; + *tune_state = CT_TUNER_STEP_0; + + } else if (*tune_state == CT_TUNER_STEP_0) { + if (state->identity.p1g && !force_soft_search) { + u8 ratio = 31; + + dib0090_write_reg(state, 0x40, (3 << 7) | (ratio << 2) | (1 << 1) | 1); + dib0090_read_reg(state, 0x40); + ret = 50; + } else { + state->step /= 2; + dib0090_write_reg(state, 0x18, lo4 | state->captrim); + + if (state->identity.in_soc) + ret = 25; + } + *tune_state = CT_TUNER_STEP_1; + + } else if (*tune_state == CT_TUNER_STEP_1) { + if (state->identity.p1g && !force_soft_search) { + dib0090_write_reg(state, 0x40, 0x18c | (0 << 1) | 0); + dib0090_read_reg(state, 0x40); + + state->fcaptrim = dib0090_read_reg(state, 0x18) & 0x7F; + dprintk("***Final Captrim= 0x%x", state->fcaptrim); + *tune_state = CT_TUNER_STEP_3; + + } else { + /* MERGE for all krosus before P1G */ + adc = dib0090_get_slow_adc_val(state); + dprintk("CAPTRIM=%d; ADC = %d (ADC) & %dmV", (u32) state->captrim, (u32) adc, (u32) (adc) * (u32) 1800 / (u32) 1024); + + if (state->rest == 0 || state->identity.in_soc) { /* Just for 8090P SOCS where auto captrim HW bug : TO CHECK IN ACI for SOCS !!! if 400 for 8090p SOC => tune issue !!! */ + adc_target = 200; + } else + adc_target = 400; + + if (adc >= adc_target) { + adc -= adc_target; + step_sign = -1; + } else { + adc = adc_target - adc; + step_sign = 1; + } + + if (adc < state->adc_diff) { + dprintk("CAPTRIM=%d is closer to target (%d/%d)", (u32) state->captrim, (u32) adc, (u32) state->adc_diff); + state->adc_diff = adc; + state->fcaptrim = state->captrim; + } + + state->captrim += step_sign * state->step; + if (state->step >= 1) + *tune_state = CT_TUNER_STEP_0; + else + *tune_state = CT_TUNER_STEP_2; + + ret = 25; + } + } else if (*tune_state == CT_TUNER_STEP_2) { /* this step is only used by krosus < P1G */ + /*write the final cptrim config */ + dib0090_write_reg(state, 0x18, lo4 | state->fcaptrim); + + *tune_state = CT_TUNER_STEP_3; + + } else if (*tune_state == CT_TUNER_STEP_3) { + state->calibrate &= ~CAPTRIM_CAL; + *tune_state = CT_TUNER_STEP_0; + } + + return ret; +} + +static int dib0090_get_temperature(struct dib0090_state *state, enum frontend_tune_state *tune_state) +{ + int ret = 15; + s16 val; + + switch (*tune_state) { + case CT_TUNER_START: + state->wbdmux = dib0090_read_reg(state, 0x10); + dib0090_write_reg(state, 0x10, (state->wbdmux & ~(0xff << 3)) | (0x8 << 3)); + + state->bias = dib0090_read_reg(state, 0x13); + dib0090_write_reg(state, 0x13, state->bias | (0x3 << 8)); + + *tune_state = CT_TUNER_STEP_0; + /* wait for the WBDMUX to switch and for the ADC to sample */ + break; + + case CT_TUNER_STEP_0: + state->adc_diff = dib0090_get_slow_adc_val(state); + dib0090_write_reg(state, 0x13, (state->bias & ~(0x3 << 8)) | (0x2 << 8)); + *tune_state = CT_TUNER_STEP_1; + break; + + case CT_TUNER_STEP_1: + val = dib0090_get_slow_adc_val(state); + state->temperature = ((s16) ((val - state->adc_diff) * 180) >> 8) + 55; + + dprintk("temperature: %d C", state->temperature - 30); + + *tune_state = CT_TUNER_STEP_2; + break; + + case CT_TUNER_STEP_2: + dib0090_write_reg(state, 0x13, state->bias); + dib0090_write_reg(state, 0x10, state->wbdmux); /* write back original WBDMUX */ + + *tune_state = CT_TUNER_START; + state->calibrate &= ~TEMP_CAL; + if (state->config->analog_output == 0) + dib0090_write_reg(state, 0x23, dib0090_read_reg(state, 0x23) | (1 << 14)); + + break; + + default: + ret = 0; + break; + } + return ret; +} + #define WBD 0x781 /* 1 1 1 1 0000 0 0 1 */ static int dib0090_tune(struct dvb_frontend *fe) { @@ -1188,87 +2033,131 @@ static int dib0090_tune(struct dvb_frontend *fe) const struct dib0090_pll *pll = state->current_pll_table_index; enum frontend_tune_state *tune_state = &state->tune_state; - u32 rf; - u16 lo4 = 0xe900, lo5, lo6, Den; + u16 lo5, lo6, Den, tmp; u32 FBDiv, Rest, FREF, VCOF_kHz = 0; - u16 tmp, adc; - int8_t step_sign; int ret = 10; /* 1ms is the default delay most of the time */ u8 c, i; - state->current_band = (u8) BAND_OF_FREQUENCY(fe->dtv_property_cache.frequency / 1000); - rf = fe->dtv_property_cache.frequency / 1000 + (state->current_band == - BAND_UHF ? state->config->freq_offset_khz_uhf : state->config->freq_offset_khz_vhf); - /* in any case we first need to do a reset if needed */ - if (state->reset & 0x1) - return dib0090_dc_offset_calibration(state, tune_state); - else if (state->reset & 0x2) - return dib0090_wbd_calibration(state, tune_state); - - /************************* VCO ***************************/ + /************************* VCO ***************************/ /* Default values for FG */ /* from these are needed : */ /* Cp,HFdiv,VCOband,SD,Num,Den,FB and REFDiv */ -#ifdef CONFIG_SYS_ISDBT - if (state->fe->dtv_property_cache.delivery_system == SYS_ISDBT && state->fe->dtv_property_cache.isdbt_sb_mode == 1) - rf += 850; -#endif + /* in any case we first need to do a calibration if needed */ + if (*tune_state == CT_TUNER_START) { + /* deactivate DataTX before some calibrations */ + if (state->calibrate & (DC_CAL | TEMP_CAL | WBD_CAL)) + dib0090_write_reg(state, 0x23, dib0090_read_reg(state, 0x23) & ~(1 << 14)); + else + /* Activate DataTX in case a calibration has been done before */ + if (state->config->analog_output == 0) + dib0090_write_reg(state, 0x23, dib0090_read_reg(state, 0x23) | (1 << 14)); + } - if (state->current_rf != rf) { - state->tuner_is_tuned = 0; + if (state->calibrate & DC_CAL) + return dib0090_dc_offset_calibration(state, tune_state); + else if (state->calibrate & WBD_CAL) { + if (state->current_rf == 0) + state->current_rf = state->fe->dtv_property_cache.frequency / 1000; + return dib0090_wbd_calibration(state, tune_state); + } else if (state->calibrate & TEMP_CAL) + return dib0090_get_temperature(state, tune_state); + else if (state->calibrate & CAPTRIM_CAL) + return dib0090_captrim_search(state, tune_state); - tune = dib0090_tuning_table; + if (*tune_state == CT_TUNER_START) { + /* if soc and AGC pwm control, disengage mux to be able to R/W access to 0x01 register to set the right filter (cutoff_freq_select) during the tune sequence, otherwise, SOC SERPAR error when accessing to 0x01 */ + if (state->config->use_pwm_agc && state->identity.in_soc) { + tmp = dib0090_read_reg(state, 0x39); + if ((tmp >> 10) & 0x1) + dib0090_write_reg(state, 0x39, tmp & ~(1 << 10)); + } - tmp = (state->revision >> 5) & 0x7; - if (tmp == 0x4 || tmp == 0x7) { - /* CBAND tuner version for VHF */ - if (state->current_band == BAND_FM || state->current_band == BAND_VHF) { - /* Force CBAND */ - state->current_band = BAND_CBAND; - tune = dib0090_tuning_table_fm_vhf_on_cband; + state->current_band = (u8) BAND_OF_FREQUENCY(state->fe->dtv_property_cache.frequency / 1000); + state->rf_request = + state->fe->dtv_property_cache.frequency / 1000 + (state->current_band == + BAND_UHF ? state->config->freq_offset_khz_uhf : state->config-> + freq_offset_khz_vhf); + + /* in ISDB-T 1seg we shift tuning frequency */ + if ((state->fe->dtv_property_cache.delivery_system == SYS_ISDBT && state->fe->dtv_property_cache.isdbt_sb_mode == 1 + && state->fe->dtv_property_cache.isdbt_partial_reception == 0)) { + const struct dib0090_low_if_offset_table *LUT_offset = state->config->low_if; + u8 found_offset = 0; + u32 margin_khz = 100; + + if (LUT_offset != NULL) { + while (LUT_offset->RF_freq != 0xffff) { + if (((state->rf_request > (LUT_offset->RF_freq - margin_khz)) + && (state->rf_request < (LUT_offset->RF_freq + margin_khz))) + && LUT_offset->std == state->fe->dtv_property_cache.delivery_system) { + state->rf_request += LUT_offset->offset_khz; + found_offset = 1; + break; + } + LUT_offset++; + } } + + if (found_offset == 0) + state->rf_request += 400; } + if (state->current_rf != state->rf_request || (state->current_standard != state->fe->dtv_property_cache.delivery_system)) { + state->tuner_is_tuned = 0; + state->current_rf = 0; + state->current_standard = 0; - pll = dib0090_pll_table; - /* Look for the interval */ - while (rf > tune->max_freq) - tune++; - while (rf > pll->max_freq) - pll++; - state->current_tune_table_index = tune; - state->current_pll_table_index = pll; - } + tune = dib0090_tuning_table; + if (state->identity.p1g) + tune = dib0090_p1g_tuning_table; - if (*tune_state == CT_TUNER_START) { + tmp = (state->identity.version >> 5) & 0x7; - if (state->tuner_is_tuned == 0) - state->current_rf = 0; + if (state->identity.in_soc) { + if (state->config->force_cband_input) { /* Use the CBAND input for all band */ + if (state->current_band & BAND_CBAND || state->current_band & BAND_FM || state->current_band & BAND_VHF + || state->current_band & BAND_UHF) { + state->current_band = BAND_CBAND; + tune = dib0090_tuning_table_cband_7090; + } + } else { /* Use the CBAND input for all band under UHF */ + if (state->current_band & BAND_CBAND || state->current_band & BAND_FM || state->current_band & BAND_VHF) { + state->current_band = BAND_CBAND; + tune = dib0090_tuning_table_cband_7090; + } + } + } else + if (tmp == 0x4 || tmp == 0x7) { + /* CBAND tuner version for VHF */ + if (state->current_band == BAND_FM || state->current_band == BAND_CBAND || state->current_band == BAND_VHF) { + state->current_band = BAND_CBAND; /* Force CBAND */ + + tune = dib0090_tuning_table_fm_vhf_on_cband; + if (state->identity.p1g) + tune = dib0090_p1g_tuning_table_fm_vhf_on_cband; + } + } - if (state->current_rf != rf) { + pll = dib0090_pll_table; + if (state->identity.p1g) + pll = dib0090_p1g_pll_table; - dib0090_write_reg(state, 0x0b, 0xb800 | (tune->switch_trim)); + /* Look for the interval */ + while (state->rf_request > tune->max_freq) + tune++; + while (state->rf_request > pll->max_freq) + pll++; - /* external loop filter, otherwise: - * lo5 = (0 << 15) | (0 << 12) | (0 << 11) | (3 << 9) | (4 << 6) | (3 << 4) | 4; - * lo6 = 0x0e34 */ - if (pll->vco_band) - lo5 = 0x049e; - else if (state->config->analog_output) - lo5 = 0x041d; - else - lo5 = 0x041c; - - lo5 |= (pll->hfdiv_code << 11) | (pll->vco_band << 7); /* bit 15 is the split to the slave, we do not do it here */ + state->current_tune_table_index = tune; + state->current_pll_table_index = pll; - if (!state->config->io.pll_int_loop_filt) - lo6 = 0xff28; - else - lo6 = (state->config->io.pll_int_loop_filt << 3); + dib0090_write_reg(state, 0x0b, 0xb800 | (tune->switch_trim)); - VCOF_kHz = (pll->hfdiv * rf) * 2; + VCOF_kHz = (pll->hfdiv * state->rf_request) * 2; FREF = state->config->io.clock_khz; + if (state->config->fref_clock_ratio != 0) + FREF /= state->config->fref_clock_ratio; FBDiv = (VCOF_kHz / pll->topresc / FREF); Rest = (VCOF_kHz / pll->topresc) - FBDiv * FREF; @@ -1283,144 +2172,132 @@ static int dib0090_tune(struct dvb_frontend *fe) } else if (Rest > (FREF - 2 * LPF)) Rest = FREF - 2 * LPF; Rest = (Rest * 6528) / (FREF / 10); + state->rest = Rest; - Den = 1; + /* external loop filter, otherwise: + * lo5 = (0 << 15) | (0 << 12) | (0 << 11) | (3 << 9) | (4 << 6) | (3 << 4) | 4; + * lo6 = 0x0e34 */ + + if (Rest == 0) { + if (pll->vco_band) + lo5 = 0x049f; + else + lo5 = 0x041f; + } else { + if (pll->vco_band) + lo5 = 0x049e; + else if (state->config->analog_output) + lo5 = 0x041d; + else + lo5 = 0x041c; + } + + if (state->identity.p1g) { /* Bias is done automatically in P1G */ + if (state->identity.in_soc) { + if (state->identity.version == SOC_8090_P1G_11R1) + lo5 = 0x46f; + else + lo5 = 0x42f; + } else + lo5 = 0x42c; + } + + lo5 |= (pll->hfdiv_code << 11) | (pll->vco_band << 7); /* bit 15 is the split to the slave, we do not do it here */ - dprintk(" ***** ******* Rest value = %d", Rest); + if (!state->config->io.pll_int_loop_filt) { + if (state->identity.in_soc) + lo6 = 0xff98; + else if (state->identity.p1g || (Rest == 0)) + lo6 = 0xfff8; + else + lo6 = 0xff28; + } else + lo6 = (state->config->io.pll_int_loop_filt << 3); + + Den = 1; if (Rest > 0) { if (state->config->analog_output) lo6 |= (1 << 2) | 2; - else - lo6 |= (1 << 2) | 1; + else { + if (state->identity.in_soc) + lo6 |= (1 << 2) | 2; + else + lo6 |= (1 << 2) | 2; + } Den = 255; } -#ifdef CONFIG_BAND_SBAND - if (state->current_band == BAND_SBAND) - lo6 &= 0xfffb; -#endif - dib0090_write_reg(state, 0x15, (u16) FBDiv); - - dib0090_write_reg(state, 0x16, (Den << 8) | 1); - + if (state->config->fref_clock_ratio != 0) + dib0090_write_reg(state, 0x16, (Den << 8) | state->config->fref_clock_ratio); + else + dib0090_write_reg(state, 0x16, (Den << 8) | 1); dib0090_write_reg(state, 0x17, (u16) Rest); - dib0090_write_reg(state, 0x19, lo5); - dib0090_write_reg(state, 0x1c, lo6); lo6 = tune->tuner_enable; if (state->config->analog_output) lo6 = (lo6 & 0xff9f) | 0x2; - dib0090_write_reg(state, 0x24, lo6 | EN_LO -#ifdef CONFIG_DIB0090_USE_PWM_AGC - | state->config->use_pwm_agc * EN_CRYSTAL -#endif - ); - - state->current_rf = rf; - - /* prepare a complete captrim */ - state->step = state->captrim = state->fcaptrim = 64; - - } else { /* we are already tuned to this frequency - the configuration is correct */ + dib0090_write_reg(state, 0x24, lo6 | EN_LO | state->config->use_pwm_agc * EN_CRYSTAL); - /* do a minimal captrim even if the frequency has not changed */ - state->step = 4; - state->captrim = state->fcaptrim = dib0090_read_reg(state, 0x18) & 0x7f; } - state->adc_diff = 3000; - - dib0090_write_reg(state, 0x10, 0x2B1); - dib0090_write_reg(state, 0x1e, 0x0032); + state->current_rf = state->rf_request; + state->current_standard = state->fe->dtv_property_cache.delivery_system; ret = 20; - *tune_state = CT_TUNER_STEP_1; - } else if (*tune_state == CT_TUNER_STEP_0) { - /* nothing */ - } else if (*tune_state == CT_TUNER_STEP_1) { - state->step /= 2; - dib0090_write_reg(state, 0x18, lo4 | state->captrim); - *tune_state = CT_TUNER_STEP_2; - } else if (*tune_state == CT_TUNER_STEP_2) { + state->calibrate = CAPTRIM_CAL; /* captrim serach now */ + } - adc = dib0090_read_reg(state, 0x1d); - dprintk("FE %d CAPTRIM=%d; ADC = %d (ADC) & %dmV", (u32) fe->id, (u32) state->captrim, (u32) adc, - (u32) (adc) * (u32) 1800 / (u32) 1024); + else if (*tune_state == CT_TUNER_STEP_0) { /* Warning : because of captrim cal, if you change this step, change it also in _cal.c file because it is the step following captrim cal state machine */ + const struct dib0090_wbd_slope *wbd = state->current_wbd_table; - if (adc >= 400) { - adc -= 400; - step_sign = -1; - } else { - adc = 400 - adc; - step_sign = 1; - } + while (state->current_rf / 1000 > wbd->max_freq) + wbd++; - if (adc < state->adc_diff) { - dprintk("FE %d CAPTRIM=%d is closer to target (%d/%d)", (u32) fe->id, (u32) state->captrim, (u32) adc, (u32) state->adc_diff); - state->adc_diff = adc; - state->fcaptrim = state->captrim; - - } + dib0090_write_reg(state, 0x1e, 0x07ff); + dprintk("Final Captrim: %d", (u32) state->fcaptrim); + dprintk("HFDIV code: %d", (u32) pll->hfdiv_code); + dprintk("VCO = %d", (u32) pll->vco_band); + dprintk("VCOF in kHz: %d ((%d*%d) << 1))", (u32) ((pll->hfdiv * state->rf_request) * 2), (u32) pll->hfdiv, (u32) state->rf_request); + dprintk("REFDIV: %d, FREF: %d", (u32) 1, (u32) state->config->io.clock_khz); + dprintk("FBDIV: %d, Rest: %d", (u32) dib0090_read_reg(state, 0x15), (u32) dib0090_read_reg(state, 0x17)); + dprintk("Num: %d, Den: %d, SD: %d", (u32) dib0090_read_reg(state, 0x17), (u32) (dib0090_read_reg(state, 0x16) >> 8), + (u32) dib0090_read_reg(state, 0x1c) & 0x3); - state->captrim += step_sign * state->step; - if (state->step >= 1) - *tune_state = CT_TUNER_STEP_1; - else - *tune_state = CT_TUNER_STEP_3; +#define WBD 0x781 /* 1 1 1 1 0000 0 0 1 */ + c = 4; + i = 3; - ret = 15; - } else if (*tune_state == CT_TUNER_STEP_3) { - /*write the final cptrim config */ - dib0090_write_reg(state, 0x18, lo4 | state->fcaptrim); + if (wbd->wbd_gain != 0) + c = wbd->wbd_gain; -#ifdef CONFIG_TUNER_DIB0090_CAPTRIM_MEMORY - state->memory[state->memory_index].cap = state->fcaptrim; -#endif + state->wbdmux = (c << 13) | (i << 11) | (WBD | (state->config->use_pwm_agc << 1)); + dib0090_write_reg(state, 0x10, state->wbdmux); - *tune_state = CT_TUNER_STEP_4; - } else if (*tune_state == CT_TUNER_STEP_4) { - dib0090_write_reg(state, 0x1e, 0x07ff); - - dprintk("FE %d Final Captrim: %d", (u32) fe->id, (u32) state->fcaptrim); - dprintk("FE %d HFDIV code: %d", (u32) fe->id, (u32) pll->hfdiv_code); - dprintk("FE %d VCO = %d", (u32) fe->id, (u32) pll->vco_band); - dprintk("FE %d VCOF in kHz: %d ((%d*%d) << 1))", (u32) fe->id, (u32) ((pll->hfdiv * rf) * 2), (u32) pll->hfdiv, (u32) rf); - dprintk("FE %d REFDIV: %d, FREF: %d", (u32) fe->id, (u32) 1, (u32) state->config->io.clock_khz); - dprintk("FE %d FBDIV: %d, Rest: %d", (u32) fe->id, (u32) dib0090_read_reg(state, 0x15), (u32) dib0090_read_reg(state, 0x17)); - dprintk("FE %d Num: %d, Den: %d, SD: %d", (u32) fe->id, (u32) dib0090_read_reg(state, 0x17), - (u32) (dib0090_read_reg(state, 0x16) >> 8), (u32) dib0090_read_reg(state, 0x1c) & 0x3); + if ((tune->tuner_enable == EN_CAB) && state->identity.p1g) { + dprintk("P1G : The cable band is selected and lna_tune = %d", tune->lna_tune); + dib0090_write_reg(state, 0x09, tune->lna_bias); + dib0090_write_reg(state, 0x0b, 0xb800 | (tune->lna_tune << 6) | (tune->switch_trim)); + } else + dib0090_write_reg(state, 0x09, (tune->lna_tune << 5) | tune->lna_bias); - c = 4; - i = 3; -#if defined(CONFIG_BAND_LBAND) || defined(CONFIG_BAND_SBAND) - if ((state->current_band == BAND_LBAND) || (state->current_band == BAND_SBAND)) { - c = 2; - i = 2; - } -#endif - dib0090_write_reg(state, 0x10, (c << 13) | (i << 11) | (WBD -#ifdef CONFIG_DIB0090_USE_PWM_AGC - | (state->config->use_pwm_agc << 1) -#endif - )); - dib0090_write_reg(state, 0x09, (tune->lna_tune << 5) | (tune->lna_bias << 0)); dib0090_write_reg(state, 0x0c, tune->v2i); dib0090_write_reg(state, 0x0d, tune->mix); dib0090_write_reg(state, 0x0e, tune->load); + *tune_state = CT_TUNER_STEP_1; - *tune_state = CT_TUNER_STEP_5; - } else if (*tune_state == CT_TUNER_STEP_5) { - + } else if (*tune_state == CT_TUNER_STEP_1) { /* initialize the lt gain register */ state->rf_lt_def = 0x7c00; - dib0090_write_reg(state, 0x0f, state->rf_lt_def); dib0090_set_bandwidth(state); state->tuner_is_tuned = 1; + + state->calibrate |= WBD_CAL; + state->calibrate |= TEMP_CAL; *tune_state = CT_TUNER_STOP; } else ret = FE_CALLBACK_TIME_NEVER; @@ -1440,6 +2317,7 @@ enum frontend_tune_state dib0090_get_tune_state(struct dvb_frontend *fe) return state->tune_state; } + EXPORT_SYMBOL(dib0090_get_tune_state); int dib0090_set_tune_state(struct dvb_frontend *fe, enum frontend_tune_state tune_state) @@ -1449,6 +2327,7 @@ int dib0090_set_tune_state(struct dvb_frontend *fe, enum frontend_tune_state tun state->tune_state = tune_state; return 0; } + EXPORT_SYMBOL(dib0090_set_tune_state); static int dib0090_get_frequency(struct dvb_frontend *fe, u32 * frequency) @@ -1462,7 +2341,7 @@ static int dib0090_get_frequency(struct dvb_frontend *fe, u32 * frequency) static int dib0090_set_params(struct dvb_frontend *fe, struct dvb_frontend_parameters *p) { struct dib0090_state *state = fe->tuner_priv; - uint32_t ret; + u32 ret; state->tune_state = CT_TUNER_START; @@ -1492,6 +2371,29 @@ static const struct dvb_tuner_ops dib0090_ops = { .get_frequency = dib0090_get_frequency, }; +static const struct dvb_tuner_ops dib0090_fw_ops = { + .info = { + .name = "DiBcom DiB0090", + .frequency_min = 45000000, + .frequency_max = 860000000, + .frequency_step = 1000, + }, + .release = dib0090_release, + + .init = NULL, + .sleep = NULL, + .set_params = NULL, + .get_frequency = NULL, +}; + +static const struct dib0090_wbd_slope dib0090_wbd_table_default[] = { + {470, 0, 250, 0, 100, 4}, + {860, 51, 866, 21, 375, 4}, + {1700, 0, 800, 0, 850, 4}, + {2900, 0, 250, 0, 100, 6}, + {0xFFFF, 0, 0, 0, 0, 0}, +}; + struct dvb_frontend *dib0090_register(struct dvb_frontend *fe, struct i2c_adapter *i2c, const struct dib0090_config *config) { struct dib0090_state *st = kzalloc(sizeof(struct dib0090_state), GFP_KERNEL); @@ -1503,6 +2405,11 @@ struct dvb_frontend *dib0090_register(struct dvb_frontend *fe, struct i2c_adapte st->fe = fe; fe->tuner_priv = st; + if (config->wbd == NULL) + st->current_wbd_table = dib0090_wbd_table_default; + else + st->current_wbd_table = config->wbd; + if (dib0090_reset(fe) != 0) goto free_mem; @@ -1515,8 +2422,34 @@ struct dvb_frontend *dib0090_register(struct dvb_frontend *fe, struct i2c_adapte fe->tuner_priv = NULL; return NULL; } + EXPORT_SYMBOL(dib0090_register); +struct dvb_frontend *dib0090_fw_register(struct dvb_frontend *fe, struct i2c_adapter *i2c, const struct dib0090_config *config) +{ + struct dib0090_fw_state *st = kzalloc(sizeof(struct dib0090_fw_state), GFP_KERNEL); + if (st == NULL) + return NULL; + + st->config = config; + st->i2c = i2c; + st->fe = fe; + fe->tuner_priv = st; + + if (dib0090_fw_reset_digital(fe, st->config) != 0) + goto free_mem; + + dprintk("DiB0090 FW: successfully identified"); + memcpy(&fe->ops.tuner_ops, &dib0090_fw_ops, sizeof(struct dvb_tuner_ops)); + + return fe; +free_mem: + kfree(st); + fe->tuner_priv = NULL; + return NULL; +} +EXPORT_SYMBOL(dib0090_fw_register); + MODULE_AUTHOR("Patrick Boettcher <pboettcher@dibcom.fr>"); MODULE_AUTHOR("Olivier Grenie <olivier.grenie@dibcom.fr>"); MODULE_DESCRIPTION("Driver for the DiBcom 0090 base-band RF Tuner"); diff --git a/drivers/media/dvb/frontends/dib0090.h b/drivers/media/dvb/frontends/dib0090.h index aa7711e88776..13d85244ec16 100644 --- a/drivers/media/dvb/frontends/dib0090.h +++ b/drivers/media/dvb/frontends/dib0090.h @@ -27,6 +27,21 @@ struct dib0090_io_config { u16 pll_int_loop_filt; }; +struct dib0090_wbd_slope { + u16 max_freq; /* for every frequency less than or equal to that field: this information is correct */ + u16 slope_cold; + u16 offset_cold; + u16 slope_hot; + u16 offset_hot; + u8 wbd_gain; +}; + +struct dib0090_low_if_offset_table { + int std; + u32 RF_freq; + s32 offset_khz; +}; + struct dib0090_config { struct dib0090_io_config io; int (*reset) (struct dvb_frontend *, int); @@ -47,10 +62,20 @@ struct dib0090_config { u16 wbd_cband_offset; u8 use_pwm_agc; u8 clkoutdrive; + + u8 ls_cfg_pad_drv; + u8 data_tx_drv; + + u8 in_soc; + const struct dib0090_low_if_offset_table *low_if; + u8 fref_clock_ratio; + u16 force_cband_input; + struct dib0090_wbd_slope *wbd; }; #if defined(CONFIG_DVB_TUNER_DIB0090) || (defined(CONFIG_DVB_TUNER_DIB0090_MODULE) && defined(MODULE)) extern struct dvb_frontend *dib0090_register(struct dvb_frontend *fe, struct i2c_adapter *i2c, const struct dib0090_config *config); +extern struct dvb_frontend *dib0090_fw_register(struct dvb_frontend *fe, struct i2c_adapter *i2c, const struct dib0090_config *config); extern void dib0090_dcc_freq(struct dvb_frontend *fe, u8 fast); extern void dib0090_pwm_gain_reset(struct dvb_frontend *fe); extern u16 dib0090_get_wbd_offset(struct dvb_frontend *tuner); @@ -65,6 +90,12 @@ static inline struct dvb_frontend *dib0090_register(struct dvb_frontend *fe, str return NULL; } +static inline struct dvb_frontend *dib0090_fw_register(struct dvb_frontend *fe, struct i2c_adapter *i2c, struct dib0090_config *config) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + static inline void dib0090_dcc_freq(struct dvb_frontend *fe, u8 fast) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); diff --git a/drivers/media/dvb/frontends/dib7000p.c b/drivers/media/dvb/frontends/dib7000p.c index 6aa02cb80733..900af60b9d36 100644 --- a/drivers/media/dvb/frontends/dib7000p.c +++ b/drivers/media/dvb/frontends/dib7000p.c @@ -26,24 +26,29 @@ MODULE_PARM_DESC(buggy_sfn_workaround, "Enable work-around for buggy SFNs (defau #define dprintk(args...) do { if (debug) { printk(KERN_DEBUG "DiB7000P: "); printk(args); printk("\n"); } } while (0) +struct i2c_device { + struct i2c_adapter *i2c_adap; + u8 i2c_addr; +}; + struct dib7000p_state { struct dvb_frontend demod; - struct dib7000p_config cfg; + struct dib7000p_config cfg; u8 i2c_addr; - struct i2c_adapter *i2c_adap; + struct i2c_adapter *i2c_adap; struct dibx000_i2c_master i2c_master; u16 wbd_ref; - u8 current_band; + u8 current_band; u32 current_bandwidth; struct dibx000_agc_config *current_agc; u32 timf; - u8 div_force_off : 1; - u8 div_state : 1; + u8 div_force_off:1; + u8 div_state:1; u16 div_sync_wait; u8 agc_state; @@ -51,7 +56,13 @@ struct dib7000p_state { u16 gpio_dir; u16 gpio_val; - u8 sfn_workaround_active :1; + u8 sfn_workaround_active:1; + +#define SOC7090 0x7090 + u16 version; + + u16 tuner_enable; + struct i2c_adapter dib7090_tuner_adap; }; enum dib7000p_power_mode { @@ -60,17 +71,20 @@ enum dib7000p_power_mode { DIB7000P_POWER_INTERFACE_ONLY, }; +static int dib7090_set_output_mode(struct dvb_frontend *fe, int mode); +static int dib7090_set_diversity_in(struct dvb_frontend *fe, int onoff); + static u16 dib7000p_read_word(struct dib7000p_state *state, u16 reg) { u8 wb[2] = { reg >> 8, reg & 0xff }; u8 rb[2]; struct i2c_msg msg[2] = { - { .addr = state->i2c_addr >> 1, .flags = 0, .buf = wb, .len = 2 }, - { .addr = state->i2c_addr >> 1, .flags = I2C_M_RD, .buf = rb, .len = 2 }, + {.addr = state->i2c_addr >> 1, .flags = 0, .buf = wb, .len = 2}, + {.addr = state->i2c_addr >> 1, .flags = I2C_M_RD, .buf = rb, .len = 2}, }; if (i2c_transfer(state->i2c_adap, msg, 2) != 2) - dprintk("i2c read error on %d",reg); + dprintk("i2c read error on %d", reg); return (rb[0] << 8) | rb[1]; } @@ -86,7 +100,8 @@ static int dib7000p_write_word(struct dib7000p_state *state, u16 reg, u16 val) }; return i2c_transfer(state->i2c_adap, &msg, 1) != 1 ? -EREMOTEIO : 0; } -static void dib7000p_write_tab(struct dib7000p_state *state, u16 *buf) + +static void dib7000p_write_tab(struct dib7000p_state *state, u16 * buf) { u16 l = 0, r, *n; n = buf; @@ -104,54 +119,54 @@ static void dib7000p_write_tab(struct dib7000p_state *state, u16 *buf) static int dib7000p_set_output_mode(struct dib7000p_state *state, int mode) { - int ret = 0; + int ret = 0; u16 outreg, fifo_threshold, smo_mode; outreg = 0; fifo_threshold = 1792; smo_mode = (dib7000p_read_word(state, 235) & 0x0050) | (1 << 1); - dprintk( "setting output mode for demod %p to %d", - &state->demod, mode); + dprintk("setting output mode for demod %p to %d", &state->demod, mode); switch (mode) { - case OUTMODE_MPEG2_PAR_GATED_CLK: // STBs with parallel gated clock - outreg = (1 << 10); /* 0x0400 */ - break; - case OUTMODE_MPEG2_PAR_CONT_CLK: // STBs with parallel continues clock - outreg = (1 << 10) | (1 << 6); /* 0x0440 */ - break; - case OUTMODE_MPEG2_SERIAL: // STBs with serial input - outreg = (1 << 10) | (2 << 6) | (0 << 1); /* 0x0480 */ - break; - case OUTMODE_DIVERSITY: - if (state->cfg.hostbus_diversity) - outreg = (1 << 10) | (4 << 6); /* 0x0500 */ - else - outreg = (1 << 11); - break; - case OUTMODE_MPEG2_FIFO: // e.g. USB feeding - smo_mode |= (3 << 1); - fifo_threshold = 512; - outreg = (1 << 10) | (5 << 6); - break; - case OUTMODE_ANALOG_ADC: - outreg = (1 << 10) | (3 << 6); - break; - case OUTMODE_HIGH_Z: // disable - outreg = 0; - break; - default: - dprintk( "Unhandled output_mode passed to be set for demod %p",&state->demod); - break; + case OUTMODE_MPEG2_PAR_GATED_CLK: + outreg = (1 << 10); /* 0x0400 */ + break; + case OUTMODE_MPEG2_PAR_CONT_CLK: + outreg = (1 << 10) | (1 << 6); /* 0x0440 */ + break; + case OUTMODE_MPEG2_SERIAL: + outreg = (1 << 10) | (2 << 6) | (0 << 1); /* 0x0480 */ + break; + case OUTMODE_DIVERSITY: + if (state->cfg.hostbus_diversity) + outreg = (1 << 10) | (4 << 6); /* 0x0500 */ + else + outreg = (1 << 11); + break; + case OUTMODE_MPEG2_FIFO: + smo_mode |= (3 << 1); + fifo_threshold = 512; + outreg = (1 << 10) | (5 << 6); + break; + case OUTMODE_ANALOG_ADC: + outreg = (1 << 10) | (3 << 6); + break; + case OUTMODE_HIGH_Z: + outreg = 0; + break; + default: + dprintk("Unhandled output_mode passed to be set for demod %p", &state->demod); + break; } if (state->cfg.output_mpeg2_in_188_bytes) - smo_mode |= (1 << 5) ; + smo_mode |= (1 << 5); - ret |= dib7000p_write_word(state, 235, smo_mode); - ret |= dib7000p_write_word(state, 236, fifo_threshold); /* synchronous fread */ - ret |= dib7000p_write_word(state, 1286, outreg); /* P_Div_active */ + ret |= dib7000p_write_word(state, 235, smo_mode); + ret |= dib7000p_write_word(state, 236, fifo_threshold); /* synchronous fread */ + if (state->version != SOC7090) + ret |= dib7000p_write_word(state, 1286, outreg); /* P_Div_active */ return ret; } @@ -161,13 +176,13 @@ static int dib7000p_set_diversity_in(struct dvb_frontend *demod, int onoff) struct dib7000p_state *state = demod->demodulator_priv; if (state->div_force_off) { - dprintk( "diversity combination deactivated - forced by COFDM parameters"); + dprintk("diversity combination deactivated - forced by COFDM parameters"); onoff = 0; dib7000p_write_word(state, 207, 0); } else dib7000p_write_word(state, 207, (state->div_sync_wait << 4) | (1 << 2) | (2 << 0)); - state->div_state = (u8)onoff; + state->div_state = (u8) onoff; if (onoff) { dib7000p_write_word(state, 204, 6); @@ -184,37 +199,48 @@ static int dib7000p_set_diversity_in(struct dvb_frontend *demod, int onoff) static int dib7000p_set_power_mode(struct dib7000p_state *state, enum dib7000p_power_mode mode) { /* by default everything is powered off */ - u16 reg_774 = 0xffff, reg_775 = 0xffff, reg_776 = 0x0007, reg_899 = 0x0003, - reg_1280 = (0xfe00) | (dib7000p_read_word(state, 1280) & 0x01ff); + u16 reg_774 = 0x3fff, reg_775 = 0xffff, reg_776 = 0x0007, reg_899 = 0x0003, reg_1280 = (0xfe00) | (dib7000p_read_word(state, 1280) & 0x01ff); /* now, depending on the requested mode, we power on */ switch (mode) { /* power up everything in the demod */ - case DIB7000P_POWER_ALL: - reg_774 = 0x0000; reg_775 = 0x0000; reg_776 = 0x0; reg_899 = 0x0; reg_1280 &= 0x01ff; - break; - - case DIB7000P_POWER_ANALOG_ADC: - /* dem, cfg, iqc, sad, agc */ - reg_774 &= ~((1 << 15) | (1 << 14) | (1 << 11) | (1 << 10) | (1 << 9)); - /* nud */ - reg_776 &= ~((1 << 0)); - /* Dout */ + case DIB7000P_POWER_ALL: + reg_774 = 0x0000; + reg_775 = 0x0000; + reg_776 = 0x0; + reg_899 = 0x0; + if (state->version == SOC7090) + reg_1280 &= 0x001f; + else + reg_1280 &= 0x01ff; + break; + + case DIB7000P_POWER_ANALOG_ADC: + /* dem, cfg, iqc, sad, agc */ + reg_774 &= ~((1 << 15) | (1 << 14) | (1 << 11) | (1 << 10) | (1 << 9)); + /* nud */ + reg_776 &= ~((1 << 0)); + /* Dout */ + if (state->version != SOC7090) reg_1280 &= ~((1 << 11)); - /* fall through wanted to enable the interfaces */ + reg_1280 &= ~(1 << 6); + /* fall through wanted to enable the interfaces */ /* just leave power on the control-interfaces: GPIO and (I2C or SDIO) */ - case DIB7000P_POWER_INTERFACE_ONLY: /* TODO power up either SDIO or I2C */ + case DIB7000P_POWER_INTERFACE_ONLY: /* TODO power up either SDIO or I2C */ + if (state->version == SOC7090) + reg_1280 &= ~((1 << 7) | (1 << 5)); + else reg_1280 &= ~((1 << 14) | (1 << 13) | (1 << 12) | (1 << 10)); - break; + break; /* TODO following stuff is just converted from the dib7000-driver - check when is used what */ } - dib7000p_write_word(state, 774, reg_774); - dib7000p_write_word(state, 775, reg_775); - dib7000p_write_word(state, 776, reg_776); - dib7000p_write_word(state, 899, reg_899); + dib7000p_write_word(state, 774, reg_774); + dib7000p_write_word(state, 775, reg_775); + dib7000p_write_word(state, 776, reg_776); + dib7000p_write_word(state, 899, reg_899); dib7000p_write_word(state, 1280, reg_1280); return 0; @@ -222,40 +248,57 @@ static int dib7000p_set_power_mode(struct dib7000p_state *state, enum dib7000p_p static void dib7000p_set_adc_state(struct dib7000p_state *state, enum dibx000_adc_states no) { - u16 reg_908 = dib7000p_read_word(state, 908), - reg_909 = dib7000p_read_word(state, 909); + u16 reg_908 = dib7000p_read_word(state, 908), reg_909 = dib7000p_read_word(state, 909); + u16 reg; switch (no) { - case DIBX000_SLOW_ADC_ON: + case DIBX000_SLOW_ADC_ON: + if (state->version == SOC7090) { + reg = dib7000p_read_word(state, 1925); + + dib7000p_write_word(state, 1925, reg | (1 << 4) | (1 << 2)); /* en_slowAdc = 1 & reset_sladc = 1 */ + + reg = dib7000p_read_word(state, 1925); /* read acces to make it works... strange ... */ + msleep(200); + dib7000p_write_word(state, 1925, reg & ~(1 << 4)); /* en_slowAdc = 1 & reset_sladc = 0 */ + + reg = dib7000p_read_word(state, 72) & ~((0x3 << 14) | (0x3 << 12)); + dib7000p_write_word(state, 72, reg | (1 << 14) | (3 << 12) | 524); /* ref = Vin1 => Vbg ; sel = Vin0 or Vin3 ; (Vin2 = Vcm) */ + } else { reg_909 |= (1 << 1) | (1 << 0); dib7000p_write_word(state, 909, reg_909); reg_909 &= ~(1 << 1); - break; + } + break; - case DIBX000_SLOW_ADC_OFF: - reg_909 |= (1 << 1) | (1 << 0); - break; + case DIBX000_SLOW_ADC_OFF: + if (state->version == SOC7090) { + reg = dib7000p_read_word(state, 1925); + dib7000p_write_word(state, 1925, (reg & ~(1 << 2)) | (1 << 4)); /* reset_sladc = 1 en_slowAdc = 0 */ + } else + reg_909 |= (1 << 1) | (1 << 0); + break; - case DIBX000_ADC_ON: - reg_908 &= 0x0fff; - reg_909 &= 0x0003; - break; + case DIBX000_ADC_ON: + reg_908 &= 0x0fff; + reg_909 &= 0x0003; + break; - case DIBX000_ADC_OFF: // leave the VBG voltage on - reg_908 |= (1 << 14) | (1 << 13) | (1 << 12); - reg_909 |= (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2); - break; + case DIBX000_ADC_OFF: + reg_908 |= (1 << 14) | (1 << 13) | (1 << 12); + reg_909 |= (1 << 5) | (1 << 4) | (1 << 3) | (1 << 2); + break; - case DIBX000_VBG_ENABLE: - reg_908 &= ~(1 << 15); - break; + case DIBX000_VBG_ENABLE: + reg_908 &= ~(1 << 15); + break; - case DIBX000_VBG_DISABLE: - reg_908 |= (1 << 15); - break; + case DIBX000_VBG_DISABLE: + reg_908 |= (1 << 15); + break; - default: - break; + default: + break; } // dprintk( "908: %x, 909: %x\n", reg_908, reg_909); @@ -275,17 +318,17 @@ static int dib7000p_set_bandwidth(struct dib7000p_state *state, u32 bw) state->current_bandwidth = bw; if (state->timf == 0) { - dprintk( "using default timf"); + dprintk("using default timf"); timf = state->cfg.bw->timf; } else { - dprintk( "using updated timf"); + dprintk("using updated timf"); timf = state->timf; } timf = timf * (bw / 50) / 160; dib7000p_write_word(state, 23, (u16) ((timf >> 16) & 0xffff)); - dib7000p_write_word(state, 24, (u16) ((timf ) & 0xffff)); + dib7000p_write_word(state, 24, (u16) ((timf) & 0xffff)); return 0; } @@ -293,9 +336,12 @@ static int dib7000p_set_bandwidth(struct dib7000p_state *state, u32 bw) static int dib7000p_sad_calib(struct dib7000p_state *state) { /* internal */ -// dib7000p_write_word(state, 72, (3 << 14) | (1 << 12) | (524 << 0)); // sampling clock of the SAD is writting in set_bandwidth dib7000p_write_word(state, 73, (0 << 1) | (0 << 0)); - dib7000p_write_word(state, 74, 776); // 0.625*3.3 / 4096 + + if (state->version == SOC7090) + dib7000p_write_word(state, 74, 2048); + else + dib7000p_write_word(state, 74, 776); /* do the calibration */ dib7000p_write_word(state, 73, (1 << 0)); @@ -314,37 +360,91 @@ int dib7000p_set_wbd_ref(struct dvb_frontend *demod, u16 value) state->wbd_ref = value; return dib7000p_write_word(state, 105, (dib7000p_read_word(state, 105) & 0xf000) | value); } - EXPORT_SYMBOL(dib7000p_set_wbd_ref); + static void dib7000p_reset_pll(struct dib7000p_state *state) { struct dibx000_bandwidth_config *bw = &state->cfg.bw[0]; u16 clk_cfg0; - /* force PLL bypass */ - clk_cfg0 = (1 << 15) | ((bw->pll_ratio & 0x3f) << 9) | - (bw->modulo << 7) | (bw->ADClkSrc << 6) | (bw->IO_CLK_en_core << 5) | - (bw->bypclk_div << 2) | (bw->enable_refdiv << 1) | (0 << 0); + if (state->version == SOC7090) { + dib7000p_write_word(state, 1856, (!bw->pll_reset << 13) | (bw->pll_range << 12) | (bw->pll_ratio << 6) | (bw->pll_prediv)); + + while (((dib7000p_read_word(state, 1856) >> 15) & 0x1) != 1) + ; - dib7000p_write_word(state, 900, clk_cfg0); + dib7000p_write_word(state, 1857, dib7000p_read_word(state, 1857) | (!bw->pll_bypass << 15)); + } else { + /* force PLL bypass */ + clk_cfg0 = (1 << 15) | ((bw->pll_ratio & 0x3f) << 9) | + (bw->modulo << 7) | (bw->ADClkSrc << 6) | (bw->IO_CLK_en_core << 5) | (bw->bypclk_div << 2) | (bw->enable_refdiv << 1) | (0 << 0); + + dib7000p_write_word(state, 900, clk_cfg0); - /* P_pll_cfg */ - dib7000p_write_word(state, 903, (bw->pll_prediv << 5) | (((bw->pll_ratio >> 6) & 0x3) << 3) | (bw->pll_range << 1) | bw->pll_reset); - clk_cfg0 = (bw->pll_bypass << 15) | (clk_cfg0 & 0x7fff); - dib7000p_write_word(state, 900, clk_cfg0); + /* P_pll_cfg */ + dib7000p_write_word(state, 903, (bw->pll_prediv << 5) | (((bw->pll_ratio >> 6) & 0x3) << 3) | (bw->pll_range << 1) | bw->pll_reset); + clk_cfg0 = (bw->pll_bypass << 15) | (clk_cfg0 & 0x7fff); + dib7000p_write_word(state, 900, clk_cfg0); + } - dib7000p_write_word(state, 18, (u16) (((bw->internal*1000) >> 16) & 0xffff)); - dib7000p_write_word(state, 19, (u16) ( (bw->internal*1000 ) & 0xffff)); - dib7000p_write_word(state, 21, (u16) ( (bw->ifreq >> 16) & 0xffff)); - dib7000p_write_word(state, 22, (u16) ( (bw->ifreq ) & 0xffff)); + dib7000p_write_word(state, 18, (u16) (((bw->internal * 1000) >> 16) & 0xffff)); + dib7000p_write_word(state, 19, (u16) ((bw->internal * 1000) & 0xffff)); + dib7000p_write_word(state, 21, (u16) ((bw->ifreq >> 16) & 0xffff)); + dib7000p_write_word(state, 22, (u16) ((bw->ifreq) & 0xffff)); dib7000p_write_word(state, 72, bw->sad_cfg); } +static u32 dib7000p_get_internal_freq(struct dib7000p_state *state) +{ + u32 internal = (u32) dib7000p_read_word(state, 18) << 16; + internal |= (u32) dib7000p_read_word(state, 19); + internal /= 1000; + + return internal; +} + +int dib7000p_update_pll(struct dvb_frontend *fe, struct dibx000_bandwidth_config *bw) +{ + struct dib7000p_state *state = fe->demodulator_priv; + u16 reg_1857, reg_1856 = dib7000p_read_word(state, 1856); + u8 loopdiv, prediv; + u32 internal, xtal; + + /* get back old values */ + prediv = reg_1856 & 0x3f; + loopdiv = (reg_1856 >> 6) & 0x3f; + + if ((bw != NULL) && (bw->pll_prediv != prediv || bw->pll_ratio != loopdiv)) { + dprintk("Updating pll (prediv: old = %d new = %d ; loopdiv : old = %d new = %d)", prediv, bw->pll_prediv, loopdiv, bw->pll_ratio); + reg_1856 &= 0xf000; + reg_1857 = dib7000p_read_word(state, 1857); + dib7000p_write_word(state, 1857, reg_1857 & ~(1 << 15)); + + dib7000p_write_word(state, 1856, reg_1856 | ((bw->pll_ratio & 0x3f) << 6) | (bw->pll_prediv & 0x3f)); + + /* write new system clk into P_sec_len */ + internal = dib7000p_get_internal_freq(state); + xtal = (internal / loopdiv) * prediv; + internal = 1000 * (xtal / bw->pll_prediv) * bw->pll_ratio; /* new internal */ + dib7000p_write_word(state, 18, (u16) ((internal >> 16) & 0xffff)); + dib7000p_write_word(state, 19, (u16) (internal & 0xffff)); + + dib7000p_write_word(state, 1857, reg_1857 | (1 << 15)); + + while (((dib7000p_read_word(state, 1856) >> 15) & 0x1) != 1) + dprintk("Waiting for PLL to lock"); + + return 0; + } + return -EIO; +} +EXPORT_SYMBOL(dib7000p_update_pll); + static int dib7000p_reset_gpio(struct dib7000p_state *st) { /* reset the GPIOs */ - dprintk( "gpio dir: %x: val: %x, pwm_pos: %x",st->gpio_dir, st->gpio_val,st->cfg.gpio_pwm_pos); + dprintk("gpio dir: %x: val: %x, pwm_pos: %x", st->gpio_dir, st->gpio_val, st->cfg.gpio_pwm_pos); dib7000p_write_word(st, 1029, st->gpio_dir); dib7000p_write_word(st, 1030, st->gpio_val); @@ -360,13 +460,13 @@ static int dib7000p_reset_gpio(struct dib7000p_state *st) static int dib7000p_cfg_gpio(struct dib7000p_state *st, u8 num, u8 dir, u8 val) { st->gpio_dir = dib7000p_read_word(st, 1029); - st->gpio_dir &= ~(1 << num); /* reset the direction bit */ - st->gpio_dir |= (dir & 0x1) << num; /* set the new direction */ + st->gpio_dir &= ~(1 << num); /* reset the direction bit */ + st->gpio_dir |= (dir & 0x1) << num; /* set the new direction */ dib7000p_write_word(st, 1029, st->gpio_dir); st->gpio_val = dib7000p_read_word(st, 1030); - st->gpio_val &= ~(1 << num); /* reset the direction bit */ - st->gpio_val |= (val & 0x01) << num; /* set the new value */ + st->gpio_val &= ~(1 << num); /* reset the direction bit */ + st->gpio_val |= (val & 0x01) << num; /* set the new value */ dib7000p_write_word(st, 1030, st->gpio_val); return 0; @@ -377,96 +477,94 @@ int dib7000p_set_gpio(struct dvb_frontend *demod, u8 num, u8 dir, u8 val) struct dib7000p_state *state = demod->demodulator_priv; return dib7000p_cfg_gpio(state, num, dir, val); } - EXPORT_SYMBOL(dib7000p_set_gpio); -static u16 dib7000p_defaults[] = -{ +static u16 dib7000p_defaults[] = { // auto search configuration 3, 2, - 0x0004, - 0x1000, - 0x0814, /* Equal Lock */ + 0x0004, + 0x1000, + 0x0814, /* Equal Lock */ 12, 6, - 0x001b, - 0x7740, - 0x005b, - 0x8d80, - 0x01c9, - 0xc380, - 0x0000, - 0x0080, - 0x0000, - 0x0090, - 0x0001, - 0xd4c0, + 0x001b, + 0x7740, + 0x005b, + 0x8d80, + 0x01c9, + 0xc380, + 0x0000, + 0x0080, + 0x0000, + 0x0090, + 0x0001, + 0xd4c0, 1, 26, - 0x6680, // P_timf_alpha=6, P_corm_alpha=6, P_corm_thres=128 default: 6,4,26 + 0x6680, /* set ADC level to -16 */ 11, 79, - (1 << 13) - 825 - 117, - (1 << 13) - 837 - 117, - (1 << 13) - 811 - 117, - (1 << 13) - 766 - 117, - (1 << 13) - 737 - 117, - (1 << 13) - 693 - 117, - (1 << 13) - 648 - 117, - (1 << 13) - 619 - 117, - (1 << 13) - 575 - 117, - (1 << 13) - 531 - 117, - (1 << 13) - 501 - 117, + (1 << 13) - 825 - 117, + (1 << 13) - 837 - 117, + (1 << 13) - 811 - 117, + (1 << 13) - 766 - 117, + (1 << 13) - 737 - 117, + (1 << 13) - 693 - 117, + (1 << 13) - 648 - 117, + (1 << 13) - 619 - 117, + (1 << 13) - 575 - 117, + (1 << 13) - 531 - 117, + (1 << 13) - 501 - 117, 1, 142, - 0x0410, // P_palf_filter_on=1, P_palf_filter_freeze=0, P_palf_alpha_regul=16 + 0x0410, /* disable power smoothing */ 8, 145, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, 1, 154, - 1 << 13, // P_fft_freq_dir=1, P_fft_nb_to_cut=0 + 1 << 13, 1, 168, - 0x0ccd, // P_pha3_thres, default 0x3000 - -// 1, 169, -// 0x0010, // P_cti_use_cpe=0, P_cti_use_prog=0, P_cti_win_len=16, default: 0x0010 + 0x0ccd, 1, 183, - 0x200f, // P_cspu_regul=512, P_cspu_win_cut=15, default: 0x2005 + 0x200f, + + 1, 212, + 0x169, 5, 187, - 0x023d, // P_adp_regul_cnt=573, default: 410 - 0x00a4, // P_adp_noise_cnt= - 0x00a4, // P_adp_regul_ext - 0x7ff0, // P_adp_noise_ext - 0x3ccc, // P_adp_fil + 0x023d, + 0x00a4, + 0x00a4, + 0x7ff0, + 0x3ccc, 1, 198, - 0x800, // P_equal_thres_wgn + 0x800, 1, 222, - 0x0010, // P_fec_ber_rs_len=2 + 0x0010, 1, 235, - 0x0062, // P_smo_mode, P_smo_rs_discard, P_smo_fifo_flush, P_smo_pid_parse, P_smo_error_discard + 0x0062, 2, 901, - 0x0006, // P_clk_cfg1 - (3 << 10) | (1 << 6), // P_divclksel=3 P_divbitsel=1 + 0x0006, + (3 << 10) | (1 << 6), 1, 905, - 0x2c8e, // Tuner IO bank: max drive (14mA) + divout pads max drive + 0x2c8e, 0, }; @@ -475,51 +573,64 @@ static int dib7000p_demod_reset(struct dib7000p_state *state) { dib7000p_set_power_mode(state, DIB7000P_POWER_ALL); + if (state->version == SOC7090) + dibx000_reset_i2c_master(&state->i2c_master); + dib7000p_set_adc_state(state, DIBX000_VBG_ENABLE); /* restart all parts */ - dib7000p_write_word(state, 770, 0xffff); - dib7000p_write_word(state, 771, 0xffff); - dib7000p_write_word(state, 772, 0x001f); - dib7000p_write_word(state, 898, 0x0003); - /* except i2c, sdio, gpio - control interfaces */ - dib7000p_write_word(state, 1280, 0x01fc - ((1 << 7) | (1 << 6) | (1 << 5)) ); - - dib7000p_write_word(state, 770, 0); - dib7000p_write_word(state, 771, 0); - dib7000p_write_word(state, 772, 0); - dib7000p_write_word(state, 898, 0); + dib7000p_write_word(state, 770, 0xffff); + dib7000p_write_word(state, 771, 0xffff); + dib7000p_write_word(state, 772, 0x001f); + dib7000p_write_word(state, 898, 0x0003); + dib7000p_write_word(state, 1280, 0x001f - ((1 << 4) | (1 << 3))); + + dib7000p_write_word(state, 770, 0); + dib7000p_write_word(state, 771, 0); + dib7000p_write_word(state, 772, 0); + dib7000p_write_word(state, 898, 0); dib7000p_write_word(state, 1280, 0); /* default */ dib7000p_reset_pll(state); if (dib7000p_reset_gpio(state) != 0) - dprintk( "GPIO reset was not successful."); - - if (dib7000p_set_output_mode(state, OUTMODE_HIGH_Z) != 0) - dprintk( "OUTPUT_MODE could not be reset."); + dprintk("GPIO reset was not successful."); - /* unforce divstr regardless whether i2c enumeration was done or not */ - dib7000p_write_word(state, 1285, dib7000p_read_word(state, 1285) & ~(1 << 1) ); + if (state->version == SOC7090) { + dib7000p_write_word(state, 899, 0); - dib7000p_set_bandwidth(state, 8000); + /* impulse noise */ + dib7000p_write_word(state, 42, (1<<5) | 3); /* P_iqc_thsat_ipc = 1 ; P_iqc_win2 = 3 */ + dib7000p_write_word(state, 43, 0x2d4); /*-300 fag P_iqc_dect_min = -280 */ + dib7000p_write_word(state, 44, 300); /* 300 fag P_iqc_dect_min = +280 */ + dib7000p_write_word(state, 273, (1<<6) | 30); + } + if (dib7000p_set_output_mode(state, OUTMODE_HIGH_Z) != 0) + dprintk("OUTPUT_MODE could not be reset."); dib7000p_set_adc_state(state, DIBX000_SLOW_ADC_ON); dib7000p_sad_calib(state); dib7000p_set_adc_state(state, DIBX000_SLOW_ADC_OFF); - // P_iqc_alpha_pha, P_iqc_alpha_amp_dcc_alpha, ... - if(state->cfg.tuner_is_baseband) - dib7000p_write_word(state, 36,0x0755); - else - dib7000p_write_word(state, 36,0x1f55); + /* unforce divstr regardless whether i2c enumeration was done or not */ + dib7000p_write_word(state, 1285, dib7000p_read_word(state, 1285) & ~(1 << 1)); + + dib7000p_set_bandwidth(state, 8000); + + if (state->version == SOC7090) { + dib7000p_write_word(state, 36, 0x5755);/* P_iqc_impnc_on =1 & P_iqc_corr_inh = 1 for impulsive noise */ + } else { + if (state->cfg.tuner_is_baseband) + dib7000p_write_word(state, 36, 0x0755); + else + dib7000p_write_word(state, 36, 0x1f55); + } dib7000p_write_tab(state, dib7000p_defaults); dib7000p_set_power_mode(state, DIB7000P_POWER_INTERFACE_ONLY); - return 0; } @@ -527,9 +638,9 @@ static void dib7000p_pll_clk_cfg(struct dib7000p_state *state) { u16 tmp = 0; tmp = dib7000p_read_word(state, 903); - dib7000p_write_word(state, 903, (tmp | 0x1)); //pwr-up pll + dib7000p_write_word(state, 903, (tmp | 0x1)); tmp = dib7000p_read_word(state, 900); - dib7000p_write_word(state, 900, (tmp & 0x7fff) | (1 << 6)); //use High freq clock + dib7000p_write_word(state, 900, (tmp & 0x7fff) | (1 << 6)); } static void dib7000p_restart_agc(struct dib7000p_state *state) @@ -543,11 +654,9 @@ static int dib7000p_update_lna(struct dib7000p_state *state) { u16 dyn_gain; - // when there is no LNA to program return immediatly if (state->cfg.update_lna) { - // read dyn_gain here (because it is demod-dependent and not fe) dyn_gain = dib7000p_read_word(state, 394); - if (state->cfg.update_lna(&state->demod,dyn_gain)) { // LNA has changed + if (state->cfg.update_lna(&state->demod, dyn_gain)) { dib7000p_restart_agc(state); return 1; } @@ -571,24 +680,24 @@ static int dib7000p_set_agc_config(struct dib7000p_state *state, u8 band) } if (agc == NULL) { - dprintk( "no valid AGC configuration found for band 0x%02x",band); + dprintk("no valid AGC configuration found for band 0x%02x", band); return -EINVAL; } state->current_agc = agc; /* AGC */ - dib7000p_write_word(state, 75 , agc->setup ); - dib7000p_write_word(state, 76 , agc->inv_gain ); - dib7000p_write_word(state, 77 , agc->time_stabiliz ); + dib7000p_write_word(state, 75, agc->setup); + dib7000p_write_word(state, 76, agc->inv_gain); + dib7000p_write_word(state, 77, agc->time_stabiliz); dib7000p_write_word(state, 100, (agc->alpha_level << 12) | agc->thlock); // Demod AGC loop configuration dib7000p_write_word(state, 101, (agc->alpha_mant << 5) | agc->alpha_exp); - dib7000p_write_word(state, 102, (agc->beta_mant << 6) | agc->beta_exp); + dib7000p_write_word(state, 102, (agc->beta_mant << 6) | agc->beta_exp); /* AGC continued */ - dprintk( "WBD: ref: %d, sel: %d, active: %d, alpha: %d", + dprintk("WBD: ref: %d, sel: %d, active: %d, alpha: %d", state->wbd_ref != 0 ? state->wbd_ref : agc->wbd_ref, agc->wbd_sel, !agc->perform_agc_softsplit, agc->wbd_sel); if (state->wbd_ref != 0) @@ -598,101 +707,135 @@ static int dib7000p_set_agc_config(struct dib7000p_state *state, u8 band) dib7000p_write_word(state, 106, (agc->wbd_sel << 13) | (agc->wbd_alpha << 9) | (agc->perform_agc_softsplit << 8)); - dib7000p_write_word(state, 107, agc->agc1_max); - dib7000p_write_word(state, 108, agc->agc1_min); - dib7000p_write_word(state, 109, agc->agc2_max); - dib7000p_write_word(state, 110, agc->agc2_min); - dib7000p_write_word(state, 111, (agc->agc1_pt1 << 8) | agc->agc1_pt2); - dib7000p_write_word(state, 112, agc->agc1_pt3); + dib7000p_write_word(state, 107, agc->agc1_max); + dib7000p_write_word(state, 108, agc->agc1_min); + dib7000p_write_word(state, 109, agc->agc2_max); + dib7000p_write_word(state, 110, agc->agc2_min); + dib7000p_write_word(state, 111, (agc->agc1_pt1 << 8) | agc->agc1_pt2); + dib7000p_write_word(state, 112, agc->agc1_pt3); dib7000p_write_word(state, 113, (agc->agc1_slope1 << 8) | agc->agc1_slope2); - dib7000p_write_word(state, 114, (agc->agc2_pt1 << 8) | agc->agc2_pt2); + dib7000p_write_word(state, 114, (agc->agc2_pt1 << 8) | agc->agc2_pt2); dib7000p_write_word(state, 115, (agc->agc2_slope1 << 8) | agc->agc2_slope2); return 0; } +static void dib7000p_set_dds(struct dib7000p_state *state, s32 offset_khz) +{ + u32 internal = dib7000p_get_internal_freq(state); + s32 unit_khz_dds_val = 67108864 / (internal); /* 2**26 / Fsampling is the unit 1KHz offset */ + u32 abs_offset_khz = ABS(offset_khz); + u32 dds = state->cfg.bw->ifreq & 0x1ffffff; + u8 invert = !!(state->cfg.bw->ifreq & (1 << 25)); + + dprintk("setting a frequency offset of %dkHz internal freq = %d invert = %d", offset_khz, internal, invert); + + if (offset_khz < 0) + unit_khz_dds_val *= -1; + + /* IF tuner */ + if (invert) + dds -= (abs_offset_khz * unit_khz_dds_val); /* /100 because of /100 on the unit_khz_dds_val line calc for better accuracy */ + else + dds += (abs_offset_khz * unit_khz_dds_val); + + if (abs_offset_khz <= (internal / 2)) { /* Max dds offset is the half of the demod freq */ + dib7000p_write_word(state, 21, (u16) (((dds >> 16) & 0x1ff) | (0 << 10) | (invert << 9))); + dib7000p_write_word(state, 22, (u16) (dds & 0xffff)); + } +} + static int dib7000p_agc_startup(struct dvb_frontend *demod, struct dvb_frontend_parameters *ch) { struct dib7000p_state *state = demod->demodulator_priv; int ret = -1; u8 *agc_state = &state->agc_state; u8 agc_split; + u16 reg; + u32 upd_demod_gain_period = 0x1000; switch (state->agc_state) { - case 0: - // set power-up level: interf+analog+AGC - dib7000p_set_power_mode(state, DIB7000P_POWER_ALL); + case 0: + dib7000p_set_power_mode(state, DIB7000P_POWER_ALL); + if (state->version == SOC7090) { + reg = dib7000p_read_word(state, 0x79b) & 0xff00; + dib7000p_write_word(state, 0x79a, upd_demod_gain_period & 0xFFFF); /* lsb */ + dib7000p_write_word(state, 0x79b, reg | (1 << 14) | ((upd_demod_gain_period >> 16) & 0xFF)); + + /* enable adc i & q */ + reg = dib7000p_read_word(state, 0x780); + dib7000p_write_word(state, 0x780, (reg | (0x3)) & (~(1 << 7))); + } else { dib7000p_set_adc_state(state, DIBX000_ADC_ON); dib7000p_pll_clk_cfg(state); + } - if (dib7000p_set_agc_config(state, BAND_OF_FREQUENCY(ch->frequency/1000)) != 0) - return -1; - - ret = 7; - (*agc_state)++; - break; + if (dib7000p_set_agc_config(state, BAND_OF_FREQUENCY(ch->frequency / 1000)) != 0) + return -1; - case 1: - // AGC initialization - if (state->cfg.agc_control) - state->cfg.agc_control(&state->demod, 1); - - dib7000p_write_word(state, 78, 32768); - if (!state->current_agc->perform_agc_softsplit) { - /* we are using the wbd - so slow AGC startup */ - /* force 0 split on WBD and restart AGC */ - dib7000p_write_word(state, 106, (state->current_agc->wbd_sel << 13) | (state->current_agc->wbd_alpha << 9) | (1 << 8)); - (*agc_state)++; - ret = 5; - } else { - /* default AGC startup */ - (*agc_state) = 4; - /* wait AGC rough lock time */ - ret = 7; - } + dib7000p_set_dds(state, 0); + ret = 7; + (*agc_state)++; + break; - dib7000p_restart_agc(state); - break; + case 1: + if (state->cfg.agc_control) + state->cfg.agc_control(&state->demod, 1); - case 2: /* fast split search path after 5sec */ - dib7000p_write_word(state, 75, state->current_agc->setup | (1 << 4)); /* freeze AGC loop */ - dib7000p_write_word(state, 106, (state->current_agc->wbd_sel << 13) | (2 << 9) | (0 << 8)); /* fast split search 0.25kHz */ + dib7000p_write_word(state, 78, 32768); + if (!state->current_agc->perform_agc_softsplit) { + /* we are using the wbd - so slow AGC startup */ + /* force 0 split on WBD and restart AGC */ + dib7000p_write_word(state, 106, (state->current_agc->wbd_sel << 13) | (state->current_agc->wbd_alpha << 9) | (1 << 8)); (*agc_state)++; - ret = 14; - break; + ret = 5; + } else { + /* default AGC startup */ + (*agc_state) = 4; + /* wait AGC rough lock time */ + ret = 7; + } - case 3: /* split search ended */ - agc_split = (u8)dib7000p_read_word(state, 396); /* store the split value for the next time */ - dib7000p_write_word(state, 78, dib7000p_read_word(state, 394)); /* set AGC gain start value */ + dib7000p_restart_agc(state); + break; - dib7000p_write_word(state, 75, state->current_agc->setup); /* std AGC loop */ - dib7000p_write_word(state, 106, (state->current_agc->wbd_sel << 13) | (state->current_agc->wbd_alpha << 9) | agc_split); /* standard split search */ + case 2: /* fast split search path after 5sec */ + dib7000p_write_word(state, 75, state->current_agc->setup | (1 << 4)); /* freeze AGC loop */ + dib7000p_write_word(state, 106, (state->current_agc->wbd_sel << 13) | (2 << 9) | (0 << 8)); /* fast split search 0.25kHz */ + (*agc_state)++; + ret = 14; + break; - dib7000p_restart_agc(state); + case 3: /* split search ended */ + agc_split = (u8) dib7000p_read_word(state, 396); /* store the split value for the next time */ + dib7000p_write_word(state, 78, dib7000p_read_word(state, 394)); /* set AGC gain start value */ - dprintk( "SPLIT %p: %hd", demod, agc_split); + dib7000p_write_word(state, 75, state->current_agc->setup); /* std AGC loop */ + dib7000p_write_word(state, 106, (state->current_agc->wbd_sel << 13) | (state->current_agc->wbd_alpha << 9) | agc_split); /* standard split search */ - (*agc_state)++; - ret = 5; - break; + dib7000p_restart_agc(state); - case 4: /* LNA startup */ - // wait AGC accurate lock time - ret = 7; + dprintk("SPLIT %p: %hd", demod, agc_split); - if (dib7000p_update_lna(state)) - // wait only AGC rough lock time - ret = 5; - else // nothing was done, go to the next state - (*agc_state)++; - break; + (*agc_state)++; + ret = 5; + break; - case 5: - if (state->cfg.agc_control) - state->cfg.agc_control(&state->demod, 0); + case 4: /* LNA startup */ + ret = 7; + + if (dib7000p_update_lna(state)) + ret = 5; + else (*agc_state)++; - break; - default: - break; + break; + + case 5: + if (state->cfg.agc_control) + state->cfg.agc_control(&state->demod, 0); + (*agc_state)++; + break; + default: + break; } return ret; } @@ -703,45 +846,89 @@ static void dib7000p_update_timf(struct dib7000p_state *state) state->timf = timf * 160 / (state->current_bandwidth / 50); dib7000p_write_word(state, 23, (u16) (timf >> 16)); dib7000p_write_word(state, 24, (u16) (timf & 0xffff)); - dprintk( "updated timf_frequency: %d (default: %d)",state->timf, state->cfg.bw->timf); + dprintk("updated timf_frequency: %d (default: %d)", state->timf, state->cfg.bw->timf); + +} +u32 dib7000p_ctrl_timf(struct dvb_frontend *fe, u8 op, u32 timf) +{ + struct dib7000p_state *state = fe->demodulator_priv; + switch (op) { + case DEMOD_TIMF_SET: + state->timf = timf; + break; + case DEMOD_TIMF_UPDATE: + dib7000p_update_timf(state); + break; + case DEMOD_TIMF_GET: + break; + } + dib7000p_set_bandwidth(state, state->current_bandwidth); + return state->timf; } +EXPORT_SYMBOL(dib7000p_ctrl_timf); static void dib7000p_set_channel(struct dib7000p_state *state, struct dvb_frontend_parameters *ch, u8 seq) { u16 value, est[4]; - dib7000p_set_bandwidth(state, BANDWIDTH_TO_KHZ(ch->u.ofdm.bandwidth)); + dib7000p_set_bandwidth(state, BANDWIDTH_TO_KHZ(ch->u.ofdm.bandwidth)); /* nfft, guard, qam, alpha */ value = 0; switch (ch->u.ofdm.transmission_mode) { - case TRANSMISSION_MODE_2K: value |= (0 << 7); break; - case TRANSMISSION_MODE_4K: value |= (2 << 7); break; - default: - case TRANSMISSION_MODE_8K: value |= (1 << 7); break; + case TRANSMISSION_MODE_2K: + value |= (0 << 7); + break; + case TRANSMISSION_MODE_4K: + value |= (2 << 7); + break; + default: + case TRANSMISSION_MODE_8K: + value |= (1 << 7); + break; } switch (ch->u.ofdm.guard_interval) { - case GUARD_INTERVAL_1_32: value |= (0 << 5); break; - case GUARD_INTERVAL_1_16: value |= (1 << 5); break; - case GUARD_INTERVAL_1_4: value |= (3 << 5); break; - default: - case GUARD_INTERVAL_1_8: value |= (2 << 5); break; + case GUARD_INTERVAL_1_32: + value |= (0 << 5); + break; + case GUARD_INTERVAL_1_16: + value |= (1 << 5); + break; + case GUARD_INTERVAL_1_4: + value |= (3 << 5); + break; + default: + case GUARD_INTERVAL_1_8: + value |= (2 << 5); + break; } switch (ch->u.ofdm.constellation) { - case QPSK: value |= (0 << 3); break; - case QAM_16: value |= (1 << 3); break; - default: - case QAM_64: value |= (2 << 3); break; + case QPSK: + value |= (0 << 3); + break; + case QAM_16: + value |= (1 << 3); + break; + default: + case QAM_64: + value |= (2 << 3); + break; } switch (HIERARCHY_1) { - case HIERARCHY_2: value |= 2; break; - case HIERARCHY_4: value |= 4; break; - default: - case HIERARCHY_1: value |= 1; break; + case HIERARCHY_2: + value |= 2; + break; + case HIERARCHY_4: + value |= 4; + break; + default: + case HIERARCHY_1: + value |= 1; + break; } dib7000p_write_word(state, 0, value); - dib7000p_write_word(state, 5, (seq << 4) | 1); /* do not force tps, search list 0 */ + dib7000p_write_word(state, 5, (seq << 4) | 1); /* do not force tps, search list 0 */ /* P_dintl_native, P_dintlv_inv, P_hrch, P_code_rate, P_select_hp */ value = 0; @@ -752,39 +939,63 @@ static void dib7000p_set_channel(struct dib7000p_state *state, struct dvb_fronte if (1 == 1) value |= 1; switch ((ch->u.ofdm.hierarchy_information == 0 || 1 == 1) ? ch->u.ofdm.code_rate_HP : ch->u.ofdm.code_rate_LP) { - case FEC_2_3: value |= (2 << 1); break; - case FEC_3_4: value |= (3 << 1); break; - case FEC_5_6: value |= (5 << 1); break; - case FEC_7_8: value |= (7 << 1); break; - default: - case FEC_1_2: value |= (1 << 1); break; + case FEC_2_3: + value |= (2 << 1); + break; + case FEC_3_4: + value |= (3 << 1); + break; + case FEC_5_6: + value |= (5 << 1); + break; + case FEC_7_8: + value |= (7 << 1); + break; + default: + case FEC_1_2: + value |= (1 << 1); + break; } dib7000p_write_word(state, 208, value); /* offset loop parameters */ - dib7000p_write_word(state, 26, 0x6680); // timf(6xxx) - dib7000p_write_word(state, 32, 0x0003); // pha_off_max(xxx3) - dib7000p_write_word(state, 29, 0x1273); // isi - dib7000p_write_word(state, 33, 0x0005); // sfreq(xxx5) + dib7000p_write_word(state, 26, 0x6680); + dib7000p_write_word(state, 32, 0x0003); + dib7000p_write_word(state, 29, 0x1273); + dib7000p_write_word(state, 33, 0x0005); /* P_dvsy_sync_wait */ switch (ch->u.ofdm.transmission_mode) { - case TRANSMISSION_MODE_8K: value = 256; break; - case TRANSMISSION_MODE_4K: value = 128; break; - case TRANSMISSION_MODE_2K: - default: value = 64; break; + case TRANSMISSION_MODE_8K: + value = 256; + break; + case TRANSMISSION_MODE_4K: + value = 128; + break; + case TRANSMISSION_MODE_2K: + default: + value = 64; + break; } switch (ch->u.ofdm.guard_interval) { - case GUARD_INTERVAL_1_16: value *= 2; break; - case GUARD_INTERVAL_1_8: value *= 4; break; - case GUARD_INTERVAL_1_4: value *= 8; break; - default: - case GUARD_INTERVAL_1_32: value *= 1; break; + case GUARD_INTERVAL_1_16: + value *= 2; + break; + case GUARD_INTERVAL_1_8: + value *= 4; + break; + case GUARD_INTERVAL_1_4: + value *= 8; + break; + default: + case GUARD_INTERVAL_1_32: + value *= 1; + break; } if (state->cfg.diversity_delay == 0) - state->div_sync_wait = (value * 3) / 2 + 48; // add 50% SFN margin + compensate for one DVSY-fifo + state->div_sync_wait = (value * 3) / 2 + 48; else - state->div_sync_wait = (value * 3) / 2 + state->cfg.diversity_delay; // add 50% SFN margin + compensate for one DVSY-fifo + state->div_sync_wait = (value * 3) / 2 + state->cfg.diversity_delay; /* deactive the possibility of diversity reception if extended interleaver */ state->div_force_off = !1 && ch->u.ofdm.transmission_mode != TRANSMISSION_MODE_8K; @@ -792,24 +1003,24 @@ static void dib7000p_set_channel(struct dib7000p_state *state, struct dvb_fronte /* channel estimation fine configuration */ switch (ch->u.ofdm.constellation) { - case QAM_64: - est[0] = 0x0148; /* P_adp_regul_cnt 0.04 */ - est[1] = 0xfff0; /* P_adp_noise_cnt -0.002 */ - est[2] = 0x00a4; /* P_adp_regul_ext 0.02 */ - est[3] = 0xfff8; /* P_adp_noise_ext -0.001 */ - break; - case QAM_16: - est[0] = 0x023d; /* P_adp_regul_cnt 0.07 */ - est[1] = 0xffdf; /* P_adp_noise_cnt -0.004 */ - est[2] = 0x00a4; /* P_adp_regul_ext 0.02 */ - est[3] = 0xfff0; /* P_adp_noise_ext -0.002 */ - break; - default: - est[0] = 0x099a; /* P_adp_regul_cnt 0.3 */ - est[1] = 0xffae; /* P_adp_noise_cnt -0.01 */ - est[2] = 0x0333; /* P_adp_regul_ext 0.1 */ - est[3] = 0xfff8; /* P_adp_noise_ext -0.002 */ - break; + case QAM_64: + est[0] = 0x0148; /* P_adp_regul_cnt 0.04 */ + est[1] = 0xfff0; /* P_adp_noise_cnt -0.002 */ + est[2] = 0x00a4; /* P_adp_regul_ext 0.02 */ + est[3] = 0xfff8; /* P_adp_noise_ext -0.001 */ + break; + case QAM_16: + est[0] = 0x023d; /* P_adp_regul_cnt 0.07 */ + est[1] = 0xffdf; /* P_adp_noise_cnt -0.004 */ + est[2] = 0x00a4; /* P_adp_regul_ext 0.02 */ + est[3] = 0xfff0; /* P_adp_noise_ext -0.002 */ + break; + default: + est[0] = 0x099a; /* P_adp_regul_cnt 0.3 */ + est[1] = 0xffae; /* P_adp_noise_cnt -0.01 */ + est[2] = 0x0333; /* P_adp_regul_ext 0.1 */ + est[3] = 0xfff8; /* P_adp_noise_ext -0.002 */ + break; } for (value = 0; value < 4; value++) dib7000p_write_word(state, 187 + value, est[value]); @@ -820,14 +1031,15 @@ static int dib7000p_autosearch_start(struct dvb_frontend *demod, struct dvb_fron struct dib7000p_state *state = demod->demodulator_priv; struct dvb_frontend_parameters schan; u32 value, factor; + u32 internal = dib7000p_get_internal_freq(state); schan = *ch; schan.u.ofdm.constellation = QAM_64; - schan.u.ofdm.guard_interval = GUARD_INTERVAL_1_32; - schan.u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; - schan.u.ofdm.code_rate_HP = FEC_2_3; - schan.u.ofdm.code_rate_LP = FEC_3_4; - schan.u.ofdm.hierarchy_information = 0; + schan.u.ofdm.guard_interval = GUARD_INTERVAL_1_32; + schan.u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; + schan.u.ofdm.code_rate_HP = FEC_2_3; + schan.u.ofdm.code_rate_LP = FEC_3_4; + schan.u.ofdm.hierarchy_information = 0; dib7000p_set_channel(state, &schan, 7); @@ -837,16 +1049,15 @@ static int dib7000p_autosearch_start(struct dvb_frontend *demod, struct dvb_fron else factor = 6; - // always use the setting for 8MHz here lock_time for 7,6 MHz are longer - value = 30 * state->cfg.bw->internal * factor; - dib7000p_write_word(state, 6, (u16) ((value >> 16) & 0xffff)); // lock0 wait time - dib7000p_write_word(state, 7, (u16) (value & 0xffff)); // lock0 wait time - value = 100 * state->cfg.bw->internal * factor; - dib7000p_write_word(state, 8, (u16) ((value >> 16) & 0xffff)); // lock1 wait time - dib7000p_write_word(state, 9, (u16) (value & 0xffff)); // lock1 wait time - value = 500 * state->cfg.bw->internal * factor; - dib7000p_write_word(state, 10, (u16) ((value >> 16) & 0xffff)); // lock2 wait time - dib7000p_write_word(state, 11, (u16) (value & 0xffff)); // lock2 wait time + value = 30 * internal * factor; + dib7000p_write_word(state, 6, (u16) ((value >> 16) & 0xffff)); + dib7000p_write_word(state, 7, (u16) (value & 0xffff)); + value = 100 * internal * factor; + dib7000p_write_word(state, 8, (u16) ((value >> 16) & 0xffff)); + dib7000p_write_word(state, 9, (u16) (value & 0xffff)); + value = 500 * internal * factor; + dib7000p_write_word(state, 10, (u16) ((value >> 16) & 0xffff)); + dib7000p_write_word(state, 11, (u16) (value & 0xffff)); value = dib7000p_read_word(state, 0); dib7000p_write_word(state, 0, (u16) ((1 << 9) | value)); @@ -861,101 +1072,101 @@ static int dib7000p_autosearch_is_irq(struct dvb_frontend *demod) struct dib7000p_state *state = demod->demodulator_priv; u16 irq_pending = dib7000p_read_word(state, 1284); - if (irq_pending & 0x1) // failed + if (irq_pending & 0x1) return 1; - if (irq_pending & 0x2) // succeeded + if (irq_pending & 0x2) return 2; - return 0; // still pending + return 0; } static void dib7000p_spur_protect(struct dib7000p_state *state, u32 rf_khz, u32 bw) { - static s16 notch[]={16143, 14402, 12238, 9713, 6902, 3888, 759, -2392}; - static u8 sine [] ={0, 2, 3, 5, 6, 8, 9, 11, 13, 14, 16, 17, 19, 20, 22, - 24, 25, 27, 28, 30, 31, 33, 34, 36, 38, 39, 41, 42, 44, 45, 47, 48, 50, 51, - 53, 55, 56, 58, 59, 61, 62, 64, 65, 67, 68, 70, 71, 73, 74, 76, 77, 79, 80, - 82, 83, 85, 86, 88, 89, 91, 92, 94, 95, 97, 98, 99, 101, 102, 104, 105, - 107, 108, 109, 111, 112, 114, 115, 117, 118, 119, 121, 122, 123, 125, 126, - 128, 129, 130, 132, 133, 134, 136, 137, 138, 140, 141, 142, 144, 145, 146, - 147, 149, 150, 151, 152, 154, 155, 156, 157, 159, 160, 161, 162, 164, 165, - 166, 167, 168, 170, 171, 172, 173, 174, 175, 177, 178, 179, 180, 181, 182, - 183, 184, 185, 186, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, - 199, 200, 201, 202, 203, 204, 205, 206, 207, 207, 208, 209, 210, 211, 212, - 213, 214, 215, 215, 216, 217, 218, 219, 220, 220, 221, 222, 223, 224, 224, - 225, 226, 227, 227, 228, 229, 229, 230, 231, 231, 232, 233, 233, 234, 235, - 235, 236, 237, 237, 238, 238, 239, 239, 240, 241, 241, 242, 242, 243, 243, - 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 248, 249, 249, 249, - 250, 250, 250, 251, 251, 251, 252, 252, 252, 252, 253, 253, 253, 253, 254, - 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, - 255, 255, 255, 255, 255, 255}; + static s16 notch[] = { 16143, 14402, 12238, 9713, 6902, 3888, 759, -2392 }; + static u8 sine[] = { 0, 2, 3, 5, 6, 8, 9, 11, 13, 14, 16, 17, 19, 20, 22, + 24, 25, 27, 28, 30, 31, 33, 34, 36, 38, 39, 41, 42, 44, 45, 47, 48, 50, 51, + 53, 55, 56, 58, 59, 61, 62, 64, 65, 67, 68, 70, 71, 73, 74, 76, 77, 79, 80, + 82, 83, 85, 86, 88, 89, 91, 92, 94, 95, 97, 98, 99, 101, 102, 104, 105, + 107, 108, 109, 111, 112, 114, 115, 117, 118, 119, 121, 122, 123, 125, 126, + 128, 129, 130, 132, 133, 134, 136, 137, 138, 140, 141, 142, 144, 145, 146, + 147, 149, 150, 151, 152, 154, 155, 156, 157, 159, 160, 161, 162, 164, 165, + 166, 167, 168, 170, 171, 172, 173, 174, 175, 177, 178, 179, 180, 181, 182, + 183, 184, 185, 186, 188, 189, 190, 191, 192, 193, 194, 195, 196, 197, 198, + 199, 200, 201, 202, 203, 204, 205, 206, 207, 207, 208, 209, 210, 211, 212, + 213, 214, 215, 215, 216, 217, 218, 219, 220, 220, 221, 222, 223, 224, 224, + 225, 226, 227, 227, 228, 229, 229, 230, 231, 231, 232, 233, 233, 234, 235, + 235, 236, 237, 237, 238, 238, 239, 239, 240, 241, 241, 242, 242, 243, 243, + 244, 244, 245, 245, 245, 246, 246, 247, 247, 248, 248, 248, 249, 249, 249, + 250, 250, 250, 251, 251, 251, 252, 252, 252, 252, 253, 253, 253, 253, 254, + 254, 254, 254, 254, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255 + }; u32 xtal = state->cfg.bw->xtal_hz / 1000; int f_rel = DIV_ROUND_CLOSEST(rf_khz, xtal) * xtal - rf_khz; int k; - int coef_re[8],coef_im[8]; + int coef_re[8], coef_im[8]; int bw_khz = bw; u32 pha; - dprintk( "relative position of the Spur: %dk (RF: %dk, XTAL: %dk)", f_rel, rf_khz, xtal); - + dprintk("relative position of the Spur: %dk (RF: %dk, XTAL: %dk)", f_rel, rf_khz, xtal); - if (f_rel < -bw_khz/2 || f_rel > bw_khz/2) + if (f_rel < -bw_khz / 2 || f_rel > bw_khz / 2) return; bw_khz /= 100; - dib7000p_write_word(state, 142 ,0x0610); + dib7000p_write_word(state, 142, 0x0610); for (k = 0; k < 8; k++) { - pha = ((f_rel * (k+1) * 112 * 80/bw_khz) /1000) & 0x3ff; + pha = ((f_rel * (k + 1) * 112 * 80 / bw_khz) / 1000) & 0x3ff; - if (pha==0) { + if (pha == 0) { coef_re[k] = 256; coef_im[k] = 0; - } else if(pha < 256) { - coef_re[k] = sine[256-(pha&0xff)]; - coef_im[k] = sine[pha&0xff]; + } else if (pha < 256) { + coef_re[k] = sine[256 - (pha & 0xff)]; + coef_im[k] = sine[pha & 0xff]; } else if (pha == 256) { coef_re[k] = 0; coef_im[k] = 256; } else if (pha < 512) { - coef_re[k] = -sine[pha&0xff]; - coef_im[k] = sine[256 - (pha&0xff)]; + coef_re[k] = -sine[pha & 0xff]; + coef_im[k] = sine[256 - (pha & 0xff)]; } else if (pha == 512) { coef_re[k] = -256; coef_im[k] = 0; } else if (pha < 768) { - coef_re[k] = -sine[256-(pha&0xff)]; - coef_im[k] = -sine[pha&0xff]; + coef_re[k] = -sine[256 - (pha & 0xff)]; + coef_im[k] = -sine[pha & 0xff]; } else if (pha == 768) { coef_re[k] = 0; coef_im[k] = -256; } else { - coef_re[k] = sine[pha&0xff]; - coef_im[k] = -sine[256 - (pha&0xff)]; + coef_re[k] = sine[pha & 0xff]; + coef_im[k] = -sine[256 - (pha & 0xff)]; } coef_re[k] *= notch[k]; - coef_re[k] += (1<<14); - if (coef_re[k] >= (1<<24)) - coef_re[k] = (1<<24) - 1; - coef_re[k] /= (1<<15); + coef_re[k] += (1 << 14); + if (coef_re[k] >= (1 << 24)) + coef_re[k] = (1 << 24) - 1; + coef_re[k] /= (1 << 15); coef_im[k] *= notch[k]; - coef_im[k] += (1<<14); - if (coef_im[k] >= (1<<24)) - coef_im[k] = (1<<24)-1; - coef_im[k] /= (1<<15); + coef_im[k] += (1 << 14); + if (coef_im[k] >= (1 << 24)) + coef_im[k] = (1 << 24) - 1; + coef_im[k] /= (1 << 15); - dprintk( "PALF COEF: %d re: %d im: %d", k, coef_re[k], coef_im[k]); + dprintk("PALF COEF: %d re: %d im: %d", k, coef_re[k], coef_im[k]); dib7000p_write_word(state, 143, (0 << 14) | (k << 10) | (coef_re[k] & 0x3ff)); dib7000p_write_word(state, 144, coef_im[k] & 0x3ff); dib7000p_write_word(state, 143, (1 << 14) | (k << 10) | (coef_re[k] & 0x3ff)); } - dib7000p_write_word(state,143 ,0); + dib7000p_write_word(state, 143, 0); } static int dib7000p_tune(struct dvb_frontend *demod, struct dvb_frontend_parameters *ch) @@ -976,11 +1187,11 @@ static int dib7000p_tune(struct dvb_frontend *demod, struct dvb_frontend_paramet /* P_ctrl_inh_cor=0, P_ctrl_alpha_cor=4, P_ctrl_inh_isi=0, P_ctrl_alpha_isi=3, P_ctrl_inh_cor4=1, P_ctrl_alpha_cor4=3 */ tmp = (0 << 14) | (4 << 10) | (0 << 9) | (3 << 5) | (1 << 4) | (0x3); if (state->sfn_workaround_active) { - dprintk( "SFN workaround is active"); + dprintk("SFN workaround is active"); tmp |= (1 << 9); - dib7000p_write_word(state, 166, 0x4000); // P_pha3_force_pha_shift + dib7000p_write_word(state, 166, 0x4000); } else { - dib7000p_write_word(state, 166, 0x0000); // P_pha3_force_pha_shift + dib7000p_write_word(state, 166, 0x0000); } dib7000p_write_word(state, 29, tmp); @@ -993,51 +1204,72 @@ static int dib7000p_tune(struct dvb_frontend *demod, struct dvb_frontend_paramet /* P_timf_alpha, P_corm_alpha=6, P_corm_thres=0x80 */ tmp = (6 << 8) | 0x80; switch (ch->u.ofdm.transmission_mode) { - case TRANSMISSION_MODE_2K: tmp |= (7 << 12); break; - case TRANSMISSION_MODE_4K: tmp |= (8 << 12); break; - default: - case TRANSMISSION_MODE_8K: tmp |= (9 << 12); break; + case TRANSMISSION_MODE_2K: + tmp |= (2 << 12); + break; + case TRANSMISSION_MODE_4K: + tmp |= (3 << 12); + break; + default: + case TRANSMISSION_MODE_8K: + tmp |= (4 << 12); + break; } - dib7000p_write_word(state, 26, tmp); /* timf_a(6xxx) */ + dib7000p_write_word(state, 26, tmp); /* timf_a(6xxx) */ /* P_ctrl_freeze_pha_shift=0, P_ctrl_pha_off_max */ tmp = (0 << 4); switch (ch->u.ofdm.transmission_mode) { - case TRANSMISSION_MODE_2K: tmp |= 0x6; break; - case TRANSMISSION_MODE_4K: tmp |= 0x7; break; - default: - case TRANSMISSION_MODE_8K: tmp |= 0x8; break; + case TRANSMISSION_MODE_2K: + tmp |= 0x6; + break; + case TRANSMISSION_MODE_4K: + tmp |= 0x7; + break; + default: + case TRANSMISSION_MODE_8K: + tmp |= 0x8; + break; } - dib7000p_write_word(state, 32, tmp); + dib7000p_write_word(state, 32, tmp); /* P_ctrl_sfreq_inh=0, P_ctrl_sfreq_step */ tmp = (0 << 4); switch (ch->u.ofdm.transmission_mode) { - case TRANSMISSION_MODE_2K: tmp |= 0x6; break; - case TRANSMISSION_MODE_4K: tmp |= 0x7; break; - default: - case TRANSMISSION_MODE_8K: tmp |= 0x8; break; + case TRANSMISSION_MODE_2K: + tmp |= 0x6; + break; + case TRANSMISSION_MODE_4K: + tmp |= 0x7; + break; + default: + case TRANSMISSION_MODE_8K: + tmp |= 0x8; + break; } - dib7000p_write_word(state, 33, tmp); + dib7000p_write_word(state, 33, tmp); - tmp = dib7000p_read_word(state,509); + tmp = dib7000p_read_word(state, 509); if (!((tmp >> 6) & 0x1)) { /* restart the fec */ - tmp = dib7000p_read_word(state,771); + tmp = dib7000p_read_word(state, 771); dib7000p_write_word(state, 771, tmp | (1 << 1)); dib7000p_write_word(state, 771, tmp); - msleep(10); - tmp = dib7000p_read_word(state,509); + msleep(40); + tmp = dib7000p_read_word(state, 509); } - // we achieved a lock - it's time to update the osc freq - if ((tmp >> 6) & 0x1) + if ((tmp >> 6) & 0x1) { dib7000p_update_timf(state); + /* P_timf_alpha += 2 */ + tmp = dib7000p_read_word(state, 26); + dib7000p_write_word(state, 26, (tmp & ~(0xf << 12)) | ((((tmp >> 12) & 0xf) + 5) << 12)); + } if (state->cfg.spur_protect) - dib7000p_spur_protect(state, ch->frequency/1000, BANDWIDTH_TO_KHZ(ch->u.ofdm.bandwidth)); + dib7000p_spur_protect(state, ch->frequency / 1000, BANDWIDTH_TO_KHZ(ch->u.ofdm.bandwidth)); - dib7000p_set_bandwidth(state, BANDWIDTH_TO_KHZ(ch->u.ofdm.bandwidth)); + dib7000p_set_bandwidth(state, BANDWIDTH_TO_KHZ(ch->u.ofdm.bandwidth)); return 0; } @@ -1046,63 +1278,82 @@ static int dib7000p_wakeup(struct dvb_frontend *demod) struct dib7000p_state *state = demod->demodulator_priv; dib7000p_set_power_mode(state, DIB7000P_POWER_ALL); dib7000p_set_adc_state(state, DIBX000_SLOW_ADC_ON); + if (state->version == SOC7090) + dib7000p_sad_calib(state); return 0; } static int dib7000p_sleep(struct dvb_frontend *demod) { struct dib7000p_state *state = demod->demodulator_priv; + if (state->version == SOC7090) + return dib7090_set_output_mode(demod, OUTMODE_HIGH_Z) | dib7000p_set_power_mode(state, DIB7000P_POWER_INTERFACE_ONLY); return dib7000p_set_output_mode(state, OUTMODE_HIGH_Z) | dib7000p_set_power_mode(state, DIB7000P_POWER_INTERFACE_ONLY); } static int dib7000p_identify(struct dib7000p_state *st) { u16 value; - dprintk( "checking demod on I2C address: %d (%x)", - st->i2c_addr, st->i2c_addr); + dprintk("checking demod on I2C address: %d (%x)", st->i2c_addr, st->i2c_addr); if ((value = dib7000p_read_word(st, 768)) != 0x01b3) { - dprintk( "wrong Vendor ID (read=0x%x)",value); + dprintk("wrong Vendor ID (read=0x%x)", value); return -EREMOTEIO; } if ((value = dib7000p_read_word(st, 769)) != 0x4000) { - dprintk( "wrong Device ID (%x)",value); + dprintk("wrong Device ID (%x)", value); return -EREMOTEIO; } return 0; } - -static int dib7000p_get_frontend(struct dvb_frontend* fe, - struct dvb_frontend_parameters *fep) +static int dib7000p_get_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) { struct dib7000p_state *state = fe->demodulator_priv; - u16 tps = dib7000p_read_word(state,463); + u16 tps = dib7000p_read_word(state, 463); fep->inversion = INVERSION_AUTO; fep->u.ofdm.bandwidth = BANDWIDTH_TO_INDEX(state->current_bandwidth); switch ((tps >> 8) & 0x3) { - case 0: fep->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K; break; - case 1: fep->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; break; - /* case 2: fep->u.ofdm.transmission_mode = TRANSMISSION_MODE_4K; break; */ + case 0: + fep->u.ofdm.transmission_mode = TRANSMISSION_MODE_2K; + break; + case 1: + fep->u.ofdm.transmission_mode = TRANSMISSION_MODE_8K; + break; + /* case 2: fep->u.ofdm.transmission_mode = TRANSMISSION_MODE_4K; break; */ } switch (tps & 0x3) { - case 0: fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_32; break; - case 1: fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_16; break; - case 2: fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; break; - case 3: fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_4; break; + case 0: + fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + fep->u.ofdm.guard_interval = GUARD_INTERVAL_1_4; + break; } switch ((tps >> 14) & 0x3) { - case 0: fep->u.ofdm.constellation = QPSK; break; - case 1: fep->u.ofdm.constellation = QAM_16; break; - case 2: - default: fep->u.ofdm.constellation = QAM_64; break; + case 0: + fep->u.ofdm.constellation = QPSK; + break; + case 1: + fep->u.ofdm.constellation = QAM_16; + break; + case 2: + default: + fep->u.ofdm.constellation = QAM_64; + break; } /* as long as the frontend_param structure is fixed for hierarchical transmission I refuse to use it */ @@ -1110,22 +1361,42 @@ static int dib7000p_get_frontend(struct dvb_frontend* fe, fep->u.ofdm.hierarchy_information = HIERARCHY_NONE; switch ((tps >> 5) & 0x7) { - case 1: fep->u.ofdm.code_rate_HP = FEC_1_2; break; - case 2: fep->u.ofdm.code_rate_HP = FEC_2_3; break; - case 3: fep->u.ofdm.code_rate_HP = FEC_3_4; break; - case 5: fep->u.ofdm.code_rate_HP = FEC_5_6; break; - case 7: - default: fep->u.ofdm.code_rate_HP = FEC_7_8; break; + case 1: + fep->u.ofdm.code_rate_HP = FEC_1_2; + break; + case 2: + fep->u.ofdm.code_rate_HP = FEC_2_3; + break; + case 3: + fep->u.ofdm.code_rate_HP = FEC_3_4; + break; + case 5: + fep->u.ofdm.code_rate_HP = FEC_5_6; + break; + case 7: + default: + fep->u.ofdm.code_rate_HP = FEC_7_8; + break; } switch ((tps >> 2) & 0x7) { - case 1: fep->u.ofdm.code_rate_LP = FEC_1_2; break; - case 2: fep->u.ofdm.code_rate_LP = FEC_2_3; break; - case 3: fep->u.ofdm.code_rate_LP = FEC_3_4; break; - case 5: fep->u.ofdm.code_rate_LP = FEC_5_6; break; - case 7: - default: fep->u.ofdm.code_rate_LP = FEC_7_8; break; + case 1: + fep->u.ofdm.code_rate_LP = FEC_1_2; + break; + case 2: + fep->u.ofdm.code_rate_LP = FEC_2_3; + break; + case 3: + fep->u.ofdm.code_rate_LP = FEC_3_4; + break; + case 5: + fep->u.ofdm.code_rate_LP = FEC_5_6; + break; + case 7: + default: + fep->u.ofdm.code_rate_LP = FEC_7_8; + break; } /* native interleaver: (dib7000p_read_word(state, 464) >> 5) & 0x1 */ @@ -1133,15 +1404,18 @@ static int dib7000p_get_frontend(struct dvb_frontend* fe, return 0; } -static int dib7000p_set_frontend(struct dvb_frontend* fe, - struct dvb_frontend_parameters *fep) +static int dib7000p_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) { struct dib7000p_state *state = fe->demodulator_priv; int time, ret; - dib7000p_set_output_mode(state, OUTMODE_HIGH_Z); + if (state->version == SOC7090) { + dib7090_set_diversity_in(fe, 0); + dib7090_set_output_mode(fe, OUTMODE_HIGH_Z); + } else + dib7000p_set_output_mode(state, OUTMODE_HIGH_Z); - /* maybe the parameter has been changed */ + /* maybe the parameter has been changed */ state->sfn_workaround_active = buggy_sfn_workaround; if (fe->ops.tuner_ops.set_params) @@ -1156,9 +1430,7 @@ static int dib7000p_set_frontend(struct dvb_frontend* fe, } while (time != -1); if (fep->u.ofdm.transmission_mode == TRANSMISSION_MODE_AUTO || - fep->u.ofdm.guard_interval == GUARD_INTERVAL_AUTO || - fep->u.ofdm.constellation == QAM_AUTO || - fep->u.ofdm.code_rate_HP == FEC_AUTO) { + fep->u.ofdm.guard_interval == GUARD_INTERVAL_AUTO || fep->u.ofdm.constellation == QAM_AUTO || fep->u.ofdm.code_rate_HP == FEC_AUTO) { int i = 800, found; dib7000p_autosearch_start(fe, fep); @@ -1167,9 +1439,9 @@ static int dib7000p_set_frontend(struct dvb_frontend* fe, found = dib7000p_autosearch_is_irq(fe); } while (found == 0 && i--); - dprintk("autosearch returns: %d",found); + dprintk("autosearch returns: %d", found); if (found == 0 || found == 1) - return 0; // no channel found + return 0; dib7000p_get_frontend(fe, fep); } @@ -1177,11 +1449,15 @@ static int dib7000p_set_frontend(struct dvb_frontend* fe, ret = dib7000p_tune(fe, fep); /* make this a config parameter */ - dib7000p_set_output_mode(state, state->cfg.output_mode); - return ret; + if (state->version == SOC7090) + dib7090_set_output_mode(fe, state->cfg.output_mode); + else + dib7000p_set_output_mode(state, state->cfg.output_mode); + + return ret; } -static int dib7000p_read_status(struct dvb_frontend *fe, fe_status_t *stat) +static int dib7000p_read_status(struct dvb_frontend *fe, fe_status_t * stat) { struct dib7000p_state *state = fe->demodulator_priv; u16 lock = dib7000p_read_word(state, 509); @@ -1196,27 +1472,27 @@ static int dib7000p_read_status(struct dvb_frontend *fe, fe_status_t *stat) *stat |= FE_HAS_VITERBI; if (lock & 0x0010) *stat |= FE_HAS_SYNC; - if ((lock & 0x0038) == 0x38) + if ((lock & 0x0038) == 0x38) *stat |= FE_HAS_LOCK; return 0; } -static int dib7000p_read_ber(struct dvb_frontend *fe, u32 *ber) +static int dib7000p_read_ber(struct dvb_frontend *fe, u32 * ber) { struct dib7000p_state *state = fe->demodulator_priv; *ber = (dib7000p_read_word(state, 500) << 16) | dib7000p_read_word(state, 501); return 0; } -static int dib7000p_read_unc_blocks(struct dvb_frontend *fe, u32 *unc) +static int dib7000p_read_unc_blocks(struct dvb_frontend *fe, u32 * unc) { struct dib7000p_state *state = fe->demodulator_priv; *unc = dib7000p_read_word(state, 506); return 0; } -static int dib7000p_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +static int dib7000p_read_signal_strength(struct dvb_frontend *fe, u16 * strength) { struct dib7000p_state *state = fe->demodulator_priv; u16 val = dib7000p_read_word(state, 394); @@ -1224,7 +1500,7 @@ static int dib7000p_read_signal_strength(struct dvb_frontend *fe, u16 *strength) return 0; } -static int dib7000p_read_snr(struct dvb_frontend* fe, u16 *snr) +static int dib7000p_read_snr(struct dvb_frontend *fe, u16 * snr) { struct dib7000p_state *state = fe->demodulator_priv; u16 val; @@ -1240,19 +1516,17 @@ static int dib7000p_read_snr(struct dvb_frontend* fe, u16 *snr) noise_exp -= 0x40; signal_mant = (val >> 6) & 0xFF; - signal_exp = (val & 0x3F); + signal_exp = (val & 0x3F); if ((signal_exp & 0x20) != 0) signal_exp -= 0x40; if (signal_mant != 0) - result = intlog10(2) * 10 * signal_exp + 10 * - intlog10(signal_mant); + result = intlog10(2) * 10 * signal_exp + 10 * intlog10(signal_mant); else result = intlog10(2) * 10 * signal_exp - 100; if (noise_mant != 0) - result -= intlog10(2) * 10 * noise_exp + 10 * - intlog10(noise_mant); + result -= intlog10(2) * 10 * noise_exp + 10 * intlog10(noise_mant); else result -= intlog10(2) * 10 * noise_exp - 100; @@ -1260,7 +1534,7 @@ static int dib7000p_read_snr(struct dvb_frontend* fe, u16 *snr) return 0; } -static int dib7000p_fe_get_tune_settings(struct dvb_frontend* fe, struct dvb_frontend_tune_settings *tune) +static int dib7000p_fe_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_tune_settings *tune) { tune->min_delay_ms = 1000; return 0; @@ -1270,6 +1544,7 @@ static void dib7000p_release(struct dvb_frontend *demod) { struct dib7000p_state *st = demod->demodulator_priv; dibx000_exit_i2c_master(&st->i2c_master); + i2c_del_adapter(&st->dib7090_tuner_adap); kfree(st); } @@ -1277,8 +1552,8 @@ int dib7000pc_detection(struct i2c_adapter *i2c_adap) { u8 tx[2], rx[2]; struct i2c_msg msg[2] = { - { .addr = 18 >> 1, .flags = 0, .buf = tx, .len = 2 }, - { .addr = 18 >> 1, .flags = I2C_M_RD, .buf = rx, .len = 2 }, + {.addr = 18 >> 1, .flags = 0, .buf = tx, .len = 2}, + {.addr = 18 >> 1, .flags = I2C_M_RD, .buf = rx, .len = 2}, }; tx[0] = 0x03; @@ -1303,7 +1578,7 @@ int dib7000pc_detection(struct i2c_adapter *i2c_adap) } EXPORT_SYMBOL(dib7000pc_detection); -struct i2c_adapter * dib7000p_get_i2c_master(struct dvb_frontend *demod, enum dibx000_i2c_interface intf, int gating) +struct i2c_adapter *dib7000p_get_i2c_master(struct dvb_frontend *demod, enum dibx000_i2c_interface intf, int gating) { struct dib7000p_state *st = demod->demodulator_priv; return dibx000_get_i2c_adapter(&st->i2c_master, intf, gating); @@ -1312,19 +1587,19 @@ EXPORT_SYMBOL(dib7000p_get_i2c_master); int dib7000p_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff) { - struct dib7000p_state *state = fe->demodulator_priv; - u16 val = dib7000p_read_word(state, 235) & 0xffef; - val |= (onoff & 0x1) << 4; - dprintk("PID filter enabled %d", onoff); - return dib7000p_write_word(state, 235, val); + struct dib7000p_state *state = fe->demodulator_priv; + u16 val = dib7000p_read_word(state, 235) & 0xffef; + val |= (onoff & 0x1) << 4; + dprintk("PID filter enabled %d", onoff); + return dib7000p_write_word(state, 235, val); } EXPORT_SYMBOL(dib7000p_pid_filter_ctrl); int dib7000p_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff) { - struct dib7000p_state *state = fe->demodulator_priv; - dprintk("PID filter: index %x, PID %d, OnOff %d", id, pid, onoff); - return dib7000p_write_word(state, 241 + id, onoff ? (1 << 13) | pid : 0); + struct dib7000p_state *state = fe->demodulator_priv; + dprintk("PID filter: index %x, PID %d, OnOff %d", id, pid, onoff); + return dib7000p_write_word(state, 241 + id, onoff ? (1 << 13) | pid : 0); } EXPORT_SYMBOL(dib7000p_pid_filter); @@ -1340,16 +1615,19 @@ int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 defau dpst->i2c_adap = i2c; - for (k = no_of_demods-1; k >= 0; k--) { + for (k = no_of_demods - 1; k >= 0; k--) { dpst->cfg = cfg[k]; /* designated i2c address */ - new_addr = (0x40 + k) << 1; + if (cfg[k].default_i2c_addr != 0) + new_addr = cfg[k].default_i2c_addr + (k << 1); + else + new_addr = (0x40 + k) << 1; dpst->i2c_addr = new_addr; - dib7000p_write_word(dpst, 1287, 0x0003); /* sram lead in, rdy */ + dib7000p_write_word(dpst, 1287, 0x0003); /* sram lead in, rdy */ if (dib7000p_identify(dpst) != 0) { dpst->i2c_addr = default_addr; - dib7000p_write_word(dpst, 1287, 0x0003); /* sram lead in, rdy */ + dib7000p_write_word(dpst, 1287, 0x0003); /* sram lead in, rdy */ if (dib7000p_identify(dpst) != 0) { dprintk("DiB7000P #%d: not identified\n", k); kfree(dpst); @@ -1368,7 +1646,10 @@ int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 defau for (k = 0; k < no_of_demods; k++) { dpst->cfg = cfg[k]; - dpst->i2c_addr = (0x40 + k) << 1; + if (cfg[k].default_i2c_addr != 0) + dpst->i2c_addr = (cfg[k].default_i2c_addr + k) << 1; + else + dpst->i2c_addr = (0x40 + k) << 1; // unforce divstr dib7000p_write_word(dpst, 1285, dpst->i2c_addr << 2); @@ -1382,8 +1663,613 @@ int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 defau } EXPORT_SYMBOL(dib7000p_i2c_enumeration); +static const s32 lut_1000ln_mant[] = { + 6908, 6956, 7003, 7047, 7090, 7131, 7170, 7208, 7244, 7279, 7313, 7346, 7377, 7408, 7438, 7467, 7495, 7523, 7549, 7575, 7600 +}; + +static s32 dib7000p_get_adc_power(struct dvb_frontend *fe) +{ + struct dib7000p_state *state = fe->demodulator_priv; + u32 tmp_val = 0, exp = 0, mant = 0; + s32 pow_i; + u16 buf[2]; + u8 ix = 0; + + buf[0] = dib7000p_read_word(state, 0x184); + buf[1] = dib7000p_read_word(state, 0x185); + pow_i = (buf[0] << 16) | buf[1]; + dprintk("raw pow_i = %d", pow_i); + + tmp_val = pow_i; + while (tmp_val >>= 1) + exp++; + + mant = (pow_i * 1000 / (1 << exp)); + dprintk(" mant = %d exp = %d", mant / 1000, exp); + + ix = (u8) ((mant - 1000) / 100); /* index of the LUT */ + dprintk(" ix = %d", ix); + + pow_i = (lut_1000ln_mant[ix] + 693 * (exp - 20) - 6908); + pow_i = (pow_i << 8) / 1000; + dprintk(" pow_i = %d", pow_i); + + return pow_i; +} + +static int map_addr_to_serpar_number(struct i2c_msg *msg) +{ + if ((msg->buf[0] <= 15)) + msg->buf[0] -= 1; + else if (msg->buf[0] == 17) + msg->buf[0] = 15; + else if (msg->buf[0] == 16) + msg->buf[0] = 17; + else if (msg->buf[0] == 19) + msg->buf[0] = 16; + else if (msg->buf[0] >= 21 && msg->buf[0] <= 25) + msg->buf[0] -= 3; + else if (msg->buf[0] == 28) + msg->buf[0] = 23; + else + return -EINVAL; + return 0; +} + +static int w7090p_tuner_write_serpar(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + struct dib7000p_state *state = i2c_get_adapdata(i2c_adap); + u8 n_overflow = 1; + u16 i = 1000; + u16 serpar_num = msg[0].buf[0]; + + while (n_overflow == 1 && i) { + n_overflow = (dib7000p_read_word(state, 1984) >> 1) & 0x1; + i--; + if (i == 0) + dprintk("Tuner ITF: write busy (overflow)"); + } + dib7000p_write_word(state, 1985, (1 << 6) | (serpar_num & 0x3f)); + dib7000p_write_word(state, 1986, (msg[0].buf[1] << 8) | msg[0].buf[2]); + + return num; +} + +static int w7090p_tuner_read_serpar(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + struct dib7000p_state *state = i2c_get_adapdata(i2c_adap); + u8 n_overflow = 1, n_empty = 1; + u16 i = 1000; + u16 serpar_num = msg[0].buf[0]; + u16 read_word; + + while (n_overflow == 1 && i) { + n_overflow = (dib7000p_read_word(state, 1984) >> 1) & 0x1; + i--; + if (i == 0) + dprintk("TunerITF: read busy (overflow)"); + } + dib7000p_write_word(state, 1985, (0 << 6) | (serpar_num & 0x3f)); + + i = 1000; + while (n_empty == 1 && i) { + n_empty = dib7000p_read_word(state, 1984) & 0x1; + i--; + if (i == 0) + dprintk("TunerITF: read busy (empty)"); + } + read_word = dib7000p_read_word(state, 1987); + msg[1].buf[0] = (read_word >> 8) & 0xff; + msg[1].buf[1] = (read_word) & 0xff; + + return num; +} + +static int w7090p_tuner_rw_serpar(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + if (map_addr_to_serpar_number(&msg[0]) == 0) { /* else = Tuner regs to ignore : DIG_CFG, CTRL_RF_LT, PLL_CFG, PWM1_REG, ADCCLK, DIG_CFG_3; SLEEP_EN... */ + if (num == 1) { /* write */ + return w7090p_tuner_write_serpar(i2c_adap, msg, 1); + } else { /* read */ + return w7090p_tuner_read_serpar(i2c_adap, msg, 2); + } + } + return num; +} + +int dib7090p_rw_on_apb(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num, u16 apb_address) +{ + struct dib7000p_state *state = i2c_get_adapdata(i2c_adap); + u16 word; + + if (num == 1) { /* write */ + dib7000p_write_word(state, apb_address, ((msg[0].buf[1] << 8) | (msg[0].buf[2]))); + } else { + word = dib7000p_read_word(state, apb_address); + msg[1].buf[0] = (word >> 8) & 0xff; + msg[1].buf[1] = (word) & 0xff; + } + + return num; +} + +static int dib7090_tuner_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + struct dib7000p_state *state = i2c_get_adapdata(i2c_adap); + + u16 apb_address = 0, word; + int i = 0; + switch (msg[0].buf[0]) { + case 0x12: + apb_address = 1920; + break; + case 0x14: + apb_address = 1921; + break; + case 0x24: + apb_address = 1922; + break; + case 0x1a: + apb_address = 1923; + break; + case 0x22: + apb_address = 1924; + break; + case 0x33: + apb_address = 1926; + break; + case 0x34: + apb_address = 1927; + break; + case 0x35: + apb_address = 1928; + break; + case 0x36: + apb_address = 1929; + break; + case 0x37: + apb_address = 1930; + break; + case 0x38: + apb_address = 1931; + break; + case 0x39: + apb_address = 1932; + break; + case 0x2a: + apb_address = 1935; + break; + case 0x2b: + apb_address = 1936; + break; + case 0x2c: + apb_address = 1937; + break; + case 0x2d: + apb_address = 1938; + break; + case 0x2e: + apb_address = 1939; + break; + case 0x2f: + apb_address = 1940; + break; + case 0x30: + apb_address = 1941; + break; + case 0x31: + apb_address = 1942; + break; + case 0x32: + apb_address = 1943; + break; + case 0x3e: + apb_address = 1944; + break; + case 0x3f: + apb_address = 1945; + break; + case 0x40: + apb_address = 1948; + break; + case 0x25: + apb_address = 914; + break; + case 0x26: + apb_address = 915; + break; + case 0x27: + apb_address = 916; + break; + case 0x28: + apb_address = 917; + break; + case 0x1d: + i = ((dib7000p_read_word(state, 72) >> 12) & 0x3); + word = dib7000p_read_word(state, 384 + i); + msg[1].buf[0] = (word >> 8) & 0xff; + msg[1].buf[1] = (word) & 0xff; + return num; + case 0x1f: + if (num == 1) { /* write */ + word = (u16) ((msg[0].buf[1] << 8) | msg[0].buf[2]); + word &= 0x3; + word = (dib7000p_read_word(state, 72) & ~(3 << 12)) | (word << 12); + dib7000p_write_word(state, 72, word); /* Set the proper input */ + return num; + } + } + + if (apb_address != 0) /* R/W acces via APB */ + return dib7090p_rw_on_apb(i2c_adap, msg, num, apb_address); + else /* R/W access via SERPAR */ + return w7090p_tuner_rw_serpar(i2c_adap, msg, num); + + return 0; +} + +static u32 dib7000p_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm dib7090_tuner_xfer_algo = { + .master_xfer = dib7090_tuner_xfer, + .functionality = dib7000p_i2c_func, +}; + +struct i2c_adapter *dib7090_get_i2c_tuner(struct dvb_frontend *fe) +{ + struct dib7000p_state *st = fe->demodulator_priv; + return &st->dib7090_tuner_adap; +} +EXPORT_SYMBOL(dib7090_get_i2c_tuner); + +static int dib7090_host_bus_drive(struct dib7000p_state *state, u8 drive) +{ + u16 reg; + + /* drive host bus 2, 3, 4 */ + reg = dib7000p_read_word(state, 1798) & ~((0x7) | (0x7 << 6) | (0x7 << 12)); + reg |= (drive << 12) | (drive << 6) | drive; + dib7000p_write_word(state, 1798, reg); + + /* drive host bus 5,6 */ + reg = dib7000p_read_word(state, 1799) & ~((0x7 << 2) | (0x7 << 8)); + reg |= (drive << 8) | (drive << 2); + dib7000p_write_word(state, 1799, reg); + + /* drive host bus 7, 8, 9 */ + reg = dib7000p_read_word(state, 1800) & ~((0x7) | (0x7 << 6) | (0x7 << 12)); + reg |= (drive << 12) | (drive << 6) | drive; + dib7000p_write_word(state, 1800, reg); + + /* drive host bus 10, 11 */ + reg = dib7000p_read_word(state, 1801) & ~((0x7 << 2) | (0x7 << 8)); + reg |= (drive << 8) | (drive << 2); + dib7000p_write_word(state, 1801, reg); + + /* drive host bus 12, 13, 14 */ + reg = dib7000p_read_word(state, 1802) & ~((0x7) | (0x7 << 6) | (0x7 << 12)); + reg |= (drive << 12) | (drive << 6) | drive; + dib7000p_write_word(state, 1802, reg); + + return 0; +} + +static u32 dib7090_calcSyncFreq(u32 P_Kin, u32 P_Kout, u32 insertExtSynchro, u32 syncSize) +{ + u32 quantif = 3; + u32 nom = (insertExtSynchro * P_Kin + syncSize); + u32 denom = P_Kout; + u32 syncFreq = ((nom << quantif) / denom); + + if ((syncFreq & ((1 << quantif) - 1)) != 0) + syncFreq = (syncFreq >> quantif) + 1; + else + syncFreq = (syncFreq >> quantif); + + if (syncFreq != 0) + syncFreq = syncFreq - 1; + + return syncFreq; +} + +static int dib7090_cfg_DibTx(struct dib7000p_state *state, u32 P_Kin, u32 P_Kout, u32 insertExtSynchro, u32 synchroMode, u32 syncWord, u32 syncSize) +{ + u8 index_buf; + u16 rx_copy_buf[22]; + + dprintk("Configure DibStream Tx"); + for (index_buf = 0; index_buf < 22; index_buf++) + rx_copy_buf[index_buf] = dib7000p_read_word(state, 1536+index_buf); + + dib7000p_write_word(state, 1615, 1); + dib7000p_write_word(state, 1603, P_Kin); + dib7000p_write_word(state, 1605, P_Kout); + dib7000p_write_word(state, 1606, insertExtSynchro); + dib7000p_write_word(state, 1608, synchroMode); + dib7000p_write_word(state, 1609, (syncWord >> 16) & 0xffff); + dib7000p_write_word(state, 1610, syncWord & 0xffff); + dib7000p_write_word(state, 1612, syncSize); + dib7000p_write_word(state, 1615, 0); + + for (index_buf = 0; index_buf < 22; index_buf++) + dib7000p_write_word(state, 1536+index_buf, rx_copy_buf[index_buf]); + + return 0; +} + +static int dib7090_cfg_DibRx(struct dib7000p_state *state, u32 P_Kin, u32 P_Kout, u32 synchroMode, u32 insertExtSynchro, u32 syncWord, u32 syncSize, + u32 dataOutRate) +{ + u32 syncFreq; + + dprintk("Configure DibStream Rx"); + if ((P_Kin != 0) && (P_Kout != 0)) { + syncFreq = dib7090_calcSyncFreq(P_Kin, P_Kout, insertExtSynchro, syncSize); + dib7000p_write_word(state, 1542, syncFreq); + } + dib7000p_write_word(state, 1554, 1); + dib7000p_write_word(state, 1536, P_Kin); + dib7000p_write_word(state, 1537, P_Kout); + dib7000p_write_word(state, 1539, synchroMode); + dib7000p_write_word(state, 1540, (syncWord >> 16) & 0xffff); + dib7000p_write_word(state, 1541, syncWord & 0xffff); + dib7000p_write_word(state, 1543, syncSize); + dib7000p_write_word(state, 1544, dataOutRate); + dib7000p_write_word(state, 1554, 0); + + return 0; +} + +static int dib7090_enDivOnHostBus(struct dib7000p_state *state) +{ + u16 reg; + + dprintk("Enable Diversity on host bus"); + reg = (1 << 8) | (1 << 5); + dib7000p_write_word(state, 1288, reg); + + return dib7090_cfg_DibTx(state, 5, 5, 0, 0, 0, 0); +} + +static int dib7090_enAdcOnHostBus(struct dib7000p_state *state) +{ + u16 reg; + + dprintk("Enable ADC on host bus"); + reg = (1 << 7) | (1 << 5); + dib7000p_write_word(state, 1288, reg); + + return dib7090_cfg_DibTx(state, 20, 5, 10, 0, 0, 0); +} + +static int dib7090_enMpegOnHostBus(struct dib7000p_state *state) +{ + u16 reg; + + dprintk("Enable Mpeg on host bus"); + reg = (1 << 9) | (1 << 5); + dib7000p_write_word(state, 1288, reg); + + return dib7090_cfg_DibTx(state, 8, 5, 0, 0, 0, 0); +} + +static int dib7090_enMpegInput(struct dib7000p_state *state) +{ + dprintk("Enable Mpeg input"); + return dib7090_cfg_DibRx(state, 8, 5, 0, 0, 0, 8, 0); /*outputRate = 8 */ +} + +static int dib7090_enMpegMux(struct dib7000p_state *state, u16 pulseWidth, u16 enSerialMode, u16 enSerialClkDiv2) +{ + u16 reg = (1 << 7) | ((pulseWidth & 0x1f) << 2) | ((enSerialMode & 0x1) << 1) | (enSerialClkDiv2 & 0x1); + + dprintk("Enable Mpeg mux"); + dib7000p_write_word(state, 1287, reg); + + reg &= ~(1 << 7); + dib7000p_write_word(state, 1287, reg); + + reg = (1 << 4); + dib7000p_write_word(state, 1288, reg); + + return 0; +} + +static int dib7090_disableMpegMux(struct dib7000p_state *state) +{ + u16 reg; + + dprintk("Disable Mpeg mux"); + dib7000p_write_word(state, 1288, 0); + + reg = dib7000p_read_word(state, 1287); + reg &= ~(1 << 7); + dib7000p_write_word(state, 1287, reg); + + return 0; +} + +static int dib7090_set_input_mode(struct dvb_frontend *fe, int mode) +{ + struct dib7000p_state *state = fe->demodulator_priv; + + switch (mode) { + case INPUT_MODE_DIVERSITY: + dprintk("Enable diversity INPUT"); + dib7090_cfg_DibRx(state, 5, 5, 0, 0, 0, 0, 0); + break; + case INPUT_MODE_MPEG: + dprintk("Enable Mpeg INPUT"); + dib7090_cfg_DibRx(state, 8, 5, 0, 0, 0, 8, 0); /*outputRate = 8 */ + break; + case INPUT_MODE_OFF: + default: + dprintk("Disable INPUT"); + dib7090_cfg_DibRx(state, 0, 0, 0, 0, 0, 0, 0); + break; + } + return 0; +} + +static int dib7090_set_diversity_in(struct dvb_frontend *fe, int onoff) +{ + switch (onoff) { + case 0: /* only use the internal way - not the diversity input */ + dib7090_set_input_mode(fe, INPUT_MODE_MPEG); + break; + case 1: /* both ways */ + case 2: /* only the diversity input */ + dib7090_set_input_mode(fe, INPUT_MODE_DIVERSITY); + break; + } + + return 0; +} + +static int dib7090_set_output_mode(struct dvb_frontend *fe, int mode) +{ + struct dib7000p_state *state = fe->demodulator_priv; + + u16 outreg, smo_mode, fifo_threshold; + u8 prefer_mpeg_mux_use = 1; + int ret = 0; + + dib7090_host_bus_drive(state, 1); + + fifo_threshold = 1792; + smo_mode = (dib7000p_read_word(state, 235) & 0x0050) | (1 << 1); + outreg = dib7000p_read_word(state, 1286) & ~((1 << 10) | (0x7 << 6) | (1 << 1)); + + switch (mode) { + case OUTMODE_HIGH_Z: + outreg = 0; + break; + + case OUTMODE_MPEG2_SERIAL: + if (prefer_mpeg_mux_use) { + dprintk("Sip 7090P setting output mode TS_SERIAL using Mpeg Mux"); + dib7090_enMpegOnHostBus(state); + dib7090_enMpegInput(state); + if (state->cfg.enMpegOutput == 1) + dib7090_enMpegMux(state, 3, 1, 1); + + } else { /* Use Smooth block */ + dprintk("Sip 7090P setting output mode TS_SERIAL using Smooth bloc"); + dib7090_disableMpegMux(state); + dib7000p_write_word(state, 1288, (1 << 6)); + outreg |= (2 << 6) | (0 << 1); + } + break; + + case OUTMODE_MPEG2_PAR_GATED_CLK: + if (prefer_mpeg_mux_use) { + dprintk("Sip 7090P setting output mode TS_PARALLEL_GATED using Mpeg Mux"); + dib7090_enMpegOnHostBus(state); + dib7090_enMpegInput(state); + if (state->cfg.enMpegOutput == 1) + dib7090_enMpegMux(state, 2, 0, 0); + } else { /* Use Smooth block */ + dprintk("Sip 7090P setting output mode TS_PARALLEL_GATED using Smooth block"); + dib7090_disableMpegMux(state); + dib7000p_write_word(state, 1288, (1 << 6)); + outreg |= (0 << 6); + } + break; + + case OUTMODE_MPEG2_PAR_CONT_CLK: /* Using Smooth block only */ + dprintk("Sip 7090P setting output mode TS_PARALLEL_CONT using Smooth block"); + dib7090_disableMpegMux(state); + dib7000p_write_word(state, 1288, (1 << 6)); + outreg |= (1 << 6); + break; + + case OUTMODE_MPEG2_FIFO: /* Using Smooth block because not supported by new Mpeg Mux bloc */ + dprintk("Sip 7090P setting output mode TS_FIFO using Smooth block"); + dib7090_disableMpegMux(state); + dib7000p_write_word(state, 1288, (1 << 6)); + outreg |= (5 << 6); + smo_mode |= (3 << 1); + fifo_threshold = 512; + break; + + case OUTMODE_DIVERSITY: + dprintk("Sip 7090P setting output mode MODE_DIVERSITY"); + dib7090_disableMpegMux(state); + dib7090_enDivOnHostBus(state); + break; + + case OUTMODE_ANALOG_ADC: + dprintk("Sip 7090P setting output mode MODE_ANALOG_ADC"); + dib7090_enAdcOnHostBus(state); + break; + } + + if (state->cfg.output_mpeg2_in_188_bytes) + smo_mode |= (1 << 5); + + ret |= dib7000p_write_word(state, 235, smo_mode); + ret |= dib7000p_write_word(state, 236, fifo_threshold); /* synchronous fread */ + ret |= dib7000p_write_word(state, 1286, outreg | (1 << 10)); /* allways set Dout active = 1 !!! */ + + return ret; +} + +int dib7090_tuner_sleep(struct dvb_frontend *fe, int onoff) +{ + struct dib7000p_state *state = fe->demodulator_priv; + u16 en_cur_state; + + dprintk("sleep dib7090: %d", onoff); + + en_cur_state = dib7000p_read_word(state, 1922); + + if (en_cur_state > 0xff) + state->tuner_enable = en_cur_state; + + if (onoff) + en_cur_state &= 0x00ff; + else { + if (state->tuner_enable != 0) + en_cur_state = state->tuner_enable; + } + + dib7000p_write_word(state, 1922, en_cur_state); + + return 0; +} +EXPORT_SYMBOL(dib7090_tuner_sleep); + +int dib7090_agc_restart(struct dvb_frontend *fe, u8 restart) +{ + dprintk("AGC restart callback: %d", restart); + return 0; +} +EXPORT_SYMBOL(dib7090_agc_restart); + +int dib7090_get_adc_power(struct dvb_frontend *fe) +{ + return dib7000p_get_adc_power(fe); +} +EXPORT_SYMBOL(dib7090_get_adc_power); + +int dib7090_slave_reset(struct dvb_frontend *fe) +{ + struct dib7000p_state *state = fe->demodulator_priv; + u16 reg; + + reg = dib7000p_read_word(state, 1794); + dib7000p_write_word(state, 1794, reg | (4 << 12)); + + dib7000p_write_word(state, 1032, 0xffff); + return 0; +} +EXPORT_SYMBOL(dib7090_slave_reset); + static struct dvb_frontend_ops dib7000p_ops; -struct dvb_frontend * dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib7000p_config *cfg) +struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib7000p_config *cfg) { struct dvb_frontend *demod; struct dib7000p_state *st; @@ -1400,28 +2286,41 @@ struct dvb_frontend * dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, /* Ensure the output mode remains at the previous default if it's * not specifically set by the caller. */ - if ((st->cfg.output_mode != OUTMODE_MPEG2_SERIAL) && - (st->cfg.output_mode != OUTMODE_MPEG2_PAR_GATED_CLK)) + if ((st->cfg.output_mode != OUTMODE_MPEG2_SERIAL) && (st->cfg.output_mode != OUTMODE_MPEG2_PAR_GATED_CLK)) st->cfg.output_mode = OUTMODE_MPEG2_FIFO; - demod = &st->demod; + demod = &st->demod; demod->demodulator_priv = st; memcpy(&st->demod.ops, &dib7000p_ops, sizeof(struct dvb_frontend_ops)); - dib7000p_write_word(st, 1287, 0x0003); /* sram lead in, rdy */ + dib7000p_write_word(st, 1287, 0x0003); /* sram lead in, rdy */ if (dib7000p_identify(st) != 0) goto error; + st->version = dib7000p_read_word(st, 897); + /* FIXME: make sure the dev.parent field is initialized, or else - request_firmware() will hit an OOPS (this should be moved somewhere - more common) */ - st->i2c_master.gated_tuner_i2c_adap.dev.parent = i2c_adap->dev.parent; + request_firmware() will hit an OOPS (this should be moved somewhere + more common) */ dibx000_init_i2c_master(&st->i2c_master, DIB7000P, st->i2c_adap, st->i2c_addr); + /* init 7090 tuner adapter */ + strncpy(st->dib7090_tuner_adap.name, "DiB7090 tuner interface", sizeof(st->dib7090_tuner_adap.name)); + st->dib7090_tuner_adap.algo = &dib7090_tuner_xfer_algo; + st->dib7090_tuner_adap.algo_data = NULL; + st->dib7090_tuner_adap.dev.parent = st->i2c_adap->dev.parent; + i2c_set_adapdata(&st->dib7090_tuner_adap, st); + i2c_add_adapter(&st->dib7090_tuner_adap); + dib7000p_demod_reset(st); + if (st->version == SOC7090) { + dib7090_set_output_mode(demod, st->cfg.output_mode); + dib7090_set_diversity_in(demod, 0); + } + return demod; error: @@ -1432,37 +2331,35 @@ EXPORT_SYMBOL(dib7000p_attach); static struct dvb_frontend_ops dib7000p_ops = { .info = { - .name = "DiBcom 7000PC", - .type = FE_OFDM, - .frequency_min = 44250000, - .frequency_max = 867250000, - .frequency_stepsize = 62500, - .caps = FE_CAN_INVERSION_AUTO | - FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | - FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | - FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | - FE_CAN_TRANSMISSION_MODE_AUTO | - FE_CAN_GUARD_INTERVAL_AUTO | - FE_CAN_RECOVER | - FE_CAN_HIERARCHY_AUTO, - }, - - .release = dib7000p_release, - - .init = dib7000p_wakeup, - .sleep = dib7000p_sleep, - - .set_frontend = dib7000p_set_frontend, - .get_tune_settings = dib7000p_fe_get_tune_settings, - .get_frontend = dib7000p_get_frontend, - - .read_status = dib7000p_read_status, - .read_ber = dib7000p_read_ber, + .name = "DiBcom 7000PC", + .type = FE_OFDM, + .frequency_min = 44250000, + .frequency_max = 867250000, + .frequency_stepsize = 62500, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_RECOVER | FE_CAN_HIERARCHY_AUTO, + }, + + .release = dib7000p_release, + + .init = dib7000p_wakeup, + .sleep = dib7000p_sleep, + + .set_frontend = dib7000p_set_frontend, + .get_tune_settings = dib7000p_fe_get_tune_settings, + .get_frontend = dib7000p_get_frontend, + + .read_status = dib7000p_read_status, + .read_ber = dib7000p_read_ber, .read_signal_strength = dib7000p_read_signal_strength, - .read_snr = dib7000p_read_snr, - .read_ucblocks = dib7000p_read_unc_blocks, + .read_snr = dib7000p_read_snr, + .read_ucblocks = dib7000p_read_unc_blocks, }; +MODULE_AUTHOR("Olivier Grenie <ogrenie@dibcom.fr>"); MODULE_AUTHOR("Patrick Boettcher <pboettcher@dibcom.fr>"); MODULE_DESCRIPTION("Driver for the DiBcom 7000PC COFDM demodulator"); MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/dib7000p.h b/drivers/media/dvb/frontends/dib7000p.h index da17345bf5bd..0179f9474bac 100644 --- a/drivers/media/dvb/frontends/dib7000p.h +++ b/drivers/media/dvb/frontends/dib7000p.h @@ -33,59 +33,54 @@ struct dib7000p_config { int (*agc_control) (struct dvb_frontend *, u8 before); u8 output_mode; - u8 disable_sample_and_hold : 1; + u8 disable_sample_and_hold:1; - u8 enable_current_mirror : 1; - u8 diversity_delay; + u8 enable_current_mirror:1; + u16 diversity_delay; + u8 default_i2c_addr; + u8 enMpegOutput:1; }; #define DEFAULT_DIB7000P_I2C_ADDRESS 18 #if defined(CONFIG_DVB_DIB7000P) || (defined(CONFIG_DVB_DIB7000P_MODULE) && \ - defined(MODULE)) -extern struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, - u8 i2c_addr, - struct dib7000p_config *cfg); -extern struct i2c_adapter *dib7000p_get_i2c_master(struct dvb_frontend *, - enum dibx000_i2c_interface, - int); -extern int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, - int no_of_demods, u8 default_addr, - struct dib7000p_config cfg[]); + defined(MODULE)) +extern struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib7000p_config *cfg); +extern struct i2c_adapter *dib7000p_get_i2c_master(struct dvb_frontend *, enum dibx000_i2c_interface, int); +extern int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 default_addr, struct dib7000p_config cfg[]); extern int dib7000p_set_gpio(struct dvb_frontend *, u8 num, u8 dir, u8 val); extern int dib7000p_set_wbd_ref(struct dvb_frontend *, u16 value); extern int dib7000pc_detection(struct i2c_adapter *i2c_adap); extern int dib7000p_pid_filter(struct dvb_frontend *, u8 id, u16 pid, u8 onoff); extern int dib7000p_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff); +extern int dib7000p_update_pll(struct dvb_frontend *fe, struct dibx000_bandwidth_config *bw); +extern u32 dib7000p_ctrl_timf(struct dvb_frontend *fe, u8 op, u32 timf); +extern int dib7090_agc_restart(struct dvb_frontend *fe, u8 restart); +extern int dib7090_tuner_sleep(struct dvb_frontend *fe, int onoff); +extern int dib7090_get_adc_power(struct dvb_frontend *fe); +extern struct i2c_adapter *dib7090_get_i2c_tuner(struct dvb_frontend *fe); +extern int dib7090_slave_reset(struct dvb_frontend *fe); #else -static inline -struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, - struct dib7000p_config *cfg) +static inline struct dvb_frontend *dib7000p_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib7000p_config *cfg) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; } -static inline -struct i2c_adapter *dib7000p_get_i2c_master(struct dvb_frontend *fe, - enum dibx000_i2c_interface i, - int x) +static inline struct i2c_adapter *dib7000p_get_i2c_master(struct dvb_frontend *fe, enum dibx000_i2c_interface i, int x) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; } -static inline int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, - int no_of_demods, u8 default_addr, - struct dib7000p_config cfg[]) +static inline int dib7000p_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 default_addr, struct dib7000p_config cfg[]) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return -ENODEV; } -static inline int dib7000p_set_gpio(struct dvb_frontend *fe, - u8 num, u8 dir, u8 val) +static inline int dib7000p_set_gpio(struct dvb_frontend *fe, u8 num, u8 dir, u8 val) { printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return -ENODEV; @@ -102,16 +97,59 @@ static inline int dib7000pc_detection(struct i2c_adapter *i2c_adap) printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return -ENODEV; } + static inline int dib7000p_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff) { - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return -ENODEV; + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; } static inline int dib7000p_pid_filter_ctrl(struct dvb_frontend *fe, uint8_t onoff) { - printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); - return -ENODEV; + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib7000p_update_pll(struct dvb_frontend *fe, struct dibx000_bandwidth_config *bw) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline u32 dib7000p_ctrl_timf(struct dvb_frontend *fe, u8 op, u32 timf) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return 0; +} + +static inline int dib7090_agc_restart(struct dvb_frontend *fe, u8 restart) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib7090_tuner_sleep(struct dvb_frontend *fe, int onoff) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib7090_get_adc_power(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline struct i2c_adapter *dib7090_get_i2c_tuner(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline int dib7090_slave_reset(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; } #endif diff --git a/drivers/media/dvb/frontends/dib8000.c b/drivers/media/dvb/frontends/dib8000.c index df17b91b3250..3e20aa8db23b 100644 --- a/drivers/media/dvb/frontends/dib8000.c +++ b/drivers/media/dvb/frontends/dib8000.c @@ -22,6 +22,7 @@ #define LAYER_C 3 #define FE_CALLBACK_TIME_NEVER 0xffffffff +#define MAX_NUMBER_OF_FRONTENDS 6 static int debug; module_param(debug, int, 0644); @@ -37,7 +38,6 @@ struct i2c_device { }; struct dib8000_state { - struct dvb_frontend fe; struct dib8000_config cfg; struct i2c_device i2c; @@ -68,6 +68,8 @@ struct dib8000_state { u8 isdbt_cfg_loaded; enum frontend_tune_state tune_state; u32 status; + + struct dvb_frontend *fe[MAX_NUMBER_OF_FRONTENDS]; }; enum dib8000_power_mode { @@ -122,111 +124,111 @@ static int dib8000_write_word(struct dib8000_state *state, u16 reg, u16 val) return dib8000_i2c_write16(&state->i2c, reg, val); } -static const int16_t coeff_2k_sb_1seg_dqpsk[8] = { +static const s16 coeff_2k_sb_1seg_dqpsk[8] = { (769 << 5) | 0x0a, (745 << 5) | 0x03, (595 << 5) | 0x0d, (769 << 5) | 0x0a, (920 << 5) | 0x09, (784 << 5) | 0x02, (519 << 5) | 0x0c, - (920 << 5) | 0x09 + (920 << 5) | 0x09 }; -static const int16_t coeff_2k_sb_1seg[8] = { +static const s16 coeff_2k_sb_1seg[8] = { (692 << 5) | 0x0b, (683 << 5) | 0x01, (519 << 5) | 0x09, (692 << 5) | 0x0b, 0 | 0x1f, 0 | 0x1f, 0 | 0x1f, 0 | 0x1f }; -static const int16_t coeff_2k_sb_3seg_0dqpsk_1dqpsk[8] = { +static const s16 coeff_2k_sb_3seg_0dqpsk_1dqpsk[8] = { (832 << 5) | 0x10, (912 << 5) | 0x05, (900 << 5) | 0x12, (832 << 5) | 0x10, (-931 << 5) | 0x0f, (912 << 5) | 0x04, (807 << 5) | 0x11, - (-931 << 5) | 0x0f + (-931 << 5) | 0x0f }; -static const int16_t coeff_2k_sb_3seg_0dqpsk[8] = { +static const s16 coeff_2k_sb_3seg_0dqpsk[8] = { (622 << 5) | 0x0c, (941 << 5) | 0x04, (796 << 5) | 0x10, (622 << 5) | 0x0c, (982 << 5) | 0x0c, (519 << 5) | 0x02, (572 << 5) | 0x0e, - (982 << 5) | 0x0c + (982 << 5) | 0x0c }; -static const int16_t coeff_2k_sb_3seg_1dqpsk[8] = { +static const s16 coeff_2k_sb_3seg_1dqpsk[8] = { (699 << 5) | 0x14, (607 << 5) | 0x04, (944 << 5) | 0x13, (699 << 5) | 0x14, (-720 << 5) | 0x0d, (640 << 5) | 0x03, (866 << 5) | 0x12, - (-720 << 5) | 0x0d + (-720 << 5) | 0x0d }; -static const int16_t coeff_2k_sb_3seg[8] = { +static const s16 coeff_2k_sb_3seg[8] = { (664 << 5) | 0x0c, (925 << 5) | 0x03, (937 << 5) | 0x10, (664 << 5) | 0x0c, (-610 << 5) | 0x0a, (697 << 5) | 0x01, (836 << 5) | 0x0e, - (-610 << 5) | 0x0a + (-610 << 5) | 0x0a }; -static const int16_t coeff_4k_sb_1seg_dqpsk[8] = { +static const s16 coeff_4k_sb_1seg_dqpsk[8] = { (-955 << 5) | 0x0e, (687 << 5) | 0x04, (818 << 5) | 0x10, (-955 << 5) | 0x0e, (-922 << 5) | 0x0d, (750 << 5) | 0x03, (665 << 5) | 0x0f, - (-922 << 5) | 0x0d + (-922 << 5) | 0x0d }; -static const int16_t coeff_4k_sb_1seg[8] = { +static const s16 coeff_4k_sb_1seg[8] = { (638 << 5) | 0x0d, (683 << 5) | 0x02, (638 << 5) | 0x0d, (638 << 5) | 0x0d, (-655 << 5) | 0x0a, (517 << 5) | 0x00, (698 << 5) | 0x0d, - (-655 << 5) | 0x0a + (-655 << 5) | 0x0a }; -static const int16_t coeff_4k_sb_3seg_0dqpsk_1dqpsk[8] = { +static const s16 coeff_4k_sb_3seg_0dqpsk_1dqpsk[8] = { (-707 << 5) | 0x14, (910 << 5) | 0x06, (889 << 5) | 0x16, (-707 << 5) | 0x14, (-958 << 5) | 0x13, (993 << 5) | 0x05, (523 << 5) | 0x14, - (-958 << 5) | 0x13 + (-958 << 5) | 0x13 }; -static const int16_t coeff_4k_sb_3seg_0dqpsk[8] = { +static const s16 coeff_4k_sb_3seg_0dqpsk[8] = { (-723 << 5) | 0x13, (910 << 5) | 0x05, (777 << 5) | 0x14, (-723 << 5) | 0x13, (-568 << 5) | 0x0f, (547 << 5) | 0x03, (696 << 5) | 0x12, - (-568 << 5) | 0x0f + (-568 << 5) | 0x0f }; -static const int16_t coeff_4k_sb_3seg_1dqpsk[8] = { +static const s16 coeff_4k_sb_3seg_1dqpsk[8] = { (-940 << 5) | 0x15, (607 << 5) | 0x05, (915 << 5) | 0x16, (-940 << 5) | 0x15, (-848 << 5) | 0x13, (683 << 5) | 0x04, (543 << 5) | 0x14, - (-848 << 5) | 0x13 + (-848 << 5) | 0x13 }; -static const int16_t coeff_4k_sb_3seg[8] = { +static const s16 coeff_4k_sb_3seg[8] = { (612 << 5) | 0x12, (910 << 5) | 0x04, (864 << 5) | 0x14, (612 << 5) | 0x12, (-869 << 5) | 0x13, (683 << 5) | 0x02, (869 << 5) | 0x12, - (-869 << 5) | 0x13 + (-869 << 5) | 0x13 }; -static const int16_t coeff_8k_sb_1seg_dqpsk[8] = { +static const s16 coeff_8k_sb_1seg_dqpsk[8] = { (-835 << 5) | 0x12, (684 << 5) | 0x05, (735 << 5) | 0x14, (-835 << 5) | 0x12, (-598 << 5) | 0x10, (781 << 5) | 0x04, (739 << 5) | 0x13, - (-598 << 5) | 0x10 + (-598 << 5) | 0x10 }; -static const int16_t coeff_8k_sb_1seg[8] = { +static const s16 coeff_8k_sb_1seg[8] = { (673 << 5) | 0x0f, (683 << 5) | 0x03, (808 << 5) | 0x12, (673 << 5) | 0x0f, (585 << 5) | 0x0f, (512 << 5) | 0x01, (780 << 5) | 0x0f, - (585 << 5) | 0x0f + (585 << 5) | 0x0f }; -static const int16_t coeff_8k_sb_3seg_0dqpsk_1dqpsk[8] = { +static const s16 coeff_8k_sb_3seg_0dqpsk_1dqpsk[8] = { (863 << 5) | 0x17, (930 << 5) | 0x07, (878 << 5) | 0x19, (863 << 5) | 0x17, (0 << 5) | 0x14, (521 << 5) | 0x05, (980 << 5) | 0x18, - (0 << 5) | 0x14 + (0 << 5) | 0x14 }; -static const int16_t coeff_8k_sb_3seg_0dqpsk[8] = { +static const s16 coeff_8k_sb_3seg_0dqpsk[8] = { (-924 << 5) | 0x17, (910 << 5) | 0x06, (774 << 5) | 0x17, (-924 << 5) | 0x17, (-877 << 5) | 0x15, (565 << 5) | 0x04, (553 << 5) | 0x15, - (-877 << 5) | 0x15 + (-877 << 5) | 0x15 }; -static const int16_t coeff_8k_sb_3seg_1dqpsk[8] = { +static const s16 coeff_8k_sb_3seg_1dqpsk[8] = { (-921 << 5) | 0x19, (607 << 5) | 0x06, (881 << 5) | 0x19, (-921 << 5) | 0x19, (-921 << 5) | 0x14, (713 << 5) | 0x05, (1018 << 5) | 0x18, - (-921 << 5) | 0x14 + (-921 << 5) | 0x14 }; -static const int16_t coeff_8k_sb_3seg[8] = { +static const s16 coeff_8k_sb_3seg[8] = { (514 << 5) | 0x14, (910 << 5) | 0x05, (861 << 5) | 0x17, (514 << 5) | 0x14, (690 << 5) | 0x14, (683 << 5) | 0x03, (662 << 5) | 0x15, - (690 << 5) | 0x14 + (690 << 5) | 0x14 }; -static const int16_t ana_fe_coeff_3seg[24] = { +static const s16 ana_fe_coeff_3seg[24] = { 81, 80, 78, 74, 68, 61, 54, 45, 37, 28, 19, 11, 4, 1022, 1017, 1013, 1010, 1008, 1008, 1008, 1008, 1010, 1014, 1017 }; -static const int16_t ana_fe_coeff_1seg[24] = { +static const s16 ana_fe_coeff_1seg[24] = { 249, 226, 164, 82, 5, 981, 970, 988, 1018, 20, 31, 26, 8, 1012, 1000, 1018, 1012, 8, 15, 14, 9, 3, 1017, 1003 }; -static const int16_t ana_fe_coeff_13seg[24] = { +static const s16 ana_fe_coeff_13seg[24] = { 396, 305, 105, -51, -77, -12, 41, 31, -11, -30, -11, 14, 15, -2, -13, -7, 5, 8, 1, -6, -7, -3, 0, 1 }; static u16 fft_to_mode(struct dib8000_state *state) { u16 mode; - switch (state->fe.dtv_property_cache.transmission_mode) { + switch (state->fe[0]->dtv_property_cache.transmission_mode) { case TRANSMISSION_MODE_2K: mode = 1; break; @@ -249,16 +251,18 @@ static void dib8000_set_acquisition_mode(struct dib8000_state *state) dprintk("acquisition mode activated"); dib8000_write_word(state, 298, nud); } - -static int dib8000_set_output_mode(struct dib8000_state *state, int mode) +static int dib8000_set_output_mode(struct dvb_frontend *fe, int mode) { + struct dib8000_state *state = fe->demodulator_priv; + u16 outreg, fifo_threshold, smo_mode, sram = 0x0205; /* by default SDRAM deintlv is enabled */ outreg = 0; fifo_threshold = 1792; smo_mode = (dib8000_read_word(state, 299) & 0x0050) | (1 << 1); - dprintk("-I- Setting output mode for demod %p to %d", &state->fe, mode); + dprintk("-I- Setting output mode for demod %p to %d", + &state->fe[0], mode); switch (mode) { case OUTMODE_MPEG2_PAR_GATED_CLK: // STBs with parallel gated clock @@ -292,7 +296,8 @@ static int dib8000_set_output_mode(struct dib8000_state *state, int mode) break; default: - dprintk("Unhandled output_mode passed to be set for demod %p", &state->fe); + dprintk("Unhandled output_mode passed to be set for demod %p", + &state->fe[0]); return -EINVAL; } @@ -342,7 +347,8 @@ static void dib8000_set_power_mode(struct dib8000_state *state, enum dib8000_pow { /* by default everything is going to be powered off */ u16 reg_774 = 0x3fff, reg_775 = 0xffff, reg_776 = 0xffff, - reg_900 = (dib8000_read_word(state, 900) & 0xfffc) | 0x3, reg_1280 = (dib8000_read_word(state, 1280) & 0x00ff) | 0xff00; + reg_900 = (dib8000_read_word(state, 900) & 0xfffc) | 0x3, + reg_1280 = (dib8000_read_word(state, 1280) & 0x00ff) | 0xff00; /* now, depending on the requested mode, we power on */ switch (mode) { @@ -411,8 +417,9 @@ static int dib8000_set_adc_state(struct dib8000_state *state, enum dibx000_adc_s return ret; } -static int dib8000_set_bandwidth(struct dib8000_state *state, u32 bw) +static int dib8000_set_bandwidth(struct dvb_frontend *fe, u32 bw) { + struct dib8000_state *state = fe->demodulator_priv; u32 timf; if (bw == 0) @@ -478,7 +485,8 @@ static void dib8000_reset_pll(struct dib8000_state *state) // clk_cfg1 clk_cfg1 = (1 << 10) | (0 << 9) | (pll->IO_CLK_en_core << 8) | - (pll->bypclk_div << 5) | (pll->enable_refdiv << 4) | (1 << 3) | (pll->pll_range << 1) | (pll->pll_reset << 0); + (pll->bypclk_div << 5) | (pll->enable_refdiv << 4) | (1 << 3) | + (pll->pll_range << 1) | (pll->pll_reset << 0); dib8000_write_word(state, 902, clk_cfg1); clk_cfg1 = (clk_cfg1 & 0xfff7) | (pll->pll_bypass << 3); @@ -488,11 +496,12 @@ static void dib8000_reset_pll(struct dib8000_state *state) /* smpl_cfg: P_refclksel=2, P_ensmplsel=1 nodivsmpl=1 */ if (state->cfg.pll->ADClkSrc == 0) - dib8000_write_word(state, 904, (0 << 15) | (0 << 12) | (0 << 10) | (pll->modulo << 8) | (pll->ADClkSrc << 7) | (0 << 1)); + dib8000_write_word(state, 904, (0 << 15) | (0 << 12) | (0 << 10) | + (pll->modulo << 8) | (pll->ADClkSrc << 7) | (0 << 1)); else if (state->cfg.refclksel != 0) - dib8000_write_word(state, 904, - (0 << 15) | (1 << 12) | ((state->cfg.refclksel & 0x3) << 10) | (pll->modulo << 8) | (pll-> - ADClkSrc << 7) | (0 << 1)); + dib8000_write_word(state, 904, (0 << 15) | (1 << 12) | + ((state->cfg.refclksel & 0x3) << 10) | (pll->modulo << 8) | + (pll->ADClkSrc << 7) | (0 << 1)); else dib8000_write_word(state, 904, (0 << 15) | (1 << 12) | (3 << 10) | (pll->modulo << 8) | (pll->ADClkSrc << 7) | (0 << 1)); @@ -560,7 +569,7 @@ static const u16 dib8000_defaults[] = { 0xd4c0, /*1, 32, - 0x6680 // P_corm_thres Lock algorithms configuration */ + 0x6680 // P_corm_thres Lock algorithms configuration */ 11, 80, /* set ADC level to -16 */ (1 << 13) - 825 - 117, @@ -623,14 +632,14 @@ static const u16 dib8000_defaults[] = { 1, 285, 0x0020, //p_fec_ 1, 299, - 0x0062, // P_smo_mode, P_smo_rs_discard, P_smo_fifo_flush, P_smo_pid_parse, P_smo_error_discard + 0x0062, /* P_smo_mode, P_smo_rs_discard, P_smo_fifo_flush, P_smo_pid_parse, P_smo_error_discard */ 1, 338, (1 << 12) | // P_ctrl_corm_thres4pre_freq_inh=1 - (1 << 10) | // P_ctrl_pre_freq_mode_sat=1 - (0 << 9) | // P_ctrl_pre_freq_inh=0 - (3 << 5) | // P_ctrl_pre_freq_step=3 - (1 << 0), // P_pre_freq_win_len=1 + (1 << 10) | + (0 << 9) | /* P_ctrl_pre_freq_inh=0 */ + (3 << 5) | /* P_ctrl_pre_freq_step=3 */ + (1 << 0), /* P_pre_freq_win_len=1 */ 1, 903, (0 << 4) | 2, // P_divclksel=0 P_divbitsel=2 (was clk=3,bit=1 for MPW) @@ -717,7 +726,7 @@ static int dib8000_reset(struct dvb_frontend *fe) if (dib8000_reset_gpio(state) != 0) dprintk("GPIO reset was not successful."); - if (dib8000_set_output_mode(state, OUTMODE_HIGH_Z) != 0) + if (dib8000_set_output_mode(fe, OUTMODE_HIGH_Z) != 0) dprintk("OUTPUT_MODE could not be resetted."); state->current_agc = NULL; @@ -752,7 +761,7 @@ static int dib8000_reset(struct dvb_frontend *fe) /* unforce divstr regardless whether i2c enumeration was done or not */ dib8000_write_word(state, 1285, dib8000_read_word(state, 1285) & ~(1 << 1)); - dib8000_set_bandwidth(state, 6000); + dib8000_set_bandwidth(fe, 6000); dib8000_set_adc_state(state, DIBX000_SLOW_ADC_ON); dib8000_sad_calib(state); @@ -778,7 +787,7 @@ static int dib8000_update_lna(struct dib8000_state *state) // read dyn_gain here (because it is demod-dependent and not tuner) dyn_gain = dib8000_read_word(state, 390); - if (state->cfg.update_lna(&state->fe, dyn_gain)) { // LNA has changed + if (state->cfg.update_lna(state->fe[0], dyn_gain)) { dib8000_restart_agc(state); return 1; } @@ -865,7 +874,8 @@ static int dib8000_agc_soft_split(struct dib8000_state *state) split_offset = state->current_agc->split.max; else split_offset = state->current_agc->split.max * - (agc - state->current_agc->split.min_thres) / (state->current_agc->split.max_thres - state->current_agc->split.min_thres); + (agc - state->current_agc->split.min_thres) / + (state->current_agc->split.max_thres - state->current_agc->split.min_thres); dprintk("AGC split_offset: %d", split_offset); @@ -900,7 +910,7 @@ static int dib8000_agc_startup(struct dvb_frontend *fe) case CT_AGC_STEP_0: //AGC initialization if (state->cfg.agc_control) - state->cfg.agc_control(&state->fe, 1); + state->cfg.agc_control(fe, 1); dib8000_restart_agc(state); @@ -924,7 +934,7 @@ static int dib8000_agc_startup(struct dvb_frontend *fe) dib8000_agc_soft_split(state); if (state->cfg.agc_control) - state->cfg.agc_control(&state->fe, 0); + state->cfg.agc_control(fe, 0); *tune_state = CT_AGC_STOP; break; @@ -936,29 +946,28 @@ static int dib8000_agc_startup(struct dvb_frontend *fe) } -static const int32_t lut_1000ln_mant[] = +static const s32 lut_1000ln_mant[] = { 908, 7003, 7090, 7170, 7244, 7313, 7377, 7438, 7495, 7549, 7600 }; -int32_t dib8000_get_adc_power(struct dvb_frontend *fe, uint8_t mode) +s32 dib8000_get_adc_power(struct dvb_frontend *fe, u8 mode) { - struct dib8000_state *state = fe->demodulator_priv; - uint32_t ix = 0, tmp_val = 0, exp = 0, mant = 0; - int32_t val; - - val = dib8000_read32(state, 384); - /* mode = 1 : ln_agcpower calc using mant-exp conversion and mantis look up table */ - if (mode) { - tmp_val = val; - while (tmp_val >>= 1) - exp++; - mant = (val * 1000 / (1<<exp)); - ix = (uint8_t)((mant-1000)/100); /* index of the LUT */ - val = (lut_1000ln_mant[ix] + 693*(exp-20) - 6908); /* 1000 * ln(adcpower_real) ; 693 = 1000ln(2) ; 6908 = 1000*ln(1000) ; 20 comes from adc_real = adc_pow_int / 2**20 */ - val = (val*256)/1000; - } - return val; + struct dib8000_state *state = fe->demodulator_priv; + u32 ix = 0, tmp_val = 0, exp = 0, mant = 0; + s32 val; + + val = dib8000_read32(state, 384); + if (mode) { + tmp_val = val; + while (tmp_val >>= 1) + exp++; + mant = (val * 1000 / (1<<exp)); + ix = (u8)((mant-1000)/100); /* index of the LUT */ + val = (lut_1000ln_mant[ix] + 693*(exp-20) - 6908); + val = (val*256)/1000; + } + return val; } EXPORT_SYMBOL(dib8000_get_adc_power); @@ -1002,22 +1011,23 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 285, dib8000_read_word(state, 285) & 0x60); i = dib8000_read_word(state, 26) & 1; // P_dds_invspec - dib8000_write_word(state, 26, state->fe.dtv_property_cache.inversion ^ i); + dib8000_write_word(state, 26, state->fe[0]->dtv_property_cache.inversion^i); - if (state->fe.dtv_property_cache.isdbt_sb_mode) { + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode) { //compute new dds_freq for the seg and adjust prbs int seg_offset = - state->fe.dtv_property_cache.isdbt_sb_segment_idx - (state->fe.dtv_property_cache.isdbt_sb_segment_count / 2) - - (state->fe.dtv_property_cache.isdbt_sb_segment_count % 2); + state->fe[0]->dtv_property_cache.isdbt_sb_segment_idx - + (state->fe[0]->dtv_property_cache.isdbt_sb_segment_count / 2) - + (state->fe[0]->dtv_property_cache.isdbt_sb_segment_count % 2); int clk = state->cfg.pll->internal; u32 segtodds = ((u32) (430 << 23) / clk) << 3; // segtodds = SegBW / Fclk * pow(2,26) int dds_offset = seg_offset * segtodds; int new_dds, sub_channel; - if ((state->fe.dtv_property_cache.isdbt_sb_segment_count % 2) == 0) // if even + if ((state->fe[0]->dtv_property_cache.isdbt_sb_segment_count % 2) == 0) dds_offset -= (int)(segtodds / 2); if (state->cfg.pll->ifreq == 0) { - if ((state->fe.dtv_property_cache.inversion ^ i) == 0) { + if ((state->fe[0]->dtv_property_cache.inversion ^ i) == 0) { dib8000_write_word(state, 26, dib8000_read_word(state, 26) | 1); new_dds = dds_offset; } else @@ -1027,35 +1037,35 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear // - the segment of center frequency with an odd total number of segments // - the segment to the left of center frequency with an even total number of segments // - the segment to the right of center frequency with an even total number of segments - if ((state->fe.dtv_property_cache.delivery_system == SYS_ISDBT) && (state->fe.dtv_property_cache.isdbt_sb_mode == 1) - && - (((state->fe.dtv_property_cache.isdbt_sb_segment_count % 2) - && (state->fe.dtv_property_cache.isdbt_sb_segment_idx == - ((state->fe.dtv_property_cache.isdbt_sb_segment_count / 2) + 1))) - || (((state->fe.dtv_property_cache.isdbt_sb_segment_count % 2) == 0) - && (state->fe.dtv_property_cache.isdbt_sb_segment_idx == (state->fe.dtv_property_cache.isdbt_sb_segment_count / 2))) - || (((state->fe.dtv_property_cache.isdbt_sb_segment_count % 2) == 0) - && (state->fe.dtv_property_cache.isdbt_sb_segment_idx == - ((state->fe.dtv_property_cache.isdbt_sb_segment_count / 2) + 1))) - )) { + if ((state->fe[0]->dtv_property_cache.delivery_system == SYS_ISDBT) + && (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) + && (((state->fe[0]->dtv_property_cache.isdbt_sb_segment_count % 2) + && (state->fe[0]->dtv_property_cache.isdbt_sb_segment_idx == + ((state->fe[0]->dtv_property_cache.isdbt_sb_segment_count / 2) + 1))) + || (((state->fe[0]->dtv_property_cache.isdbt_sb_segment_count % 2) == 0) + && (state->fe[0]->dtv_property_cache.isdbt_sb_segment_idx == (state->fe[0]->dtv_property_cache.isdbt_sb_segment_count / 2))) + || (((state->fe[0]->dtv_property_cache.isdbt_sb_segment_count % 2) == 0) + && (state->fe[0]->dtv_property_cache.isdbt_sb_segment_idx == + ((state->fe[0]->dtv_property_cache.isdbt_sb_segment_count / 2) + 1))) + )) { new_dds -= ((u32) (850 << 22) / clk) << 4; // new_dds = 850 (freq shift in KHz) / Fclk * pow(2,26) } } else { - if ((state->fe.dtv_property_cache.inversion ^ i) == 0) + if ((state->fe[0]->dtv_property_cache.inversion ^ i) == 0) new_dds = state->cfg.pll->ifreq - dds_offset; else new_dds = state->cfg.pll->ifreq + dds_offset; } dib8000_write_word(state, 27, (u16) ((new_dds >> 16) & 0x01ff)); dib8000_write_word(state, 28, (u16) (new_dds & 0xffff)); - if (state->fe.dtv_property_cache.isdbt_sb_segment_count % 2) // if odd - sub_channel = ((state->fe.dtv_property_cache.isdbt_sb_subchannel + (3 * seg_offset) + 1) % 41) / 3; - else // if even - sub_channel = ((state->fe.dtv_property_cache.isdbt_sb_subchannel + (3 * seg_offset)) % 41) / 3; + if (state->fe[0]->dtv_property_cache.isdbt_sb_segment_count % 2) + sub_channel = ((state->fe[0]->dtv_property_cache.isdbt_sb_subchannel + (3 * seg_offset) + 1) % 41) / 3; + else + sub_channel = ((state->fe[0]->dtv_property_cache.isdbt_sb_subchannel + (3 * seg_offset)) % 41) / 3; sub_channel -= 6; - if (state->fe.dtv_property_cache.transmission_mode == TRANSMISSION_MODE_2K - || state->fe.dtv_property_cache.transmission_mode == TRANSMISSION_MODE_4K) { + if (state->fe[0]->dtv_property_cache.transmission_mode == TRANSMISSION_MODE_2K + || state->fe[0]->dtv_property_cache.transmission_mode == TRANSMISSION_MODE_4K) { dib8000_write_word(state, 219, dib8000_read_word(state, 219) | 0x1); //adp_pass =1 dib8000_write_word(state, 190, dib8000_read_word(state, 190) | (0x1 << 14)); //pha3_force_pha_shift = 1 } else { @@ -1063,7 +1073,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 190, dib8000_read_word(state, 190) & 0xbfff); //pha3_force_pha_shift = 0 } - switch (state->fe.dtv_property_cache.transmission_mode) { + switch (state->fe[0]->dtv_property_cache.transmission_mode) { case TRANSMISSION_MODE_2K: switch (sub_channel) { case -6: @@ -1209,7 +1219,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear } break; } - } else { // if not state->fe.dtv_property_cache.isdbt_sb_mode + } else { dib8000_write_word(state, 27, (u16) ((state->cfg.pll->ifreq >> 16) & 0x01ff)); dib8000_write_word(state, 28, (u16) (state->cfg.pll->ifreq & 0xffff)); dib8000_write_word(state, 26, (u16) ((state->cfg.pll->ifreq >> 25) & 0x0003)); @@ -1218,7 +1228,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 10, (seq << 4)); // dib8000_write_word(state, 287, (dib8000_read_word(state, 287) & 0xe000) | 0x1000); - switch (state->fe.dtv_property_cache.guard_interval) { + switch (state->fe[0]->dtv_property_cache.guard_interval) { case GUARD_INTERVAL_1_32: guard = 0; break; @@ -1238,7 +1248,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear max_constellation = DQPSK; for (i = 0; i < 3; i++) { - switch (state->fe.dtv_property_cache.layer[i].modulation) { + switch (state->fe[0]->dtv_property_cache.layer[i].modulation) { case DQPSK: constellation = 0; break; @@ -1254,7 +1264,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear break; } - switch (state->fe.dtv_property_cache.layer[i].fec) { + switch (state->fe[0]->dtv_property_cache.layer[i].fec) { case FEC_1_2: crate = 1; break; @@ -1273,26 +1283,26 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear break; } - if ((state->fe.dtv_property_cache.layer[i].interleaving > 0) && - ((state->fe.dtv_property_cache.layer[i].interleaving <= 3) || - (state->fe.dtv_property_cache.layer[i].interleaving == 4 && state->fe.dtv_property_cache.isdbt_sb_mode == 1)) - ) - timeI = state->fe.dtv_property_cache.layer[i].interleaving; + if ((state->fe[0]->dtv_property_cache.layer[i].interleaving > 0) && + ((state->fe[0]->dtv_property_cache.layer[i].interleaving <= 3) || + (state->fe[0]->dtv_property_cache.layer[i].interleaving == 4 && state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1)) + ) + timeI = state->fe[0]->dtv_property_cache.layer[i].interleaving; else timeI = 0; - dib8000_write_word(state, 2 + i, (constellation << 10) | ((state->fe.dtv_property_cache.layer[i].segment_count & 0xf) << 6) | - (crate << 3) | timeI); - if (state->fe.dtv_property_cache.layer[i].segment_count > 0) { + dib8000_write_word(state, 2 + i, (constellation << 10) | ((state->fe[0]->dtv_property_cache.layer[i].segment_count & 0xf) << 6) | + (crate << 3) | timeI); + if (state->fe[0]->dtv_property_cache.layer[i].segment_count > 0) { switch (max_constellation) { case DQPSK: case QPSK: - if (state->fe.dtv_property_cache.layer[i].modulation == QAM_16 || - state->fe.dtv_property_cache.layer[i].modulation == QAM_64) - max_constellation = state->fe.dtv_property_cache.layer[i].modulation; + if (state->fe[0]->dtv_property_cache.layer[i].modulation == QAM_16 || + state->fe[0]->dtv_property_cache.layer[i].modulation == QAM_64) + max_constellation = state->fe[0]->dtv_property_cache.layer[i].modulation; break; case QAM_16: - if (state->fe.dtv_property_cache.layer[i].modulation == QAM_64) - max_constellation = state->fe.dtv_property_cache.layer[i].modulation; + if (state->fe[0]->dtv_property_cache.layer[i].modulation == QAM_64) + max_constellation = state->fe[0]->dtv_property_cache.layer[i].modulation; break; } } @@ -1303,34 +1313,34 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear //dib8000_write_word(state, 5, 13); /*p_last_seg = 13*/ dib8000_write_word(state, 274, (dib8000_read_word(state, 274) & 0xffcf) | - ((state->fe.dtv_property_cache.isdbt_partial_reception & 1) << 5) | ((state->fe.dtv_property_cache. + ((state->fe[0]->dtv_property_cache.isdbt_partial_reception & 1) << 5) | ((state->fe[0]->dtv_property_cache. isdbt_sb_mode & 1) << 4)); - dprintk("mode = %d ; guard = %d", mode, state->fe.dtv_property_cache.guard_interval); + dprintk("mode = %d ; guard = %d", mode, state->fe[0]->dtv_property_cache.guard_interval); /* signal optimization parameter */ - if (state->fe.dtv_property_cache.isdbt_partial_reception) { - seg_diff_mask = (state->fe.dtv_property_cache.layer[0].modulation == DQPSK) << permu_seg[0]; + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception) { + seg_diff_mask = (state->fe[0]->dtv_property_cache.layer[0].modulation == DQPSK) << permu_seg[0]; for (i = 1; i < 3; i++) nbseg_diff += - (state->fe.dtv_property_cache.layer[i].modulation == DQPSK) * state->fe.dtv_property_cache.layer[i].segment_count; + (state->fe[0]->dtv_property_cache.layer[i].modulation == DQPSK) * state->fe[0]->dtv_property_cache.layer[i].segment_count; for (i = 0; i < nbseg_diff; i++) seg_diff_mask |= 1 << permu_seg[i + 1]; } else { for (i = 0; i < 3; i++) nbseg_diff += - (state->fe.dtv_property_cache.layer[i].modulation == DQPSK) * state->fe.dtv_property_cache.layer[i].segment_count; + (state->fe[0]->dtv_property_cache.layer[i].modulation == DQPSK) * state->fe[0]->dtv_property_cache.layer[i].segment_count; for (i = 0; i < nbseg_diff; i++) seg_diff_mask |= 1 << permu_seg[i]; } dprintk("nbseg_diff = %X (%d)", seg_diff_mask, seg_diff_mask); state->differential_constellation = (seg_diff_mask != 0); - dib8000_set_diversity_in(&state->fe, state->diversity_onoff); + dib8000_set_diversity_in(state->fe[0], state->diversity_onoff); - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { // ISDB-Tsb - if (state->fe.dtv_property_cache.isdbt_partial_reception == 1) // 3-segments + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 1) seg_mask13 = 0x00E0; else // 1-segment seg_mask13 = 0x0040; @@ -1340,7 +1350,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear // WRITE: Mode & Diff mask dib8000_write_word(state, 0, (mode << 13) | seg_diff_mask); - if ((seg_diff_mask) || (state->fe.dtv_property_cache.isdbt_sb_mode)) + if ((seg_diff_mask) || (state->fe[0]->dtv_property_cache.isdbt_sb_mode)) dib8000_write_word(state, 268, (dib8000_read_word(state, 268) & 0xF9FF) | 0x0200); else dib8000_write_word(state, 268, (2 << 9) | 39); //init value @@ -1351,26 +1361,25 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 353, seg_mask13); // ADDR 353 -/* // P_small_narrow_band=0, P_small_last_seg=13, P_small_offset_num_car=5 */ - // dib8000_write_word(state, 351, (state->fe.dtv_property_cache.isdbt_sb_mode << 8) | (13 << 4) | 5 ); +/* // P_small_narrow_band=0, P_small_last_seg=13, P_small_offset_num_car=5 */ // ---- SMALL ---- - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { - switch (state->fe.dtv_property_cache.transmission_mode) { + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { + switch (state->fe[0]->dtv_property_cache.transmission_mode) { case TRANSMISSION_MODE_2K: - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) { // 1-seg - if (state->fe.dtv_property_cache.layer[0].modulation == DQPSK) // DQPSK + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) { + if (state->fe[0]->dtv_property_cache.layer[0].modulation == DQPSK) ncoeff = coeff_2k_sb_1seg_dqpsk; else // QPSK or QAM ncoeff = coeff_2k_sb_1seg; } else { // 3-segments - if (state->fe.dtv_property_cache.layer[0].modulation == DQPSK) { // DQPSK on central segment - if (state->fe.dtv_property_cache.layer[1].modulation == DQPSK) // DQPSK on external segments + if (state->fe[0]->dtv_property_cache.layer[0].modulation == DQPSK) { + if (state->fe[0]->dtv_property_cache.layer[1].modulation == DQPSK) ncoeff = coeff_2k_sb_3seg_0dqpsk_1dqpsk; else // QPSK or QAM on external segments ncoeff = coeff_2k_sb_3seg_0dqpsk; } else { // QPSK or QAM on central segment - if (state->fe.dtv_property_cache.layer[1].modulation == DQPSK) // DQPSK on external segments + if (state->fe[0]->dtv_property_cache.layer[1].modulation == DQPSK) ncoeff = coeff_2k_sb_3seg_1dqpsk; else // QPSK or QAM on external segments ncoeff = coeff_2k_sb_3seg; @@ -1379,20 +1388,20 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear break; case TRANSMISSION_MODE_4K: - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) { // 1-seg - if (state->fe.dtv_property_cache.layer[0].modulation == DQPSK) // DQPSK + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) { + if (state->fe[0]->dtv_property_cache.layer[0].modulation == DQPSK) ncoeff = coeff_4k_sb_1seg_dqpsk; else // QPSK or QAM ncoeff = coeff_4k_sb_1seg; } else { // 3-segments - if (state->fe.dtv_property_cache.layer[0].modulation == DQPSK) { // DQPSK on central segment - if (state->fe.dtv_property_cache.layer[1].modulation == DQPSK) { // DQPSK on external segments + if (state->fe[0]->dtv_property_cache.layer[0].modulation == DQPSK) { + if (state->fe[0]->dtv_property_cache.layer[1].modulation == DQPSK) { ncoeff = coeff_4k_sb_3seg_0dqpsk_1dqpsk; } else { // QPSK or QAM on external segments ncoeff = coeff_4k_sb_3seg_0dqpsk; } } else { // QPSK or QAM on central segment - if (state->fe.dtv_property_cache.layer[1].modulation == DQPSK) { // DQPSK on external segments + if (state->fe[0]->dtv_property_cache.layer[1].modulation == DQPSK) { ncoeff = coeff_4k_sb_3seg_1dqpsk; } else // QPSK or QAM on external segments ncoeff = coeff_4k_sb_3seg; @@ -1403,20 +1412,20 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear case TRANSMISSION_MODE_AUTO: case TRANSMISSION_MODE_8K: default: - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) { // 1-seg - if (state->fe.dtv_property_cache.layer[0].modulation == DQPSK) // DQPSK + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) { + if (state->fe[0]->dtv_property_cache.layer[0].modulation == DQPSK) ncoeff = coeff_8k_sb_1seg_dqpsk; else // QPSK or QAM ncoeff = coeff_8k_sb_1seg; } else { // 3-segments - if (state->fe.dtv_property_cache.layer[0].modulation == DQPSK) { // DQPSK on central segment - if (state->fe.dtv_property_cache.layer[1].modulation == DQPSK) { // DQPSK on external segments + if (state->fe[0]->dtv_property_cache.layer[0].modulation == DQPSK) { + if (state->fe[0]->dtv_property_cache.layer[1].modulation == DQPSK) { ncoeff = coeff_8k_sb_3seg_0dqpsk_1dqpsk; } else { // QPSK or QAM on external segments ncoeff = coeff_8k_sb_3seg_0dqpsk; } } else { // QPSK or QAM on central segment - if (state->fe.dtv_property_cache.layer[1].modulation == DQPSK) { // DQPSK on external segments + if (state->fe[0]->dtv_property_cache.layer[1].modulation == DQPSK) { ncoeff = coeff_8k_sb_3seg_1dqpsk; } else // QPSK or QAM on external segments ncoeff = coeff_8k_sb_3seg; @@ -1430,22 +1439,22 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear // P_small_coef_ext_enable=ISDB-Tsb, P_small_narrow_band=ISDB-Tsb, P_small_last_seg=13, P_small_offset_num_car=5 dib8000_write_word(state, 351, - (state->fe.dtv_property_cache.isdbt_sb_mode << 9) | (state->fe.dtv_property_cache.isdbt_sb_mode << 8) | (13 << 4) | 5); + (state->fe[0]->dtv_property_cache.isdbt_sb_mode << 9) | (state->fe[0]->dtv_property_cache.isdbt_sb_mode << 8) | (13 << 4) | 5); // ---- COFF ---- // Carloff, the most robust - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { // Sound Broadcasting mode - use both TMCC and AC pilots + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { // P_coff_cpil_alpha=4, P_coff_inh=0, P_coff_cpil_winlen=64 // P_coff_narrow_band=1, P_coff_square_val=1, P_coff_one_seg=~partial_rcpt, P_coff_use_tmcc=1, P_coff_use_ac=1 dib8000_write_word(state, 187, - (4 << 12) | (0 << 11) | (63 << 5) | (0x3 << 3) | ((~state->fe.dtv_property_cache.isdbt_partial_reception & 1) << 2) - | 0x3); + (4 << 12) | (0 << 11) | (63 << 5) | (0x3 << 3) | ((~state->fe[0]->dtv_property_cache.isdbt_partial_reception & 1) << 2) + | 0x3); -/* // P_small_coef_ext_enable = 1 */ -/* dib8000_write_word(state, 351, dib8000_read_word(state, 351) | 0x200); */ +/* // P_small_coef_ext_enable = 1 */ +/* dib8000_write_word(state, 351, dib8000_read_word(state, 351) | 0x200); */ - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) { // Sound Broadcasting mode 1 seg + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) { // P_coff_winlen=63, P_coff_thres_lock=15, P_coff_one_seg_width= (P_mode == 3) , P_coff_one_seg_sym= (P_mode-1) if (mode == 3) @@ -1469,10 +1478,10 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 186, 80); } else { // Sound Broadcasting mode 3 seg // P_coff_one_seg_sym= 1, P_coff_one_seg_width= 1, P_coff_winlen=63, P_coff_thres_lock=15 - /* if (mode == 3) */ - /* dib8000_write_word(state, 180, 0x2fca | ((0) << 14)); */ - /* else */ - /* dib8000_write_word(state, 180, 0x2fca | ((1) << 14)); */ + /* if (mode == 3) */ + /* dib8000_write_word(state, 180, 0x2fca | ((0) << 14)); */ + /* else */ + /* dib8000_write_word(state, 180, 0x2fca | ((1) << 14)); */ dib8000_write_word(state, 180, 0x1fcf | (1 << 14)); // P_ctrl_corm_thres4pre_freq_inh = 1, P_ctrl_pre_freq_mode_sat=1, @@ -1509,7 +1518,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 341, (4 << 3) | (1 << 2) | (1 << 1) | (1 << 0)); } // ---- FFT ---- - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1 && state->fe.dtv_property_cache.isdbt_partial_reception == 0) // 1-seg + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1 && state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) dib8000_write_word(state, 178, 64); // P_fft_powrange=64 else dib8000_write_word(state, 178, 32); // P_fft_powrange=32 @@ -1518,12 +1527,12 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear * 6bits; p_coff_thres_lock 6bits (for coff lock if needed) */ /* if ( ( nbseg_diff>0)&&(nbseg_diff<13)) - dib8000_write_word(state, 187, (dib8000_read_word(state, 187) & 0xfffb) | (1 << 3)); */ + dib8000_write_word(state, 187, (dib8000_read_word(state, 187) & 0xfffb) | (1 << 3)); */ dib8000_write_word(state, 189, ~seg_mask13 | seg_diff_mask); /* P_lmod4_seg_inh */ dib8000_write_word(state, 192, ~seg_mask13 | seg_diff_mask); /* P_pha3_seg_inh */ dib8000_write_word(state, 225, ~seg_mask13 | seg_diff_mask); /* P_tac_seg_inh */ - if ((!state->fe.dtv_property_cache.isdbt_sb_mode) && (state->cfg.pll->ifreq == 0)) + if ((!state->fe[0]->dtv_property_cache.isdbt_sb_mode) && (state->cfg.pll->ifreq == 0)) dib8000_write_word(state, 266, ~seg_mask13 | seg_diff_mask | 0x40); /* P_equal_noise_seg_inh */ else dib8000_write_word(state, 266, ~seg_mask13 | seg_diff_mask); /* P_equal_noise_seg_inh */ @@ -1538,8 +1547,8 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 211, seg_mask13 & (~seg_diff_mask)); /* P_des_seg_enabled */ /* offset loop parameters */ - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) // Sound Broadcasting mode 1 seg + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) /* P_timf_alpha = (11-P_mode), P_corm_alpha=6, P_corm_thres=0x80 */ dib8000_write_word(state, 32, ((11 - mode) << 12) | (6 << 8) | 0x40); @@ -1551,8 +1560,8 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear /* P_timf_alpha = (9-P_mode, P_corm_alpha=6, P_corm_thres=0x80 */ dib8000_write_word(state, 32, ((9 - mode) << 12) | (6 << 8) | 0x80); - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) // Sound Broadcasting mode 1 seg + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) /* P_ctrl_pha_off_max=3 P_ctrl_sfreq_inh =0 P_ctrl_sfreq_step = (11-P_mode) */ dib8000_write_word(state, 37, (3 << 5) | (0 << 4) | (10 - mode)); @@ -1564,7 +1573,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear dib8000_write_word(state, 37, (3 << 5) | (0 << 4) | (8 - mode)); /* P_dvsy_sync_wait - reuse mode */ - switch (state->fe.dtv_property_cache.transmission_mode) { + switch (state->fe[0]->dtv_property_cache.transmission_mode) { case TRANSMISSION_MODE_8K: mode = 256; break; @@ -1624,15 +1633,15 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear } // ---- ANA_FE ---- - if (state->fe.dtv_property_cache.isdbt_sb_mode) { - if (state->fe.dtv_property_cache.isdbt_partial_reception == 1) // 3-segments + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode) { + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 1) ana_fe = ana_fe_coeff_3seg; else // 1-segment ana_fe = ana_fe_coeff_1seg; } else ana_fe = ana_fe_coeff_13seg; - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1 || state->isdbt_cfg_loaded == 0) + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1 || state->isdbt_cfg_loaded == 0) for (mode = 0; mode < 24; mode++) dib8000_write_word(state, 117 + mode, ana_fe[mode]); @@ -1648,11 +1657,11 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear // "P_cspu_left_edge" not used => do not care // "P_cspu_right_edge" not used => do not care - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { // ISDB-Tsb + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { dib8000_write_word(state, 228, 1); // P_2d_mode_byp=1 dib8000_write_word(state, 205, dib8000_read_word(state, 205) & 0xfff0); // P_cspu_win_cut = 0 - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0 // 1-segment - && state->fe.dtv_property_cache.transmission_mode == TRANSMISSION_MODE_2K) { + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0 + && state->fe[0]->dtv_property_cache.transmission_mode == TRANSMISSION_MODE_2K) { //dib8000_write_word(state, 219, dib8000_read_word(state, 219) & 0xfffe); // P_adp_pass = 0 dib8000_write_word(state, 265, 15); // P_equal_noise_sel = 15 } @@ -1664,7 +1673,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear // ---- TMCC ---- for (i = 0; i < 3; i++) tmcc_pow += - (((state->fe.dtv_property_cache.layer[i].modulation == DQPSK) * 4 + 1) * state->fe.dtv_property_cache.layer[i].segment_count); + (((state->fe[0]->dtv_property_cache.layer[i].modulation == DQPSK) * 4 + 1) * state->fe[0]->dtv_property_cache.layer[i].segment_count); // Quantif of "P_tmcc_dec_thres_?k" is (0, 5+mode, 9); // Threshold is set at 1/4 of max power. tmcc_pow *= (1 << (9 - 2)); @@ -1678,7 +1687,7 @@ static void dib8000_set_channel(struct dib8000_state *state, u8 seq, u8 autosear if (state->isdbt_cfg_loaded == 0) dib8000_write_word(state, 250, 3285); /*p_2d_hspeed_thr0 */ - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) state->isdbt_cfg_loaded = 0; else state->isdbt_cfg_loaded = 1; @@ -1693,38 +1702,38 @@ static int dib8000_autosearch_start(struct dvb_frontend *fe) int slist = 0; - state->fe.dtv_property_cache.inversion = 0; - if (!state->fe.dtv_property_cache.isdbt_sb_mode) - state->fe.dtv_property_cache.layer[0].segment_count = 13; - state->fe.dtv_property_cache.layer[0].modulation = QAM_64; - state->fe.dtv_property_cache.layer[0].fec = FEC_2_3; - state->fe.dtv_property_cache.layer[0].interleaving = 0; + state->fe[0]->dtv_property_cache.inversion = 0; + if (!state->fe[0]->dtv_property_cache.isdbt_sb_mode) + state->fe[0]->dtv_property_cache.layer[0].segment_count = 13; + state->fe[0]->dtv_property_cache.layer[0].modulation = QAM_64; + state->fe[0]->dtv_property_cache.layer[0].fec = FEC_2_3; + state->fe[0]->dtv_property_cache.layer[0].interleaving = 0; //choose the right list, in sb, always do everything - if (state->fe.dtv_property_cache.isdbt_sb_mode) { - state->fe.dtv_property_cache.transmission_mode = TRANSMISSION_MODE_8K; - state->fe.dtv_property_cache.guard_interval = GUARD_INTERVAL_1_8; + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode) { + state->fe[0]->dtv_property_cache.transmission_mode = TRANSMISSION_MODE_8K; + state->fe[0]->dtv_property_cache.guard_interval = GUARD_INTERVAL_1_8; slist = 7; dib8000_write_word(state, 0, (dib8000_read_word(state, 0) & 0x9fff) | (1 << 13)); } else { - if (state->fe.dtv_property_cache.guard_interval == GUARD_INTERVAL_AUTO) { - if (state->fe.dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) { + if (state->fe[0]->dtv_property_cache.guard_interval == GUARD_INTERVAL_AUTO) { + if (state->fe[0]->dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) { slist = 7; dib8000_write_word(state, 0, (dib8000_read_word(state, 0) & 0x9fff) | (1 << 13)); // P_mode = 1 to have autosearch start ok with mode2 } else slist = 3; } else { - if (state->fe.dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) { + if (state->fe[0]->dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) { slist = 2; dib8000_write_word(state, 0, (dib8000_read_word(state, 0) & 0x9fff) | (1 << 13)); // P_mode = 1 } else slist = 0; } - if (state->fe.dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) - state->fe.dtv_property_cache.transmission_mode = TRANSMISSION_MODE_8K; - if (state->fe.dtv_property_cache.guard_interval == GUARD_INTERVAL_AUTO) - state->fe.dtv_property_cache.guard_interval = GUARD_INTERVAL_1_8; + if (state->fe[0]->dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) + state->fe[0]->dtv_property_cache.transmission_mode = TRANSMISSION_MODE_8K; + if (state->fe[0]->dtv_property_cache.guard_interval == GUARD_INTERVAL_AUTO) + state->fe[0]->dtv_property_cache.guard_interval = GUARD_INTERVAL_1_8; dprintk("using list for autosearch : %d", slist); dib8000_set_channel(state, (unsigned char)slist, 1); @@ -1786,7 +1795,7 @@ static int dib8000_tune(struct dvb_frontend *fe) if (state == NULL) return -EINVAL; - dib8000_set_bandwidth(state, state->fe.dtv_property_cache.bandwidth_hz / 1000); + dib8000_set_bandwidth(fe, state->fe[0]->dtv_property_cache.bandwidth_hz / 1000); dib8000_set_channel(state, 0, 0); // restart demod @@ -1799,17 +1808,16 @@ static int dib8000_tune(struct dvb_frontend *fe) // never achieved a lock before - wait for timfreq to update if (state->timf == 0) { - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) // Sound Broadcasting mode 1 seg + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) msleep(300); else // Sound Broadcasting mode 3 seg msleep(500); } else // 13 seg msleep(200); } - //dump_reg(state); - if (state->fe.dtv_property_cache.isdbt_sb_mode == 1) { - if (state->fe.dtv_property_cache.isdbt_partial_reception == 0) { // Sound Broadcasting mode 1 seg + if (state->fe[0]->dtv_property_cache.isdbt_sb_mode == 1) { + if (state->fe[0]->dtv_property_cache.isdbt_partial_reception == 0) { /* P_timf_alpha = (13-P_mode) , P_corm_alpha=6, P_corm_thres=0x40 alpha to check on board */ dib8000_write_word(state, 32, ((13 - mode) << 12) | (6 << 8) | 0x40); @@ -1854,26 +1862,38 @@ static int dib8000_tune(struct dvb_frontend *fe) static int dib8000_wakeup(struct dvb_frontend *fe) { struct dib8000_state *state = fe->demodulator_priv; + u8 index_frontend; + int ret; dib8000_set_power_mode(state, DIB8000M_POWER_ALL); dib8000_set_adc_state(state, DIBX000_ADC_ON); if (dib8000_set_adc_state(state, DIBX000_SLOW_ADC_ON) != 0) dprintk("could not start Slow ADC"); + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + ret = state->fe[index_frontend]->ops.init(state->fe[index_frontend]); + if (ret < 0) + return ret; + } + return 0; } static int dib8000_sleep(struct dvb_frontend *fe) { - struct dib8000_state *st = fe->demodulator_priv; - if (1) { - dib8000_set_output_mode(st, OUTMODE_HIGH_Z); - dib8000_set_power_mode(st, DIB8000M_POWER_INTERFACE_ONLY); - return dib8000_set_adc_state(st, DIBX000_SLOW_ADC_OFF) | dib8000_set_adc_state(st, DIBX000_ADC_OFF); - } else { + struct dib8000_state *state = fe->demodulator_priv; + u8 index_frontend; + int ret; - return 0; + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + ret = state->fe[index_frontend]->ops.sleep(state->fe[index_frontend]); + if (ret < 0) + return ret; } + + dib8000_set_output_mode(fe, OUTMODE_HIGH_Z); + dib8000_set_power_mode(state, DIB8000M_POWER_INTERFACE_ONLY); + return dib8000_set_adc_state(state, DIBX000_SLOW_ADC_OFF) | dib8000_set_adc_state(state, DIBX000_ADC_OFF); } enum frontend_tune_state dib8000_get_tune_state(struct dvb_frontend *fe) @@ -1891,16 +1911,40 @@ int dib8000_set_tune_state(struct dvb_frontend *fe, enum frontend_tune_state tun } EXPORT_SYMBOL(dib8000_set_tune_state); - - - static int dib8000_get_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) { struct dib8000_state *state = fe->demodulator_priv; u16 i, val = 0; + fe_status_t stat; + u8 index_frontend, sub_index_frontend; fe->dtv_property_cache.bandwidth_hz = 6000000; + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + state->fe[index_frontend]->ops.read_status(state->fe[index_frontend], &stat); + if (stat&FE_HAS_SYNC) { + dprintk("TMCC lock on the slave%i", index_frontend); + /* synchronize the cache with the other frontends */ + state->fe[index_frontend]->ops.get_frontend(state->fe[index_frontend], fep); + for (sub_index_frontend = 0; (sub_index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[sub_index_frontend] != NULL); sub_index_frontend++) { + if (sub_index_frontend != index_frontend) { + state->fe[sub_index_frontend]->dtv_property_cache.isdbt_sb_mode = state->fe[index_frontend]->dtv_property_cache.isdbt_sb_mode; + state->fe[sub_index_frontend]->dtv_property_cache.inversion = state->fe[index_frontend]->dtv_property_cache.inversion; + state->fe[sub_index_frontend]->dtv_property_cache.transmission_mode = state->fe[index_frontend]->dtv_property_cache.transmission_mode; + state->fe[sub_index_frontend]->dtv_property_cache.guard_interval = state->fe[index_frontend]->dtv_property_cache.guard_interval; + state->fe[sub_index_frontend]->dtv_property_cache.isdbt_partial_reception = state->fe[index_frontend]->dtv_property_cache.isdbt_partial_reception; + for (i = 0; i < 3; i++) { + state->fe[sub_index_frontend]->dtv_property_cache.layer[i].segment_count = state->fe[index_frontend]->dtv_property_cache.layer[i].segment_count; + state->fe[sub_index_frontend]->dtv_property_cache.layer[i].interleaving = state->fe[index_frontend]->dtv_property_cache.layer[i].interleaving; + state->fe[sub_index_frontend]->dtv_property_cache.layer[i].fec = state->fe[index_frontend]->dtv_property_cache.layer[i].fec; + state->fe[sub_index_frontend]->dtv_property_cache.layer[i].modulation = state->fe[index_frontend]->dtv_property_cache.layer[i].modulation; + } + } + } + return 0; + } + } + fe->dtv_property_cache.isdbt_sb_mode = dib8000_read_word(state, 508) & 0x1; val = dib8000_read_word(state, 570); @@ -1992,112 +2036,200 @@ static int dib8000_get_frontend(struct dvb_frontend *fe, struct dvb_frontend_par break; } } + + /* synchronize the cache with the other frontends */ + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + state->fe[index_frontend]->dtv_property_cache.isdbt_sb_mode = fe->dtv_property_cache.isdbt_sb_mode; + state->fe[index_frontend]->dtv_property_cache.inversion = fe->dtv_property_cache.inversion; + state->fe[index_frontend]->dtv_property_cache.transmission_mode = fe->dtv_property_cache.transmission_mode; + state->fe[index_frontend]->dtv_property_cache.guard_interval = fe->dtv_property_cache.guard_interval; + state->fe[index_frontend]->dtv_property_cache.isdbt_partial_reception = fe->dtv_property_cache.isdbt_partial_reception; + for (i = 0; i < 3; i++) { + state->fe[index_frontend]->dtv_property_cache.layer[i].segment_count = fe->dtv_property_cache.layer[i].segment_count; + state->fe[index_frontend]->dtv_property_cache.layer[i].interleaving = fe->dtv_property_cache.layer[i].interleaving; + state->fe[index_frontend]->dtv_property_cache.layer[i].fec = fe->dtv_property_cache.layer[i].fec; + state->fe[index_frontend]->dtv_property_cache.layer[i].modulation = fe->dtv_property_cache.layer[i].modulation; + } + } return 0; } static int dib8000_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) { struct dib8000_state *state = fe->demodulator_priv; + u8 nbr_pending, exit_condition, index_frontend; + s8 index_frontend_success = -1; int time, ret; + int time_slave = FE_CALLBACK_TIME_NEVER; - fe->dtv_property_cache.delivery_system = SYS_ISDBT; + if (state->fe[0]->dtv_property_cache.frequency == 0) { + dprintk("dib8000: must at least specify frequency "); + return 0; + } - dib8000_set_output_mode(state, OUTMODE_HIGH_Z); + if (state->fe[0]->dtv_property_cache.bandwidth_hz == 0) { + dprintk("dib8000: no bandwidth specified, set to default "); + state->fe[0]->dtv_property_cache.bandwidth_hz = 6000000; + } + + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + /* synchronization of the cache */ + state->fe[index_frontend]->dtv_property_cache.delivery_system = SYS_ISDBT; + memcpy(&state->fe[index_frontend]->dtv_property_cache, &fe->dtv_property_cache, sizeof(struct dtv_frontend_properties)); + + dib8000_set_output_mode(state->fe[index_frontend], OUTMODE_HIGH_Z); + if (state->fe[index_frontend]->ops.tuner_ops.set_params) + state->fe[index_frontend]->ops.tuner_ops.set_params(state->fe[index_frontend], fep); - if (fe->ops.tuner_ops.set_params) - fe->ops.tuner_ops.set_params(fe, fep); + dib8000_set_tune_state(state->fe[index_frontend], CT_AGC_START); + } /* start up the AGC */ - state->tune_state = CT_AGC_START; do { - time = dib8000_agc_startup(fe); + time = dib8000_agc_startup(state->fe[0]); + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + time_slave = dib8000_agc_startup(state->fe[index_frontend]); + if (time == FE_CALLBACK_TIME_NEVER) + time = time_slave; + else if ((time_slave != FE_CALLBACK_TIME_NEVER) && (time_slave > time)) + time = time_slave; + } if (time != FE_CALLBACK_TIME_NEVER) msleep(time / 10); else break; - } while (state->tune_state != CT_AGC_STOP); - - if (state->fe.dtv_property_cache.frequency == 0) { - dprintk("dib8000: must at least specify frequency "); - return 0; - } - - if (state->fe.dtv_property_cache.bandwidth_hz == 0) { - dprintk("dib8000: no bandwidth specified, set to default "); - state->fe.dtv_property_cache.bandwidth_hz = 6000000; - } + exit_condition = 1; + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + if (dib8000_get_tune_state(state->fe[index_frontend]) != CT_AGC_STOP) { + exit_condition = 0; + break; + } + } + } while (exit_condition == 0); + + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) + dib8000_set_tune_state(state->fe[index_frontend], CT_DEMOD_START); + + if ((state->fe[0]->dtv_property_cache.delivery_system != SYS_ISDBT) || + (state->fe[0]->dtv_property_cache.inversion == INVERSION_AUTO) || + (state->fe[0]->dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) || + (state->fe[0]->dtv_property_cache.guard_interval == GUARD_INTERVAL_AUTO) || + (((state->fe[0]->dtv_property_cache.isdbt_layer_enabled & (1 << 0)) != 0) && + (state->fe[0]->dtv_property_cache.layer[0].segment_count != 0xff) && + (state->fe[0]->dtv_property_cache.layer[0].segment_count != 0) && + ((state->fe[0]->dtv_property_cache.layer[0].modulation == QAM_AUTO) || + (state->fe[0]->dtv_property_cache.layer[0].fec == FEC_AUTO))) || + (((state->fe[0]->dtv_property_cache.isdbt_layer_enabled & (1 << 1)) != 0) && + (state->fe[0]->dtv_property_cache.layer[1].segment_count != 0xff) && + (state->fe[0]->dtv_property_cache.layer[1].segment_count != 0) && + ((state->fe[0]->dtv_property_cache.layer[1].modulation == QAM_AUTO) || + (state->fe[0]->dtv_property_cache.layer[1].fec == FEC_AUTO))) || + (((state->fe[0]->dtv_property_cache.isdbt_layer_enabled & (1 << 2)) != 0) && + (state->fe[0]->dtv_property_cache.layer[2].segment_count != 0xff) && + (state->fe[0]->dtv_property_cache.layer[2].segment_count != 0) && + ((state->fe[0]->dtv_property_cache.layer[2].modulation == QAM_AUTO) || + (state->fe[0]->dtv_property_cache.layer[2].fec == FEC_AUTO))) || + (((state->fe[0]->dtv_property_cache.layer[0].segment_count == 0) || + ((state->fe[0]->dtv_property_cache.isdbt_layer_enabled & (1 << 0)) == 0)) && + ((state->fe[0]->dtv_property_cache.layer[1].segment_count == 0) || + ((state->fe[0]->dtv_property_cache.isdbt_layer_enabled & (2 << 0)) == 0)) && + ((state->fe[0]->dtv_property_cache.layer[2].segment_count == 0) || ((state->fe[0]->dtv_property_cache.isdbt_layer_enabled & (3 << 0)) == 0)))) { + int i = 80000; + u8 found = 0; + u8 tune_failed = 0; + + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + dib8000_set_bandwidth(state->fe[index_frontend], fe->dtv_property_cache.bandwidth_hz / 1000); + dib8000_autosearch_start(state->fe[index_frontend]); + } - state->tune_state = CT_DEMOD_START; - - if ((state->fe.dtv_property_cache.delivery_system != SYS_ISDBT) || - (state->fe.dtv_property_cache.inversion == INVERSION_AUTO) || - (state->fe.dtv_property_cache.transmission_mode == TRANSMISSION_MODE_AUTO) || - (state->fe.dtv_property_cache.guard_interval == GUARD_INTERVAL_AUTO) || - (((state->fe.dtv_property_cache.isdbt_layer_enabled & (1 << 0)) != 0) && - (state->fe.dtv_property_cache.layer[0].segment_count != 0xff) && - (state->fe.dtv_property_cache.layer[0].segment_count != 0) && - ((state->fe.dtv_property_cache.layer[0].modulation == QAM_AUTO) || - (state->fe.dtv_property_cache.layer[0].fec == FEC_AUTO))) || - (((state->fe.dtv_property_cache.isdbt_layer_enabled & (1 << 1)) != 0) && - (state->fe.dtv_property_cache.layer[1].segment_count != 0xff) && - (state->fe.dtv_property_cache.layer[1].segment_count != 0) && - ((state->fe.dtv_property_cache.layer[1].modulation == QAM_AUTO) || - (state->fe.dtv_property_cache.layer[1].fec == FEC_AUTO))) || - (((state->fe.dtv_property_cache.isdbt_layer_enabled & (1 << 2)) != 0) && - (state->fe.dtv_property_cache.layer[2].segment_count != 0xff) && - (state->fe.dtv_property_cache.layer[2].segment_count != 0) && - ((state->fe.dtv_property_cache.layer[2].modulation == QAM_AUTO) || - (state->fe.dtv_property_cache.layer[2].fec == FEC_AUTO))) || - (((state->fe.dtv_property_cache.layer[0].segment_count == 0) || - ((state->fe.dtv_property_cache.isdbt_layer_enabled & (1 << 0)) == 0)) && - ((state->fe.dtv_property_cache.layer[1].segment_count == 0) || - ((state->fe.dtv_property_cache.isdbt_layer_enabled & (2 << 0)) == 0)) && - ((state->fe.dtv_property_cache.layer[2].segment_count == 0) || ((state->fe.dtv_property_cache.isdbt_layer_enabled & (3 << 0)) == 0)))) { - int i = 800, found; - - dib8000_set_bandwidth(state, fe->dtv_property_cache.bandwidth_hz / 1000); - dib8000_autosearch_start(fe); do { - msleep(10); - found = dib8000_autosearch_irq(fe); - } while (found == 0 && i--); + msleep(20); + nbr_pending = 0; + exit_condition = 0; /* 0: tune pending; 1: tune failed; 2:tune success */ + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + if (((tune_failed >> index_frontend) & 0x1) == 0) { + found = dib8000_autosearch_irq(state->fe[index_frontend]); + switch (found) { + case 0: /* tune pending */ + nbr_pending++; + break; + case 2: + dprintk("autosearch succeed on the frontend%i", index_frontend); + exit_condition = 2; + index_frontend_success = index_frontend; + break; + default: + dprintk("unhandled autosearch result"); + case 1: + dprintk("autosearch failed for the frontend%i", index_frontend); + break; + } + } + } - dprintk("Frequency %d Hz, autosearch returns: %d", fep->frequency, found); + /* if all tune are done and no success, exit: tune failed */ + if ((nbr_pending == 0) && (exit_condition == 0)) + exit_condition = 1; + } while ((exit_condition == 0) && i--); - if (found == 0 || found == 1) - return 0; // no channel found + if (exit_condition == 1) { /* tune failed */ + dprintk("tune failed"); + return 0; + } + + dprintk("tune success on frontend%i", index_frontend_success); dib8000_get_frontend(fe, fep); } - ret = dib8000_tune(fe); + for (index_frontend = 0, ret = 0; (ret >= 0) && (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) + ret = dib8000_tune(state->fe[index_frontend]); + + /* set output mode and diversity input */ + dib8000_set_output_mode(state->fe[0], state->cfg.output_mode); + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + dib8000_set_output_mode(state->fe[index_frontend], OUTMODE_DIVERSITY); + dib8000_set_diversity_in(state->fe[index_frontend-1], 1); + } - /* make this a config parameter */ - dib8000_set_output_mode(state, state->cfg.output_mode); + /* turn off the diversity of the last chip */ + dib8000_set_diversity_in(state->fe[index_frontend-1], 0); return ret; } +static u16 dib8000_read_lock(struct dvb_frontend *fe) +{ + struct dib8000_state *state = fe->demodulator_priv; + + return dib8000_read_word(state, 568); +} + static int dib8000_read_status(struct dvb_frontend *fe, fe_status_t * stat) { struct dib8000_state *state = fe->demodulator_priv; - u16 lock = dib8000_read_word(state, 568); + u16 lock_slave = 0, lock = dib8000_read_word(state, 568); + u8 index_frontend; + + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) + lock_slave |= dib8000_read_lock(state->fe[index_frontend]); *stat = 0; - if ((lock >> 13) & 1) + if (((lock >> 13) & 1) || ((lock_slave >> 13) & 1)) *stat |= FE_HAS_SIGNAL; - if ((lock >> 8) & 1) /* Equal */ + if (((lock >> 8) & 1) || ((lock_slave >> 8) & 1)) /* Equal */ *stat |= FE_HAS_CARRIER; - if (((lock >> 1) & 0xf) == 0xf) /* TMCC_SYNC */ + if ((((lock >> 1) & 0xf) == 0xf) || (((lock_slave >> 1) & 0xf) == 0xf)) /* TMCC_SYNC */ *stat |= FE_HAS_SYNC; - if (((lock >> 12) & 1) && ((lock >> 5) & 7)) /* FEC MPEG */ + if ((((lock >> 12) & 1) || ((lock_slave >> 12) & 1)) && ((lock >> 5) & 7)) /* FEC MPEG */ *stat |= FE_HAS_LOCK; - if ((lock >> 12) & 1) { + if (((lock >> 12) & 1) || ((lock_slave >> 12) & 1)) { lock = dib8000_read_word(state, 554); /* Viterbi Layer A */ if (lock & 0x01) *stat |= FE_HAS_VITERBI; @@ -2131,44 +2263,120 @@ static int dib8000_read_unc_blocks(struct dvb_frontend *fe, u32 * unc) static int dib8000_read_signal_strength(struct dvb_frontend *fe, u16 * strength) { struct dib8000_state *state = fe->demodulator_priv; - u16 val = dib8000_read_word(state, 390); - *strength = 65535 - val; + u8 index_frontend; + u16 val; + + *strength = 0; + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + state->fe[index_frontend]->ops.read_signal_strength(state->fe[index_frontend], &val); + if (val > 65535 - *strength) + *strength = 65535; + else + *strength += val; + } + + val = 65535 - dib8000_read_word(state, 390); + if (val > 65535 - *strength) + *strength = 65535; + else + *strength += val; return 0; } -static int dib8000_read_snr(struct dvb_frontend *fe, u16 * snr) +static u32 dib8000_get_snr(struct dvb_frontend *fe) { struct dib8000_state *state = fe->demodulator_priv; + u32 n, s, exp; u16 val; - s32 signal_mant, signal_exp, noise_mant, noise_exp; - u32 result = 0; val = dib8000_read_word(state, 542); - noise_mant = (val >> 6) & 0xff; - noise_exp = (val & 0x3f); + n = (val >> 6) & 0xff; + exp = (val & 0x3f); + if ((exp & 0x20) != 0) + exp -= 0x40; + n <<= exp+16; val = dib8000_read_word(state, 543); - signal_mant = (val >> 6) & 0xff; - signal_exp = (val & 0x3f); + s = (val >> 6) & 0xff; + exp = (val & 0x3f); + if ((exp & 0x20) != 0) + exp -= 0x40; + s <<= exp+16; + + if (n > 0) { + u32 t = (s/n) << 16; + return t + ((s << 16) - n*t) / n; + } + return 0xffffffff; +} - if ((noise_exp & 0x20) != 0) - noise_exp -= 0x40; - if ((signal_exp & 0x20) != 0) - signal_exp -= 0x40; +static int dib8000_read_snr(struct dvb_frontend *fe, u16 * snr) +{ + struct dib8000_state *state = fe->demodulator_priv; + u8 index_frontend; + u32 snr_master; - if (signal_mant != 0) - result = intlog10(2) * 10 * signal_exp + 10 * intlog10(signal_mant); - else - result = intlog10(2) * 10 * signal_exp - 100; - if (noise_mant != 0) - result -= intlog10(2) * 10 * noise_exp + 10 * intlog10(noise_mant); + snr_master = dib8000_get_snr(fe); + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) + snr_master += dib8000_get_snr(state->fe[index_frontend]); + + if (snr_master != 0) { + snr_master = 10*intlog10(snr_master>>16); + *snr = snr_master / ((1 << 24) / 10); + } else - result -= intlog10(2) * 10 * noise_exp - 100; + *snr = 0; - *snr = result / ((1 << 24) / 10); return 0; } +int dib8000_set_slave_frontend(struct dvb_frontend *fe, struct dvb_frontend *fe_slave) +{ + struct dib8000_state *state = fe->demodulator_priv; + u8 index_frontend = 1; + + while ((index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL)) + index_frontend++; + if (index_frontend < MAX_NUMBER_OF_FRONTENDS) { + dprintk("set slave fe %p to index %i", fe_slave, index_frontend); + state->fe[index_frontend] = fe_slave; + return 0; + } + + dprintk("too many slave frontend"); + return -ENOMEM; +} +EXPORT_SYMBOL(dib8000_set_slave_frontend); + +int dib8000_remove_slave_frontend(struct dvb_frontend *fe) +{ + struct dib8000_state *state = fe->demodulator_priv; + u8 index_frontend = 1; + + while ((index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL)) + index_frontend++; + if (index_frontend != 1) { + dprintk("remove slave fe %p (index %i)", state->fe[index_frontend-1], index_frontend-1); + state->fe[index_frontend] = NULL; + return 0; + } + + dprintk("no frontend to be removed"); + return -ENODEV; +} +EXPORT_SYMBOL(dib8000_remove_slave_frontend); + +struct dvb_frontend *dib8000_get_slave_frontend(struct dvb_frontend *fe, int slave_index) +{ + struct dib8000_state *state = fe->demodulator_priv; + + if (slave_index >= MAX_NUMBER_OF_FRONTENDS) + return NULL; + return state->fe[slave_index]; +} +EXPORT_SYMBOL(dib8000_get_slave_frontend); + + int dib8000_i2c_enumeration(struct i2c_adapter *host, int no_of_demods, u8 default_addr, u8 first_addr) { int k = 0; @@ -2227,7 +2435,13 @@ static int dib8000_fe_get_tune_settings(struct dvb_frontend *fe, struct dvb_fron static void dib8000_release(struct dvb_frontend *fe) { struct dib8000_state *st = fe->demodulator_priv; + u8 index_frontend; + + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (st->fe[index_frontend] != NULL); index_frontend++) + dvb_frontend_detach(st->fe[index_frontend]); + dibx000_exit_i2c_master(&st->i2c_master); + kfree(st->fe[0]); kfree(st); } @@ -2242,19 +2456,19 @@ EXPORT_SYMBOL(dib8000_get_i2c_master); int dib8000_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff) { struct dib8000_state *st = fe->demodulator_priv; - u16 val = dib8000_read_word(st, 299) & 0xffef; - val |= (onoff & 0x1) << 4; + u16 val = dib8000_read_word(st, 299) & 0xffef; + val |= (onoff & 0x1) << 4; - dprintk("pid filter enabled %d", onoff); - return dib8000_write_word(st, 299, val); + dprintk("pid filter enabled %d", onoff); + return dib8000_write_word(st, 299, val); } EXPORT_SYMBOL(dib8000_pid_filter_ctrl); int dib8000_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff) { struct dib8000_state *st = fe->demodulator_priv; - dprintk("Index %x, PID %d, OnOff %d", id, pid, onoff); - return dib8000_write_word(st, 305 + id, onoff ? (1 << 13) | pid : 0); + dprintk("Index %x, PID %d, OnOff %d", id, pid, onoff); + return dib8000_write_word(st, 305 + id, onoff ? (1 << 13) | pid : 0); } EXPORT_SYMBOL(dib8000_pid_filter); @@ -2298,6 +2512,9 @@ struct dvb_frontend *dib8000_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, s state = kzalloc(sizeof(struct dib8000_state), GFP_KERNEL); if (state == NULL) return NULL; + fe = kzalloc(sizeof(struct dvb_frontend), GFP_KERNEL); + if (fe == NULL) + return NULL; memcpy(&state->cfg, cfg, sizeof(struct dib8000_config)); state->i2c.adap = i2c_adap; @@ -2311,9 +2528,9 @@ struct dvb_frontend *dib8000_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, s if ((state->cfg.output_mode != OUTMODE_MPEG2_SERIAL) && (state->cfg.output_mode != OUTMODE_MPEG2_PAR_GATED_CLK)) state->cfg.output_mode = OUTMODE_MPEG2_FIFO; - fe = &state->fe; + state->fe[0] = fe; fe->demodulator_priv = state; - memcpy(&state->fe.ops, &dib8000_ops, sizeof(struct dvb_frontend_ops)); + memcpy(&state->fe[0]->ops, &dib8000_ops, sizeof(struct dvb_frontend_ops)); state->timf_default = cfg->pll->timf; diff --git a/drivers/media/dvb/frontends/dib8000.h b/drivers/media/dvb/frontends/dib8000.h index e0a9ded11df4..617f9eba3a09 100644 --- a/drivers/media/dvb/frontends/dib8000.h +++ b/drivers/media/dvb/frontends/dib8000.h @@ -50,6 +50,9 @@ extern int dib8000_set_tune_state(struct dvb_frontend *fe, enum frontend_tune_st extern enum frontend_tune_state dib8000_get_tune_state(struct dvb_frontend *fe); extern void dib8000_pwm_agc_reset(struct dvb_frontend *fe); extern s32 dib8000_get_adc_power(struct dvb_frontend *fe, u8 mode); +extern int dib8000_set_slave_frontend(struct dvb_frontend *fe, struct dvb_frontend *fe_slave); +extern int dib8000_remove_slave_frontend(struct dvb_frontend *fe); +extern struct dvb_frontend *dib8000_get_slave_frontend(struct dvb_frontend *fe, int slave_index); #else static inline struct dvb_frontend *dib8000_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib8000_config *cfg) { @@ -111,6 +114,23 @@ static inline s32 dib8000_get_adc_power(struct dvb_frontend *fe, u8 mode) printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return 0; } +static inline int dib8000_set_slave_frontend(struct dvb_frontend *fe, struct dvb_frontend *fe_slave) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +int dib8000_remove_slave_frontend(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline struct dvb_frontend *dib8000_get_slave_frontend(struct dvb_frontend *fe, int slave_index) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} #endif #endif diff --git a/drivers/media/dvb/frontends/dib9000.c b/drivers/media/dvb/frontends/dib9000.c new file mode 100644 index 000000000000..43fb6e45424a --- /dev/null +++ b/drivers/media/dvb/frontends/dib9000.c @@ -0,0 +1,2350 @@ +/* + * Linux-DVB Driver for DiBcom's DiB9000 and demodulator-family. + * + * Copyright (C) 2005-10 DiBcom (http://www.dibcom.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, version 2. + */ +#include <linux/kernel.h> +#include <linux/i2c.h> +#include <linux/mutex.h> + +#include "dvb_math.h" +#include "dvb_frontend.h" + +#include "dib9000.h" +#include "dibx000_common.h" + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "turn on debugging (default: 0)"); + +#define dprintk(args...) do { if (debug) { printk(KERN_DEBUG "DiB9000: "); printk(args); printk("\n"); } } while (0) +#define MAX_NUMBER_OF_FRONTENDS 6 + +struct i2c_device { + struct i2c_adapter *i2c_adap; + u8 i2c_addr; +}; + +/* lock */ +#define DIB_LOCK struct mutex +#define DibAcquireLock(lock) do { if (mutex_lock_interruptible(lock) < 0) dprintk("could not get the lock"); } while (0) +#define DibReleaseLock(lock) mutex_unlock(lock) +#define DibInitLock(lock) mutex_init(lock) +#define DibFreeLock(lock) + +struct dib9000_state { + struct i2c_device i2c; + + struct dibx000_i2c_master i2c_master; + struct i2c_adapter tuner_adap; + struct i2c_adapter component_bus; + + u16 revision; + u8 reg_offs; + + enum frontend_tune_state tune_state; + u32 status; + struct dvb_frontend_parametersContext channel_status; + + u8 fe_id; + +#define DIB9000_GPIO_DEFAULT_DIRECTIONS 0xffff + u16 gpio_dir; +#define DIB9000_GPIO_DEFAULT_VALUES 0x0000 + u16 gpio_val; +#define DIB9000_GPIO_DEFAULT_PWM_POS 0xffff + u16 gpio_pwm_pos; + + union { /* common for all chips */ + struct { + u8 mobile_mode:1; + } host; + + struct { + struct dib9000_fe_memory_map { + u16 addr; + u16 size; + } fe_mm[18]; + u8 memcmd; + + DIB_LOCK mbx_if_lock; /* to protect read/write operations */ + DIB_LOCK mbx_lock; /* to protect the whole mailbox handling */ + + DIB_LOCK mem_lock; /* to protect the memory accesses */ + DIB_LOCK mem_mbx_lock; /* to protect the memory-based mailbox */ + +#define MBX_MAX_WORDS (256 - 200 - 2) +#define DIB9000_MSG_CACHE_SIZE 2 + u16 message_cache[DIB9000_MSG_CACHE_SIZE][MBX_MAX_WORDS]; + u8 fw_is_running; + } risc; + } platform; + + union { /* common for all platforms */ + struct { + struct dib9000_config cfg; + } d9; + } chip; + + struct dvb_frontend *fe[MAX_NUMBER_OF_FRONTENDS]; + u16 component_bus_speed; +}; + +u32 fe_info[44] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0 +}; + +enum dib9000_power_mode { + DIB9000_POWER_ALL = 0, + + DIB9000_POWER_NO, + DIB9000_POWER_INTERF_ANALOG_AGC, + DIB9000_POWER_COR4_DINTLV_ICIRM_EQUAL_CFROD, + DIB9000_POWER_COR4_CRY_ESRAM_MOUT_NUD, + DIB9000_POWER_INTERFACE_ONLY, +}; + +enum dib9000_out_messages { + OUT_MSG_HBM_ACK, + OUT_MSG_HOST_BUF_FAIL, + OUT_MSG_REQ_VERSION, + OUT_MSG_BRIDGE_I2C_W, + OUT_MSG_BRIDGE_I2C_R, + OUT_MSG_BRIDGE_APB_W, + OUT_MSG_BRIDGE_APB_R, + OUT_MSG_SCAN_CHANNEL, + OUT_MSG_MONIT_DEMOD, + OUT_MSG_CONF_GPIO, + OUT_MSG_DEBUG_HELP, + OUT_MSG_SUBBAND_SEL, + OUT_MSG_ENABLE_TIME_SLICE, + OUT_MSG_FE_FW_DL, + OUT_MSG_FE_CHANNEL_SEARCH, + OUT_MSG_FE_CHANNEL_TUNE, + OUT_MSG_FE_SLEEP, + OUT_MSG_FE_SYNC, + OUT_MSG_CTL_MONIT, + + OUT_MSG_CONF_SVC, + OUT_MSG_SET_HBM, + OUT_MSG_INIT_DEMOD, + OUT_MSG_ENABLE_DIVERSITY, + OUT_MSG_SET_OUTPUT_MODE, + OUT_MSG_SET_PRIORITARY_CHANNEL, + OUT_MSG_ACK_FRG, + OUT_MSG_INIT_PMU, +}; + +enum dib9000_in_messages { + IN_MSG_DATA, + IN_MSG_FRAME_INFO, + IN_MSG_CTL_MONIT, + IN_MSG_ACK_FREE_ITEM, + IN_MSG_DEBUG_BUF, + IN_MSG_MPE_MONITOR, + IN_MSG_RAWTS_MONITOR, + IN_MSG_END_BRIDGE_I2C_RW, + IN_MSG_END_BRIDGE_APB_RW, + IN_MSG_VERSION, + IN_MSG_END_OF_SCAN, + IN_MSG_MONIT_DEMOD, + IN_MSG_ERROR, + IN_MSG_FE_FW_DL_DONE, + IN_MSG_EVENT, + IN_MSG_ACK_CHANGE_SVC, + IN_MSG_HBM_PROF, +}; + +/* memory_access requests */ +#define FE_MM_W_CHANNEL 0 +#define FE_MM_W_FE_INFO 1 +#define FE_MM_RW_SYNC 2 + +#define FE_SYNC_CHANNEL 1 +#define FE_SYNC_W_GENERIC_MONIT 2 +#define FE_SYNC_COMPONENT_ACCESS 3 + +#define FE_MM_R_CHANNEL_SEARCH_STATE 3 +#define FE_MM_R_CHANNEL_UNION_CONTEXT 4 +#define FE_MM_R_FE_INFO 5 +#define FE_MM_R_FE_MONITOR 6 + +#define FE_MM_W_CHANNEL_HEAD 7 +#define FE_MM_W_CHANNEL_UNION 8 +#define FE_MM_W_CHANNEL_CONTEXT 9 +#define FE_MM_R_CHANNEL_UNION 10 +#define FE_MM_R_CHANNEL_CONTEXT 11 +#define FE_MM_R_CHANNEL_TUNE_STATE 12 + +#define FE_MM_R_GENERIC_MONITORING_SIZE 13 +#define FE_MM_W_GENERIC_MONITORING 14 +#define FE_MM_R_GENERIC_MONITORING 15 + +#define FE_MM_W_COMPONENT_ACCESS 16 +#define FE_MM_RW_COMPONENT_ACCESS_BUFFER 17 +static int dib9000_risc_apb_access_read(struct dib9000_state *state, u32 address, u16 attribute, const u8 * tx, u32 txlen, u8 * b, u32 len); +static int dib9000_risc_apb_access_write(struct dib9000_state *state, u32 address, u16 attribute, const u8 * b, u32 len); + +static u16 to_fw_output_mode(u16 mode) +{ + switch (mode) { + case OUTMODE_HIGH_Z: + return 0; + case OUTMODE_MPEG2_PAR_GATED_CLK: + return 4; + case OUTMODE_MPEG2_PAR_CONT_CLK: + return 8; + case OUTMODE_MPEG2_SERIAL: + return 16; + case OUTMODE_DIVERSITY: + return 128; + case OUTMODE_MPEG2_FIFO: + return 2; + case OUTMODE_ANALOG_ADC: + return 1; + default: + return 0; + } +} + +static u16 dib9000_read16_attr(struct dib9000_state *state, u16 reg, u8 * b, u32 len, u16 attribute) +{ + u32 chunk_size = 126; + u32 l; + int ret; + u8 wb[2] = { reg >> 8, reg & 0xff }; + struct i2c_msg msg[2] = { + {.addr = state->i2c.i2c_addr >> 1, .flags = 0, .buf = wb, .len = 2}, + {.addr = state->i2c.i2c_addr >> 1, .flags = I2C_M_RD, .buf = b, .len = len}, + }; + + if (state->platform.risc.fw_is_running && (reg < 1024)) + return dib9000_risc_apb_access_read(state, reg, attribute, NULL, 0, b, len); + + if (attribute & DATA_BUS_ACCESS_MODE_8BIT) + wb[0] |= (1 << 5); + if (attribute & DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT) + wb[0] |= (1 << 4); + + do { + l = len < chunk_size ? len : chunk_size; + msg[1].len = l; + msg[1].buf = b; + ret = i2c_transfer(state->i2c.i2c_adap, msg, 2) != 2 ? -EREMOTEIO : 0; + if (ret != 0) { + dprintk("i2c read error on %d", reg); + return -EREMOTEIO; + } + + b += l; + len -= l; + + if (!(attribute & DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT)) + reg += l / 2; + } while ((ret == 0) && len); + + return 0; +} + +static u16 dib9000_i2c_read16(struct i2c_device *i2c, u16 reg) +{ + u8 b[2]; + u8 wb[2] = { reg >> 8, reg & 0xff }; + struct i2c_msg msg[2] = { + {.addr = i2c->i2c_addr >> 1, .flags = 0, .buf = wb, .len = 2}, + {.addr = i2c->i2c_addr >> 1, .flags = I2C_M_RD, .buf = b, .len = 2}, + }; + + if (i2c_transfer(i2c->i2c_adap, msg, 2) != 2) { + dprintk("read register %x error", reg); + return 0; + } + + return (b[0] << 8) | b[1]; +} + +static inline u16 dib9000_read_word(struct dib9000_state *state, u16 reg) +{ + u8 b[2]; + if (dib9000_read16_attr(state, reg, b, 2, 0) != 0) + return 0; + return (b[0] << 8 | b[1]); +} + +static inline u16 dib9000_read_word_attr(struct dib9000_state *state, u16 reg, u16 attribute) +{ + u8 b[2]; + if (dib9000_read16_attr(state, reg, b, 2, attribute) != 0) + return 0; + return (b[0] << 8 | b[1]); +} + +#define dib9000_read16_noinc_attr(state, reg, b, len, attribute) dib9000_read16_attr(state, reg, b, len, (attribute) | DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT) + +static u16 dib9000_write16_attr(struct dib9000_state *state, u16 reg, const u8 * buf, u32 len, u16 attribute) +{ + u8 b[255]; + u32 chunk_size = 126; + u32 l; + int ret; + + struct i2c_msg msg = { + .addr = state->i2c.i2c_addr >> 1, .flags = 0, .buf = b, .len = len + 2 + }; + + if (state->platform.risc.fw_is_running && (reg < 1024)) { + if (dib9000_risc_apb_access_write + (state, reg, DATA_BUS_ACCESS_MODE_16BIT | DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT | attribute, buf, len) != 0) + return -EINVAL; + return 0; + } + + b[0] = (reg >> 8) & 0xff; + b[1] = (reg) & 0xff; + + if (attribute & DATA_BUS_ACCESS_MODE_8BIT) + b[0] |= (1 << 5); + if (attribute & DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT) + b[0] |= (1 << 4); + + do { + l = len < chunk_size ? len : chunk_size; + msg.len = l + 2; + memcpy(&b[2], buf, l); + + ret = i2c_transfer(state->i2c.i2c_adap, &msg, 1) != 1 ? -EREMOTEIO : 0; + + buf += l; + len -= l; + + if (!(attribute & DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT)) + reg += l / 2; + } while ((ret == 0) && len); + + return ret; +} + +static int dib9000_i2c_write16(struct i2c_device *i2c, u16 reg, u16 val) +{ + u8 b[4] = { (reg >> 8) & 0xff, reg & 0xff, (val >> 8) & 0xff, val & 0xff }; + struct i2c_msg msg = { + .addr = i2c->i2c_addr >> 1, .flags = 0, .buf = b, .len = 4 + }; + + return i2c_transfer(i2c->i2c_adap, &msg, 1) != 1 ? -EREMOTEIO : 0; +} + +static inline int dib9000_write_word(struct dib9000_state *state, u16 reg, u16 val) +{ + u8 b[2] = { val >> 8, val & 0xff }; + return dib9000_write16_attr(state, reg, b, 2, 0); +} + +static inline int dib9000_write_word_attr(struct dib9000_state *state, u16 reg, u16 val, u16 attribute) +{ + u8 b[2] = { val >> 8, val & 0xff }; + return dib9000_write16_attr(state, reg, b, 2, attribute); +} + +#define dib9000_write(state, reg, buf, len) dib9000_write16_attr(state, reg, buf, len, 0) +#define dib9000_write16_noinc(state, reg, buf, len) dib9000_write16_attr(state, reg, buf, len, DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT) +#define dib9000_write16_noinc_attr(state, reg, buf, len, attribute) dib9000_write16_attr(state, reg, buf, len, DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT | (attribute)) + +#define dib9000_mbx_send(state, id, data, len) dib9000_mbx_send_attr(state, id, data, len, 0) +#define dib9000_mbx_get_message(state, id, msg, len) dib9000_mbx_get_message_attr(state, id, msg, len, 0) + +#define MAC_IRQ (1 << 1) +#define IRQ_POL_MSK (1 << 4) + +#define dib9000_risc_mem_read_chunks(state, b, len) dib9000_read16_attr(state, 1063, b, len, DATA_BUS_ACCESS_MODE_8BIT | DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT) +#define dib9000_risc_mem_write_chunks(state, buf, len) dib9000_write16_attr(state, 1063, buf, len, DATA_BUS_ACCESS_MODE_8BIT | DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT) + +static void dib9000_risc_mem_setup_cmd(struct dib9000_state *state, u32 addr, u32 len, u8 reading) +{ + u8 b[14] = { 0 }; + +/* dprintk("%d memcmd: %d %d %d\n", state->fe_id, addr, addr+len, len); */ +/* b[0] = 0 << 7; */ + b[1] = 1; + +/* b[2] = 0; */ +/* b[3] = 0; */ + b[4] = (u8) (addr >> 8); + b[5] = (u8) (addr & 0xff); + +/* b[10] = 0; */ +/* b[11] = 0; */ + b[12] = (u8) (addr >> 8); + b[13] = (u8) (addr & 0xff); + + addr += len; +/* b[6] = 0; */ +/* b[7] = 0; */ + b[8] = (u8) (addr >> 8); + b[9] = (u8) (addr & 0xff); + + dib9000_write(state, 1056, b, 14); + if (reading) + dib9000_write_word(state, 1056, (1 << 15) | 1); + state->platform.risc.memcmd = -1; /* if it was called directly reset it - to force a future setup-call to set it */ +} + +static void dib9000_risc_mem_setup(struct dib9000_state *state, u8 cmd) +{ + struct dib9000_fe_memory_map *m = &state->platform.risc.fe_mm[cmd & 0x7f]; + /* decide whether we need to "refresh" the memory controller */ + if (state->platform.risc.memcmd == cmd && /* same command */ + !(cmd & 0x80 && m->size < 67)) /* and we do not want to read something with less than 67 bytes looping - working around a bug in the memory controller */ + return; + dib9000_risc_mem_setup_cmd(state, m->addr, m->size, cmd & 0x80); + state->platform.risc.memcmd = cmd; +} + +static int dib9000_risc_mem_read(struct dib9000_state *state, u8 cmd, u8 * b, u16 len) +{ + if (!state->platform.risc.fw_is_running) + return -EIO; + + DibAcquireLock(&state->platform.risc.mem_lock); + dib9000_risc_mem_setup(state, cmd | 0x80); + dib9000_risc_mem_read_chunks(state, b, len); + DibReleaseLock(&state->platform.risc.mem_lock); + return 0; +} + +static int dib9000_risc_mem_write(struct dib9000_state *state, u8 cmd, const u8 * b) +{ + struct dib9000_fe_memory_map *m = &state->platform.risc.fe_mm[cmd]; + if (!state->platform.risc.fw_is_running) + return -EIO; + + DibAcquireLock(&state->platform.risc.mem_lock); + dib9000_risc_mem_setup(state, cmd); + dib9000_risc_mem_write_chunks(state, b, m->size); + DibReleaseLock(&state->platform.risc.mem_lock); + return 0; +} + +static int dib9000_firmware_download(struct dib9000_state *state, u8 risc_id, u16 key, const u8 * code, u32 len) +{ + u16 offs; + + if (risc_id == 1) + offs = 16; + else + offs = 0; + + /* config crtl reg */ + dib9000_write_word(state, 1024 + offs, 0x000f); + dib9000_write_word(state, 1025 + offs, 0); + dib9000_write_word(state, 1031 + offs, key); + + dprintk("going to download %dB of microcode", len); + if (dib9000_write16_noinc(state, 1026 + offs, (u8 *) code, (u16) len) != 0) { + dprintk("error while downloading microcode for RISC %c", 'A' + risc_id); + return -EIO; + } + + dprintk("Microcode for RISC %c loaded", 'A' + risc_id); + + return 0; +} + +static int dib9000_mbx_host_init(struct dib9000_state *state, u8 risc_id) +{ + u16 mbox_offs; + u16 reset_reg; + u16 tries = 1000; + + if (risc_id == 1) + mbox_offs = 16; + else + mbox_offs = 0; + + /* Reset mailbox */ + dib9000_write_word(state, 1027 + mbox_offs, 0x8000); + + /* Read reset status */ + do { + reset_reg = dib9000_read_word(state, 1027 + mbox_offs); + msleep(100); + } while ((reset_reg & 0x8000) && --tries); + + if (reset_reg & 0x8000) { + dprintk("MBX: init ERROR, no response from RISC %c", 'A' + risc_id); + return -EIO; + } + dprintk("MBX: initialized"); + return 0; +} + +#define MAX_MAILBOX_TRY 100 +static int dib9000_mbx_send_attr(struct dib9000_state *state, u8 id, u16 * data, u8 len, u16 attr) +{ + u8 ret = 0, *d, b[2]; + u16 tmp; + u16 size; + u32 i; + + if (!state->platform.risc.fw_is_running) + return -EINVAL; + + DibAcquireLock(&state->platform.risc.mbx_if_lock); + tmp = MAX_MAILBOX_TRY; + do { + size = dib9000_read_word_attr(state, 1043, attr) & 0xff; + if ((size + len + 1) > MBX_MAX_WORDS && --tmp) { + dprintk("MBX: RISC mbx full, retrying"); + msleep(100); + } else + break; + } while (1); + + /*dprintk( "MBX: size: %d", size); */ + + if (tmp == 0) { + ret = -EINVAL; + goto out; + } +#ifdef DUMP_MSG + dprintk("--> %02x %d ", id, len + 1); + for (i = 0; i < len; i++) + dprintk("%04x ", data[i]); + dprintk("\n"); +#endif + + /* byte-order conversion - works on big (where it is not necessary) or little endian */ + d = (u8 *) data; + for (i = 0; i < len; i++) { + tmp = data[i]; + *d++ = tmp >> 8; + *d++ = tmp & 0xff; + } + + /* write msg */ + b[0] = id; + b[1] = len + 1; + if (dib9000_write16_noinc_attr(state, 1045, b, 2, attr) != 0 || dib9000_write16_noinc_attr(state, 1045, (u8 *) data, len * 2, attr) != 0) { + ret = -EIO; + goto out; + } + + /* update register nb_mes_in_RX */ + ret = (u8) dib9000_write_word_attr(state, 1043, 1 << 14, attr); + +out: + DibReleaseLock(&state->platform.risc.mbx_if_lock); + + return ret; +} + +static u8 dib9000_mbx_read(struct dib9000_state *state, u16 * data, u8 risc_id, u16 attr) +{ +#ifdef DUMP_MSG + u16 *d = data; +#endif + + u16 tmp, i; + u8 size; + u8 mc_base; + + if (!state->platform.risc.fw_is_running) + return 0; + + DibAcquireLock(&state->platform.risc.mbx_if_lock); + if (risc_id == 1) + mc_base = 16; + else + mc_base = 0; + + /* Length and type in the first word */ + *data = dib9000_read_word_attr(state, 1029 + mc_base, attr); + + size = *data & 0xff; + if (size <= MBX_MAX_WORDS) { + data++; + size--; /* Initial word already read */ + + dib9000_read16_noinc_attr(state, 1029 + mc_base, (u8 *) data, size * 2, attr); + + /* to word conversion */ + for (i = 0; i < size; i++) { + tmp = *data; + *data = (tmp >> 8) | (tmp << 8); + data++; + } + +#ifdef DUMP_MSG + dprintk("<-- "); + for (i = 0; i < size + 1; i++) + dprintk("%04x ", d[i]); + dprintk("\n"); +#endif + } else { + dprintk("MBX: message is too big for message cache (%d), flushing message", size); + size--; /* Initial word already read */ + while (size--) + dib9000_read16_noinc_attr(state, 1029 + mc_base, (u8 *) data, 2, attr); + } + /* Update register nb_mes_in_TX */ + dib9000_write_word_attr(state, 1028 + mc_base, 1 << 14, attr); + + DibReleaseLock(&state->platform.risc.mbx_if_lock); + + return size + 1; +} + +static int dib9000_risc_debug_buf(struct dib9000_state *state, u16 * data, u8 size) +{ + u32 ts = data[1] << 16 | data[0]; + char *b = (char *)&data[2]; + + b[2 * (size - 2) - 1] = '\0'; /* Bullet proof the buffer */ + if (*b == '~') { + b++; + dprintk(b); + } else + dprintk("RISC%d: %d.%04d %s", state->fe_id, ts / 10000, ts % 10000, *b ? b : "<emtpy>"); + return 1; +} + +static int dib9000_mbx_fetch_to_cache(struct dib9000_state *state, u16 attr) +{ + int i; + u8 size; + u16 *block; + /* find a free slot */ + for (i = 0; i < DIB9000_MSG_CACHE_SIZE; i++) { + block = state->platform.risc.message_cache[i]; + if (*block == 0) { + size = dib9000_mbx_read(state, block, 1, attr); + +/* dprintk( "MBX: fetched %04x message to cache", *block); */ + + switch (*block >> 8) { + case IN_MSG_DEBUG_BUF: + dib9000_risc_debug_buf(state, block + 1, size); /* debug-messages are going to be printed right away */ + *block = 0; /* free the block */ + break; +#if 0 + case IN_MSG_DATA: /* FE-TRACE */ + dib9000_risc_data_process(state, block + 1, size); + *block = 0; + break; +#endif + default: + break; + } + + return 1; + } + } + dprintk("MBX: no free cache-slot found for new message..."); + return -1; +} + +static u8 dib9000_mbx_count(struct dib9000_state *state, u8 risc_id, u16 attr) +{ + if (risc_id == 0) + return (u8) (dib9000_read_word_attr(state, 1028, attr) >> 10) & 0x1f; /* 5 bit field */ + else + return (u8) (dib9000_read_word_attr(state, 1044, attr) >> 8) & 0x7f; /* 7 bit field */ +} + +static int dib9000_mbx_process(struct dib9000_state *state, u16 attr) +{ + int ret = 0; + u16 tmp; + + if (!state->platform.risc.fw_is_running) + return -1; + + DibAcquireLock(&state->platform.risc.mbx_lock); + + if (dib9000_mbx_count(state, 1, attr)) /* 1=RiscB */ + ret = dib9000_mbx_fetch_to_cache(state, attr); + + tmp = dib9000_read_word_attr(state, 1229, attr); /* Clear the IRQ */ +/* if (tmp) */ +/* dprintk( "cleared IRQ: %x", tmp); */ + DibReleaseLock(&state->platform.risc.mbx_lock); + + return ret; +} + +static int dib9000_mbx_get_message_attr(struct dib9000_state *state, u16 id, u16 * msg, u8 * size, u16 attr) +{ + u8 i; + u16 *block; + u16 timeout = 30; + + *msg = 0; + do { + /* dib9000_mbx_get_from_cache(); */ + for (i = 0; i < DIB9000_MSG_CACHE_SIZE; i++) { + block = state->platform.risc.message_cache[i]; + if ((*block >> 8) == id) { + *size = (*block & 0xff) - 1; + memcpy(msg, block + 1, (*size) * 2); + *block = 0; /* free the block */ + i = 0; /* signal that we found a message */ + break; + } + } + + if (i == 0) + break; + + if (dib9000_mbx_process(state, attr) == -1) /* try to fetch one message - if any */ + return -1; + + } while (--timeout); + + if (timeout == 0) { + dprintk("waiting for message %d timed out", id); + return -1; + } + + return i == 0; +} + +static int dib9000_risc_check_version(struct dib9000_state *state) +{ + u8 r[4]; + u8 size; + u16 fw_version = 0; + + if (dib9000_mbx_send(state, OUT_MSG_REQ_VERSION, &fw_version, 1) != 0) + return -EIO; + + if (dib9000_mbx_get_message(state, IN_MSG_VERSION, (u16 *) r, &size) < 0) + return -EIO; + + fw_version = (r[0] << 8) | r[1]; + dprintk("RISC: ver: %d.%02d (IC: %d)", fw_version >> 10, fw_version & 0x3ff, (r[2] << 8) | r[3]); + + if ((fw_version >> 10) != 7) + return -EINVAL; + + switch (fw_version & 0x3ff) { + case 11: + case 12: + case 14: + case 15: + case 16: + case 17: + break; + default: + dprintk("RISC: invalid firmware version"); + return -EINVAL; + } + + dprintk("RISC: valid firmware version"); + return 0; +} + +static int dib9000_fw_boot(struct dib9000_state *state, const u8 * codeA, u32 lenA, const u8 * codeB, u32 lenB) +{ + /* Reconfig pool mac ram */ + dib9000_write_word(state, 1225, 0x02); /* A: 8k C, 4 k D - B: 32k C 6 k D - IRAM 96k */ + dib9000_write_word(state, 1226, 0x05); + + /* Toggles IP crypto to Host APB interface. */ + dib9000_write_word(state, 1542, 1); + + /* Set jump and no jump in the dma box */ + dib9000_write_word(state, 1074, 0); + dib9000_write_word(state, 1075, 0); + + /* Set MAC as APB Master. */ + dib9000_write_word(state, 1237, 0); + + /* Reset the RISCs */ + if (codeA != NULL) + dib9000_write_word(state, 1024, 2); + else + dib9000_write_word(state, 1024, 15); + if (codeB != NULL) + dib9000_write_word(state, 1040, 2); + + if (codeA != NULL) + dib9000_firmware_download(state, 0, 0x1234, codeA, lenA); + if (codeB != NULL) + dib9000_firmware_download(state, 1, 0x1234, codeB, lenB); + + /* Run the RISCs */ + if (codeA != NULL) + dib9000_write_word(state, 1024, 0); + if (codeB != NULL) + dib9000_write_word(state, 1040, 0); + + if (codeA != NULL) + if (dib9000_mbx_host_init(state, 0) != 0) + return -EIO; + if (codeB != NULL) + if (dib9000_mbx_host_init(state, 1) != 0) + return -EIO; + + msleep(100); + state->platform.risc.fw_is_running = 1; + + if (dib9000_risc_check_version(state) != 0) + return -EINVAL; + + state->platform.risc.memcmd = 0xff; + return 0; +} + +static u16 dib9000_identify(struct i2c_device *client) +{ + u16 value; + + value = dib9000_i2c_read16(client, 896); + if (value != 0x01b3) { + dprintk("wrong Vendor ID (0x%x)", value); + return 0; + } + + value = dib9000_i2c_read16(client, 897); + if (value != 0x4000 && value != 0x4001 && value != 0x4002 && value != 0x4003 && value != 0x4004 && value != 0x4005) { + dprintk("wrong Device ID (0x%x)", value); + return 0; + } + + /* protect this driver to be used with 7000PC */ + if (value == 0x4000 && dib9000_i2c_read16(client, 769) == 0x4000) { + dprintk("this driver does not work with DiB7000PC"); + return 0; + } + + switch (value) { + case 0x4000: + dprintk("found DiB7000MA/PA/MB/PB"); + break; + case 0x4001: + dprintk("found DiB7000HC"); + break; + case 0x4002: + dprintk("found DiB7000MC"); + break; + case 0x4003: + dprintk("found DiB9000A"); + break; + case 0x4004: + dprintk("found DiB9000H"); + break; + case 0x4005: + dprintk("found DiB9000M"); + break; + } + + return value; +} + +static void dib9000_set_power_mode(struct dib9000_state *state, enum dib9000_power_mode mode) +{ + /* by default everything is going to be powered off */ + u16 reg_903 = 0x3fff, reg_904 = 0xffff, reg_905 = 0xffff, reg_906; + u8 offset; + + if (state->revision == 0x4003 || state->revision == 0x4004 || state->revision == 0x4005) + offset = 1; + else + offset = 0; + + reg_906 = dib9000_read_word(state, 906 + offset) | 0x3; /* keep settings for RISC */ + + /* now, depending on the requested mode, we power on */ + switch (mode) { + /* power up everything in the demod */ + case DIB9000_POWER_ALL: + reg_903 = 0x0000; + reg_904 = 0x0000; + reg_905 = 0x0000; + reg_906 = 0x0000; + break; + + /* just leave power on the control-interfaces: GPIO and (I2C or SDIO or SRAM) */ + case DIB9000_POWER_INTERFACE_ONLY: /* TODO power up either SDIO or I2C or SRAM */ + reg_905 &= ~((1 << 7) | (1 << 6) | (1 << 5) | (1 << 2)); + break; + + case DIB9000_POWER_INTERF_ANALOG_AGC: + reg_903 &= ~((1 << 15) | (1 << 14) | (1 << 11) | (1 << 10)); + reg_905 &= ~((1 << 7) | (1 << 6) | (1 << 5) | (1 << 4) | (1 << 2)); + reg_906 &= ~((1 << 0)); + break; + + case DIB9000_POWER_COR4_DINTLV_ICIRM_EQUAL_CFROD: + reg_903 = 0x0000; + reg_904 = 0x801f; + reg_905 = 0x0000; + reg_906 &= ~((1 << 0)); + break; + + case DIB9000_POWER_COR4_CRY_ESRAM_MOUT_NUD: + reg_903 = 0x0000; + reg_904 = 0x8000; + reg_905 = 0x010b; + reg_906 &= ~((1 << 0)); + break; + default: + case DIB9000_POWER_NO: + break; + } + + /* always power down unused parts */ + if (!state->platform.host.mobile_mode) + reg_904 |= (1 << 7) | (1 << 6) | (1 << 4) | (1 << 2) | (1 << 1); + + /* P_sdio_select_clk = 0 on MC and after */ + if (state->revision != 0x4000) + reg_906 <<= 1; + + dib9000_write_word(state, 903 + offset, reg_903); + dib9000_write_word(state, 904 + offset, reg_904); + dib9000_write_word(state, 905 + offset, reg_905); + dib9000_write_word(state, 906 + offset, reg_906); +} + +static int dib9000_fw_reset(struct dvb_frontend *fe) +{ + struct dib9000_state *state = fe->demodulator_priv; + + dib9000_write_word(state, 1817, 0x0003); + + dib9000_write_word(state, 1227, 1); + dib9000_write_word(state, 1227, 0); + + switch ((state->revision = dib9000_identify(&state->i2c))) { + case 0x4003: + case 0x4004: + case 0x4005: + state->reg_offs = 1; + break; + default: + return -EINVAL; + } + + /* reset the i2c-master to use the host interface */ + dibx000_reset_i2c_master(&state->i2c_master); + + dib9000_set_power_mode(state, DIB9000_POWER_ALL); + + /* unforce divstr regardless whether i2c enumeration was done or not */ + dib9000_write_word(state, 1794, dib9000_read_word(state, 1794) & ~(1 << 1)); + dib9000_write_word(state, 1796, 0); + dib9000_write_word(state, 1805, 0x805); + + /* restart all parts */ + dib9000_write_word(state, 898, 0xffff); + dib9000_write_word(state, 899, 0xffff); + dib9000_write_word(state, 900, 0x0001); + dib9000_write_word(state, 901, 0xff19); + dib9000_write_word(state, 902, 0x003c); + + dib9000_write_word(state, 898, 0); + dib9000_write_word(state, 899, 0); + dib9000_write_word(state, 900, 0); + dib9000_write_word(state, 901, 0); + dib9000_write_word(state, 902, 0); + + dib9000_write_word(state, 911, state->chip.d9.cfg.if_drives); + + dib9000_set_power_mode(state, DIB9000_POWER_INTERFACE_ONLY); + + return 0; +} + +static int dib9000_risc_apb_access_read(struct dib9000_state *state, u32 address, u16 attribute, const u8 * tx, u32 txlen, u8 * b, u32 len) +{ + u16 mb[10]; + u8 i, s; + + if (address >= 1024 || !state->platform.risc.fw_is_running) + return -EINVAL; + + /* dprintk( "APB access thru rd fw %d %x", address, attribute); */ + + mb[0] = (u16) address; + mb[1] = len / 2; + dib9000_mbx_send_attr(state, OUT_MSG_BRIDGE_APB_R, mb, 2, attribute); + switch (dib9000_mbx_get_message_attr(state, IN_MSG_END_BRIDGE_APB_RW, mb, &s, attribute)) { + case 1: + s--; + for (i = 0; i < s; i++) { + b[i * 2] = (mb[i + 1] >> 8) & 0xff; + b[i * 2 + 1] = (mb[i + 1]) & 0xff; + } + return 0; + default: + return -EIO; + } + return -EIO; +} + +static int dib9000_risc_apb_access_write(struct dib9000_state *state, u32 address, u16 attribute, const u8 * b, u32 len) +{ + u16 mb[10]; + u8 s, i; + + if (address >= 1024 || !state->platform.risc.fw_is_running) + return -EINVAL; + + /* dprintk( "APB access thru wr fw %d %x", address, attribute); */ + + mb[0] = (unsigned short)address; + for (i = 0; i < len && i < 20; i += 2) + mb[1 + (i / 2)] = (b[i] << 8 | b[i + 1]); + + dib9000_mbx_send_attr(state, OUT_MSG_BRIDGE_APB_W, mb, 1 + len / 2, attribute); + return dib9000_mbx_get_message_attr(state, IN_MSG_END_BRIDGE_APB_RW, mb, &s, attribute) == 1 ? 0 : -EINVAL; +} + +static int dib9000_fw_memmbx_sync(struct dib9000_state *state, u8 i) +{ + u8 index_loop = 10; + + if (!state->platform.risc.fw_is_running) + return 0; + dib9000_risc_mem_write(state, FE_MM_RW_SYNC, &i); + do { + dib9000_risc_mem_read(state, FE_MM_RW_SYNC, &i, 1); + } while (i && index_loop--); + + if (index_loop > 0) + return 0; + return -EIO; +} + +static int dib9000_fw_init(struct dib9000_state *state) +{ + struct dibGPIOFunction *f; + u16 b[40] = { 0 }; + u8 i; + u8 size; + + if (dib9000_fw_boot(state, NULL, 0, state->chip.d9.cfg.microcode_B_fe_buffer, state->chip.d9.cfg.microcode_B_fe_size) != 0) + return -EIO; + + /* initialize the firmware */ + for (i = 0; i < ARRAY_SIZE(state->chip.d9.cfg.gpio_function); i++) { + f = &state->chip.d9.cfg.gpio_function[i]; + if (f->mask) { + switch (f->function) { + case BOARD_GPIO_FUNCTION_COMPONENT_ON: + b[0] = (u16) f->mask; + b[1] = (u16) f->direction; + b[2] = (u16) f->value; + break; + case BOARD_GPIO_FUNCTION_COMPONENT_OFF: + b[3] = (u16) f->mask; + b[4] = (u16) f->direction; + b[5] = (u16) f->value; + break; + } + } + } + if (dib9000_mbx_send(state, OUT_MSG_CONF_GPIO, b, 15) != 0) + return -EIO; + + /* subband */ + b[0] = state->chip.d9.cfg.subband.size; /* type == 0 -> GPIO - PWM not yet supported */ + for (i = 0; i < state->chip.d9.cfg.subband.size; i++) { + b[1 + i * 4] = state->chip.d9.cfg.subband.subband[i].f_mhz; + b[2 + i * 4] = (u16) state->chip.d9.cfg.subband.subband[i].gpio.mask; + b[3 + i * 4] = (u16) state->chip.d9.cfg.subband.subband[i].gpio.direction; + b[4 + i * 4] = (u16) state->chip.d9.cfg.subband.subband[i].gpio.value; + } + b[1 + i * 4] = 0; /* fe_id */ + if (dib9000_mbx_send(state, OUT_MSG_SUBBAND_SEL, b, 2 + 4 * i) != 0) + return -EIO; + + /* 0 - id, 1 - no_of_frontends */ + b[0] = (0 << 8) | 1; + /* 0 = i2c-address demod, 0 = tuner */ + b[1] = (0 << 8) | (0); + b[2] = (u16) (((state->chip.d9.cfg.xtal_clock_khz * 1000) >> 16) & 0xffff); + b[3] = (u16) (((state->chip.d9.cfg.xtal_clock_khz * 1000)) & 0xffff); + b[4] = (u16) ((state->chip.d9.cfg.vcxo_timer >> 16) & 0xffff); + b[5] = (u16) ((state->chip.d9.cfg.vcxo_timer) & 0xffff); + b[6] = (u16) ((state->chip.d9.cfg.timing_frequency >> 16) & 0xffff); + b[7] = (u16) ((state->chip.d9.cfg.timing_frequency) & 0xffff); + b[29] = state->chip.d9.cfg.if_drives; + if (dib9000_mbx_send(state, OUT_MSG_INIT_DEMOD, b, ARRAY_SIZE(b)) != 0) + return -EIO; + + if (dib9000_mbx_send(state, OUT_MSG_FE_FW_DL, NULL, 0) != 0) + return -EIO; + + if (dib9000_mbx_get_message(state, IN_MSG_FE_FW_DL_DONE, b, &size) < 0) + return -EIO; + + if (size > ARRAY_SIZE(b)) { + dprintk("error : firmware returned %dbytes needed but the used buffer has only %dbytes\n Firmware init ABORTED", size, + (int)ARRAY_SIZE(b)); + return -EINVAL; + } + + for (i = 0; i < size; i += 2) { + state->platform.risc.fe_mm[i / 2].addr = b[i + 0]; + state->platform.risc.fe_mm[i / 2].size = b[i + 1]; + } + + return 0; +} + +static void dib9000_fw_set_channel_head(struct dib9000_state *state, struct dvb_frontend_parameters *ch) +{ + u8 b[9]; + u32 freq = state->fe[0]->dtv_property_cache.frequency / 1000; + if (state->fe_id % 2) + freq += 101; + + b[0] = (u8) ((freq >> 0) & 0xff); + b[1] = (u8) ((freq >> 8) & 0xff); + b[2] = (u8) ((freq >> 16) & 0xff); + b[3] = (u8) ((freq >> 24) & 0xff); + b[4] = (u8) ((state->fe[0]->dtv_property_cache.bandwidth_hz / 1000 >> 0) & 0xff); + b[5] = (u8) ((state->fe[0]->dtv_property_cache.bandwidth_hz / 1000 >> 8) & 0xff); + b[6] = (u8) ((state->fe[0]->dtv_property_cache.bandwidth_hz / 1000 >> 16) & 0xff); + b[7] = (u8) ((state->fe[0]->dtv_property_cache.bandwidth_hz / 1000 >> 24) & 0xff); + b[8] = 0x80; /* do not wait for CELL ID when doing autosearch */ + if (state->fe[0]->dtv_property_cache.delivery_system == SYS_DVBT) + b[8] |= 1; + dib9000_risc_mem_write(state, FE_MM_W_CHANNEL_HEAD, b); +} + +static int dib9000_fw_get_channel(struct dvb_frontend *fe, struct dvb_frontend_parameters *channel) +{ + struct dib9000_state *state = fe->demodulator_priv; + struct dibDVBTChannel { + s8 spectrum_inversion; + + s8 nfft; + s8 guard; + s8 constellation; + + s8 hrch; + s8 alpha; + s8 code_rate_hp; + s8 code_rate_lp; + s8 select_hp; + + s8 intlv_native; + }; + struct dibDVBTChannel ch; + int ret = 0; + + DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) { + goto error; + ret = -EIO; + } + + dib9000_risc_mem_read(state, FE_MM_R_CHANNEL_UNION, (u8 *) &ch, sizeof(struct dibDVBTChannel)); + + switch (ch.spectrum_inversion & 0x7) { + case 1: + state->fe[0]->dtv_property_cache.inversion = INVERSION_ON; + break; + case 0: + state->fe[0]->dtv_property_cache.inversion = INVERSION_OFF; + break; + default: + case -1: + state->fe[0]->dtv_property_cache.inversion = INVERSION_AUTO; + break; + } + switch (ch.nfft) { + case 0: + state->fe[0]->dtv_property_cache.transmission_mode = TRANSMISSION_MODE_2K; + break; + case 2: + state->fe[0]->dtv_property_cache.transmission_mode = TRANSMISSION_MODE_4K; + break; + case 1: + state->fe[0]->dtv_property_cache.transmission_mode = TRANSMISSION_MODE_8K; + break; + default: + case -1: + state->fe[0]->dtv_property_cache.transmission_mode = TRANSMISSION_MODE_AUTO; + break; + } + switch (ch.guard) { + case 0: + state->fe[0]->dtv_property_cache.guard_interval = GUARD_INTERVAL_1_32; + break; + case 1: + state->fe[0]->dtv_property_cache.guard_interval = GUARD_INTERVAL_1_16; + break; + case 2: + state->fe[0]->dtv_property_cache.guard_interval = GUARD_INTERVAL_1_8; + break; + case 3: + state->fe[0]->dtv_property_cache.guard_interval = GUARD_INTERVAL_1_4; + break; + default: + case -1: + state->fe[0]->dtv_property_cache.guard_interval = GUARD_INTERVAL_AUTO; + break; + } + switch (ch.constellation) { + case 2: + state->fe[0]->dtv_property_cache.modulation = QAM_64; + break; + case 1: + state->fe[0]->dtv_property_cache.modulation = QAM_16; + break; + case 0: + state->fe[0]->dtv_property_cache.modulation = QPSK; + break; + default: + case -1: + state->fe[0]->dtv_property_cache.modulation = QAM_AUTO; + break; + } + switch (ch.hrch) { + case 0: + state->fe[0]->dtv_property_cache.hierarchy = HIERARCHY_NONE; + break; + case 1: + state->fe[0]->dtv_property_cache.hierarchy = HIERARCHY_1; + break; + default: + case -1: + state->fe[0]->dtv_property_cache.hierarchy = HIERARCHY_AUTO; + break; + } + switch (ch.code_rate_hp) { + case 1: + state->fe[0]->dtv_property_cache.code_rate_HP = FEC_1_2; + break; + case 2: + state->fe[0]->dtv_property_cache.code_rate_HP = FEC_2_3; + break; + case 3: + state->fe[0]->dtv_property_cache.code_rate_HP = FEC_3_4; + break; + case 5: + state->fe[0]->dtv_property_cache.code_rate_HP = FEC_5_6; + break; + case 7: + state->fe[0]->dtv_property_cache.code_rate_HP = FEC_7_8; + break; + default: + case -1: + state->fe[0]->dtv_property_cache.code_rate_HP = FEC_AUTO; + break; + } + switch (ch.code_rate_lp) { + case 1: + state->fe[0]->dtv_property_cache.code_rate_LP = FEC_1_2; + break; + case 2: + state->fe[0]->dtv_property_cache.code_rate_LP = FEC_2_3; + break; + case 3: + state->fe[0]->dtv_property_cache.code_rate_LP = FEC_3_4; + break; + case 5: + state->fe[0]->dtv_property_cache.code_rate_LP = FEC_5_6; + break; + case 7: + state->fe[0]->dtv_property_cache.code_rate_LP = FEC_7_8; + break; + default: + case -1: + state->fe[0]->dtv_property_cache.code_rate_LP = FEC_AUTO; + break; + } + +error: + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + return ret; +} + +static int dib9000_fw_set_channel_union(struct dvb_frontend *fe, struct dvb_frontend_parameters *channel) +{ + struct dib9000_state *state = fe->demodulator_priv; + struct dibDVBTChannel { + s8 spectrum_inversion; + + s8 nfft; + s8 guard; + s8 constellation; + + s8 hrch; + s8 alpha; + s8 code_rate_hp; + s8 code_rate_lp; + s8 select_hp; + + s8 intlv_native; + }; + struct dibDVBTChannel ch; + + switch (state->fe[0]->dtv_property_cache.inversion) { + case INVERSION_ON: + ch.spectrum_inversion = 1; + break; + case INVERSION_OFF: + ch.spectrum_inversion = 0; + break; + default: + case INVERSION_AUTO: + ch.spectrum_inversion = -1; + break; + } + switch (state->fe[0]->dtv_property_cache.transmission_mode) { + case TRANSMISSION_MODE_2K: + ch.nfft = 0; + break; + case TRANSMISSION_MODE_4K: + ch.nfft = 2; + break; + case TRANSMISSION_MODE_8K: + ch.nfft = 1; + break; + default: + case TRANSMISSION_MODE_AUTO: + ch.nfft = 1; + break; + } + switch (state->fe[0]->dtv_property_cache.guard_interval) { + case GUARD_INTERVAL_1_32: + ch.guard = 0; + break; + case GUARD_INTERVAL_1_16: + ch.guard = 1; + break; + case GUARD_INTERVAL_1_8: + ch.guard = 2; + break; + case GUARD_INTERVAL_1_4: + ch.guard = 3; + break; + default: + case GUARD_INTERVAL_AUTO: + ch.guard = -1; + break; + } + switch (state->fe[0]->dtv_property_cache.modulation) { + case QAM_64: + ch.constellation = 2; + break; + case QAM_16: + ch.constellation = 1; + break; + case QPSK: + ch.constellation = 0; + break; + default: + case QAM_AUTO: + ch.constellation = -1; + break; + } + switch (state->fe[0]->dtv_property_cache.hierarchy) { + case HIERARCHY_NONE: + ch.hrch = 0; + break; + case HIERARCHY_1: + case HIERARCHY_2: + case HIERARCHY_4: + ch.hrch = 1; + break; + default: + case HIERARCHY_AUTO: + ch.hrch = -1; + break; + } + ch.alpha = 1; + switch (state->fe[0]->dtv_property_cache.code_rate_HP) { + case FEC_1_2: + ch.code_rate_hp = 1; + break; + case FEC_2_3: + ch.code_rate_hp = 2; + break; + case FEC_3_4: + ch.code_rate_hp = 3; + break; + case FEC_5_6: + ch.code_rate_hp = 5; + break; + case FEC_7_8: + ch.code_rate_hp = 7; + break; + default: + case FEC_AUTO: + ch.code_rate_hp = -1; + break; + } + switch (state->fe[0]->dtv_property_cache.code_rate_LP) { + case FEC_1_2: + ch.code_rate_lp = 1; + break; + case FEC_2_3: + ch.code_rate_lp = 2; + break; + case FEC_3_4: + ch.code_rate_lp = 3; + break; + case FEC_5_6: + ch.code_rate_lp = 5; + break; + case FEC_7_8: + ch.code_rate_lp = 7; + break; + default: + case FEC_AUTO: + ch.code_rate_lp = -1; + break; + } + ch.select_hp = 1; + ch.intlv_native = 1; + + dib9000_risc_mem_write(state, FE_MM_W_CHANNEL_UNION, (u8 *) &ch); + + return 0; +} + +static int dib9000_fw_tune(struct dvb_frontend *fe, struct dvb_frontend_parameters *ch) +{ + struct dib9000_state *state = fe->demodulator_priv; + int ret = 10, search = state->channel_status.status == CHANNEL_STATUS_PARAMETERS_UNKNOWN; + s8 i; + + switch (state->tune_state) { + case CT_DEMOD_START: + dib9000_fw_set_channel_head(state, ch); + + /* write the channel context - a channel is initialized to 0, so it is OK */ + dib9000_risc_mem_write(state, FE_MM_W_CHANNEL_CONTEXT, (u8 *) fe_info); + dib9000_risc_mem_write(state, FE_MM_W_FE_INFO, (u8 *) fe_info); + + if (search) + dib9000_mbx_send(state, OUT_MSG_FE_CHANNEL_SEARCH, NULL, 0); + else { + dib9000_fw_set_channel_union(fe, ch); + dib9000_mbx_send(state, OUT_MSG_FE_CHANNEL_TUNE, NULL, 0); + } + state->tune_state = CT_DEMOD_STEP_1; + break; + case CT_DEMOD_STEP_1: + if (search) + dib9000_risc_mem_read(state, FE_MM_R_CHANNEL_SEARCH_STATE, (u8 *) &i, 1); + else + dib9000_risc_mem_read(state, FE_MM_R_CHANNEL_TUNE_STATE, (u8 *) &i, 1); + switch (i) { /* something happened */ + case 0: + break; + case -2: /* tps locks are "slower" than MPEG locks -> even in autosearch data is OK here */ + if (search) + state->status = FE_STATUS_DEMOD_SUCCESS; + else { + state->tune_state = CT_DEMOD_STOP; + state->status = FE_STATUS_LOCKED; + } + break; + default: + state->status = FE_STATUS_TUNE_FAILED; + state->tune_state = CT_DEMOD_STOP; + break; + } + break; + default: + ret = FE_CALLBACK_TIME_NEVER; + break; + } + + return ret; +} + +static int dib9000_fw_set_diversity_in(struct dvb_frontend *fe, int onoff) +{ + struct dib9000_state *state = fe->demodulator_priv; + u16 mode = (u16) onoff; + return dib9000_mbx_send(state, OUT_MSG_ENABLE_DIVERSITY, &mode, 1); +} + +static int dib9000_fw_set_output_mode(struct dvb_frontend *fe, int mode) +{ + struct dib9000_state *state = fe->demodulator_priv; + u16 outreg, smo_mode; + + dprintk("setting output mode for demod %p to %d", fe, mode); + + switch (mode) { + case OUTMODE_MPEG2_PAR_GATED_CLK: + outreg = (1 << 10); /* 0x0400 */ + break; + case OUTMODE_MPEG2_PAR_CONT_CLK: + outreg = (1 << 10) | (1 << 6); /* 0x0440 */ + break; + case OUTMODE_MPEG2_SERIAL: + outreg = (1 << 10) | (2 << 6) | (0 << 1); /* 0x0482 */ + break; + case OUTMODE_DIVERSITY: + outreg = (1 << 10) | (4 << 6); /* 0x0500 */ + break; + case OUTMODE_MPEG2_FIFO: + outreg = (1 << 10) | (5 << 6); + break; + case OUTMODE_HIGH_Z: + outreg = 0; + break; + default: + dprintk("Unhandled output_mode passed to be set for demod %p", &state->fe[0]); + return -EINVAL; + } + + dib9000_write_word(state, 1795, outreg); + + switch (mode) { + case OUTMODE_MPEG2_PAR_GATED_CLK: + case OUTMODE_MPEG2_PAR_CONT_CLK: + case OUTMODE_MPEG2_SERIAL: + case OUTMODE_MPEG2_FIFO: + smo_mode = (dib9000_read_word(state, 295) & 0x0010) | (1 << 1); + if (state->chip.d9.cfg.output_mpeg2_in_188_bytes) + smo_mode |= (1 << 5); + dib9000_write_word(state, 295, smo_mode); + break; + } + + outreg = to_fw_output_mode(mode); + return dib9000_mbx_send(state, OUT_MSG_SET_OUTPUT_MODE, &outreg, 1); +} + +static int dib9000_tuner_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + struct dib9000_state *state = i2c_get_adapdata(i2c_adap); + u16 i, len, t, index_msg; + + for (index_msg = 0; index_msg < num; index_msg++) { + if (msg[index_msg].flags & I2C_M_RD) { /* read */ + len = msg[index_msg].len; + if (len > 16) + len = 16; + + if (dib9000_read_word(state, 790) != 0) + dprintk("TunerITF: read busy"); + + dib9000_write_word(state, 784, (u16) (msg[index_msg].addr)); + dib9000_write_word(state, 787, (len / 2) - 1); + dib9000_write_word(state, 786, 1); /* start read */ + + i = 1000; + while (dib9000_read_word(state, 790) != (len / 2) && i) + i--; + + if (i == 0) + dprintk("TunerITF: read failed"); + + for (i = 0; i < len; i += 2) { + t = dib9000_read_word(state, 785); + msg[index_msg].buf[i] = (t >> 8) & 0xff; + msg[index_msg].buf[i + 1] = (t) & 0xff; + } + if (dib9000_read_word(state, 790) != 0) + dprintk("TunerITF: read more data than expected"); + } else { + i = 1000; + while (dib9000_read_word(state, 789) && i) + i--; + if (i == 0) + dprintk("TunerITF: write busy"); + + len = msg[index_msg].len; + if (len > 16) + len = 16; + + for (i = 0; i < len; i += 2) + dib9000_write_word(state, 785, (msg[index_msg].buf[i] << 8) | msg[index_msg].buf[i + 1]); + dib9000_write_word(state, 784, (u16) msg[index_msg].addr); + dib9000_write_word(state, 787, (len / 2) - 1); + dib9000_write_word(state, 786, 0); /* start write */ + + i = 1000; + while (dib9000_read_word(state, 791) > 0 && i) + i--; + if (i == 0) + dprintk("TunerITF: write failed"); + } + } + return num; +} + +int dib9000_fw_set_component_bus_speed(struct dvb_frontend *fe, u16 speed) +{ + struct dib9000_state *state = fe->demodulator_priv; + + state->component_bus_speed = speed; + return 0; +} +EXPORT_SYMBOL(dib9000_fw_set_component_bus_speed); + +static int dib9000_fw_component_bus_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + struct dib9000_state *state = i2c_get_adapdata(i2c_adap); + u8 type = 0; /* I2C */ + u8 port = DIBX000_I2C_INTERFACE_GPIO_3_4; + u16 scl = state->component_bus_speed; /* SCL frequency */ + struct dib9000_fe_memory_map *m = &state->platform.risc.fe_mm[FE_MM_RW_COMPONENT_ACCESS_BUFFER]; + u8 p[13] = { 0 }; + + p[0] = type; + p[1] = port; + p[2] = msg[0].addr << 1; + + p[3] = (u8) scl & 0xff; /* scl */ + p[4] = (u8) (scl >> 8); + + p[7] = 0; + p[8] = 0; + + p[9] = (u8) (msg[0].len); + p[10] = (u8) (msg[0].len >> 8); + if ((num > 1) && (msg[1].flags & I2C_M_RD)) { + p[11] = (u8) (msg[1].len); + p[12] = (u8) (msg[1].len >> 8); + } else { + p[11] = 0; + p[12] = 0; + } + + DibAcquireLock(&state->platform.risc.mem_mbx_lock); + + dib9000_risc_mem_write(state, FE_MM_W_COMPONENT_ACCESS, p); + + { /* write-part */ + dib9000_risc_mem_setup_cmd(state, m->addr, msg[0].len, 0); + dib9000_risc_mem_write_chunks(state, msg[0].buf, msg[0].len); + } + + /* do the transaction */ + if (dib9000_fw_memmbx_sync(state, FE_SYNC_COMPONENT_ACCESS) < 0) { + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + return 0; + } + + /* read back any possible result */ + if ((num > 1) && (msg[1].flags & I2C_M_RD)) + dib9000_risc_mem_read(state, FE_MM_RW_COMPONENT_ACCESS_BUFFER, msg[1].buf, msg[1].len); + + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + + return num; +} + +static u32 dib9000_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} + +static struct i2c_algorithm dib9000_tuner_algo = { + .master_xfer = dib9000_tuner_xfer, + .functionality = dib9000_i2c_func, +}; + +static struct i2c_algorithm dib9000_component_bus_algo = { + .master_xfer = dib9000_fw_component_bus_xfer, + .functionality = dib9000_i2c_func, +}; + +struct i2c_adapter *dib9000_get_tuner_interface(struct dvb_frontend *fe) +{ + struct dib9000_state *st = fe->demodulator_priv; + return &st->tuner_adap; +} +EXPORT_SYMBOL(dib9000_get_tuner_interface); + +struct i2c_adapter *dib9000_get_component_bus_interface(struct dvb_frontend *fe) +{ + struct dib9000_state *st = fe->demodulator_priv; + return &st->component_bus; +} +EXPORT_SYMBOL(dib9000_get_component_bus_interface); + +struct i2c_adapter *dib9000_get_i2c_master(struct dvb_frontend *fe, enum dibx000_i2c_interface intf, int gating) +{ + struct dib9000_state *st = fe->demodulator_priv; + return dibx000_get_i2c_adapter(&st->i2c_master, intf, gating); +} +EXPORT_SYMBOL(dib9000_get_i2c_master); + +int dib9000_set_i2c_adapter(struct dvb_frontend *fe, struct i2c_adapter *i2c) +{ + struct dib9000_state *st = fe->demodulator_priv; + + st->i2c.i2c_adap = i2c; + return 0; +} +EXPORT_SYMBOL(dib9000_set_i2c_adapter); + +static int dib9000_cfg_gpio(struct dib9000_state *st, u8 num, u8 dir, u8 val) +{ + st->gpio_dir = dib9000_read_word(st, 773); + st->gpio_dir &= ~(1 << num); /* reset the direction bit */ + st->gpio_dir |= (dir & 0x1) << num; /* set the new direction */ + dib9000_write_word(st, 773, st->gpio_dir); + + st->gpio_val = dib9000_read_word(st, 774); + st->gpio_val &= ~(1 << num); /* reset the direction bit */ + st->gpio_val |= (val & 0x01) << num; /* set the new value */ + dib9000_write_word(st, 774, st->gpio_val); + + dprintk("gpio dir: %04x: gpio val: %04x", st->gpio_dir, st->gpio_val); + + return 0; +} + +int dib9000_set_gpio(struct dvb_frontend *fe, u8 num, u8 dir, u8 val) +{ + struct dib9000_state *state = fe->demodulator_priv; + return dib9000_cfg_gpio(state, num, dir, val); +} +EXPORT_SYMBOL(dib9000_set_gpio); + +int dib9000_fw_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff) +{ + struct dib9000_state *state = fe->demodulator_priv; + u16 val = dib9000_read_word(state, 294 + 1) & 0xffef; + val |= (onoff & 0x1) << 4; + + dprintk("PID filter enabled %d", onoff); + return dib9000_write_word(state, 294 + 1, val); +} +EXPORT_SYMBOL(dib9000_fw_pid_filter_ctrl); + +int dib9000_fw_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff) +{ + struct dib9000_state *state = fe->demodulator_priv; + dprintk("Index %x, PID %d, OnOff %d", id, pid, onoff); + return dib9000_write_word(state, 300 + 1 + id, onoff ? (1 << 13) | pid : 0); +} +EXPORT_SYMBOL(dib9000_fw_pid_filter); + +int dib9000_firmware_post_pll_init(struct dvb_frontend *fe) +{ + struct dib9000_state *state = fe->demodulator_priv; + return dib9000_fw_init(state); +} +EXPORT_SYMBOL(dib9000_firmware_post_pll_init); + +static void dib9000_release(struct dvb_frontend *demod) +{ + struct dib9000_state *st = demod->demodulator_priv; + u8 index_frontend; + + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (st->fe[index_frontend] != NULL); index_frontend++) + dvb_frontend_detach(st->fe[index_frontend]); + + DibFreeLock(&state->platform.risc.mbx_if_lock); + DibFreeLock(&state->platform.risc.mbx_lock); + DibFreeLock(&state->platform.risc.mem_lock); + DibFreeLock(&state->platform.risc.mem_mbx_lock); + dibx000_exit_i2c_master(&st->i2c_master); + + i2c_del_adapter(&st->tuner_adap); + i2c_del_adapter(&st->component_bus); + kfree(st->fe[0]); + kfree(st); +} + +static int dib9000_wakeup(struct dvb_frontend *fe) +{ + return 0; +} + +static int dib9000_sleep(struct dvb_frontend *fe) +{ + struct dib9000_state *state = fe->demodulator_priv; + u8 index_frontend; + int ret; + + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + ret = state->fe[index_frontend]->ops.sleep(state->fe[index_frontend]); + if (ret < 0) + return ret; + } + return dib9000_mbx_send(state, OUT_MSG_FE_SLEEP, NULL, 0); +} + +static int dib9000_fe_get_tune_settings(struct dvb_frontend *fe, struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 1000; + return 0; +} + +static int dib9000_get_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) +{ + struct dib9000_state *state = fe->demodulator_priv; + u8 index_frontend, sub_index_frontend; + fe_status_t stat; + int ret; + + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + state->fe[index_frontend]->ops.read_status(state->fe[index_frontend], &stat); + if (stat & FE_HAS_SYNC) { + dprintk("TPS lock on the slave%i", index_frontend); + + /* synchronize the cache with the other frontends */ + state->fe[index_frontend]->ops.get_frontend(state->fe[index_frontend], fep); + for (sub_index_frontend = 0; (sub_index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[sub_index_frontend] != NULL); + sub_index_frontend++) { + if (sub_index_frontend != index_frontend) { + state->fe[sub_index_frontend]->dtv_property_cache.modulation = + state->fe[index_frontend]->dtv_property_cache.modulation; + state->fe[sub_index_frontend]->dtv_property_cache.inversion = + state->fe[index_frontend]->dtv_property_cache.inversion; + state->fe[sub_index_frontend]->dtv_property_cache.transmission_mode = + state->fe[index_frontend]->dtv_property_cache.transmission_mode; + state->fe[sub_index_frontend]->dtv_property_cache.guard_interval = + state->fe[index_frontend]->dtv_property_cache.guard_interval; + state->fe[sub_index_frontend]->dtv_property_cache.hierarchy = + state->fe[index_frontend]->dtv_property_cache.hierarchy; + state->fe[sub_index_frontend]->dtv_property_cache.code_rate_HP = + state->fe[index_frontend]->dtv_property_cache.code_rate_HP; + state->fe[sub_index_frontend]->dtv_property_cache.code_rate_LP = + state->fe[index_frontend]->dtv_property_cache.code_rate_LP; + state->fe[sub_index_frontend]->dtv_property_cache.rolloff = + state->fe[index_frontend]->dtv_property_cache.rolloff; + } + } + return 0; + } + } + + /* get the channel from master chip */ + ret = dib9000_fw_get_channel(fe, fep); + if (ret != 0) + return ret; + + /* synchronize the cache with the other frontends */ + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + state->fe[index_frontend]->dtv_property_cache.inversion = fe->dtv_property_cache.inversion; + state->fe[index_frontend]->dtv_property_cache.transmission_mode = fe->dtv_property_cache.transmission_mode; + state->fe[index_frontend]->dtv_property_cache.guard_interval = fe->dtv_property_cache.guard_interval; + state->fe[index_frontend]->dtv_property_cache.modulation = fe->dtv_property_cache.modulation; + state->fe[index_frontend]->dtv_property_cache.hierarchy = fe->dtv_property_cache.hierarchy; + state->fe[index_frontend]->dtv_property_cache.code_rate_HP = fe->dtv_property_cache.code_rate_HP; + state->fe[index_frontend]->dtv_property_cache.code_rate_LP = fe->dtv_property_cache.code_rate_LP; + state->fe[index_frontend]->dtv_property_cache.rolloff = fe->dtv_property_cache.rolloff; + } + + return 0; +} + +static int dib9000_set_tune_state(struct dvb_frontend *fe, enum frontend_tune_state tune_state) +{ + struct dib9000_state *state = fe->demodulator_priv; + state->tune_state = tune_state; + if (tune_state == CT_DEMOD_START) + state->status = FE_STATUS_TUNE_PENDING; + + return 0; +} + +static u32 dib9000_get_status(struct dvb_frontend *fe) +{ + struct dib9000_state *state = fe->demodulator_priv; + return state->status; +} + +static int dib9000_set_channel_status(struct dvb_frontend *fe, struct dvb_frontend_parametersContext *channel_status) +{ + struct dib9000_state *state = fe->demodulator_priv; + + memcpy(&state->channel_status, channel_status, sizeof(struct dvb_frontend_parametersContext)); + return 0; +} + +static int dib9000_set_frontend(struct dvb_frontend *fe, struct dvb_frontend_parameters *fep) +{ + struct dib9000_state *state = fe->demodulator_priv; + int sleep_time, sleep_time_slave; + u32 frontend_status; + u8 nbr_pending, exit_condition, index_frontend, index_frontend_success; + struct dvb_frontend_parametersContext channel_status; + + /* check that the correct parameters are set */ + if (state->fe[0]->dtv_property_cache.frequency == 0) { + dprintk("dib9000: must specify frequency "); + return 0; + } + + if (state->fe[0]->dtv_property_cache.bandwidth_hz == 0) { + dprintk("dib9000: must specify bandwidth "); + return 0; + } + fe->dtv_property_cache.delivery_system = SYS_DVBT; + + /* set the master status */ + if (fep->u.ofdm.transmission_mode == TRANSMISSION_MODE_AUTO || + fep->u.ofdm.guard_interval == GUARD_INTERVAL_AUTO || fep->u.ofdm.constellation == QAM_AUTO || fep->u.ofdm.code_rate_HP == FEC_AUTO) { + /* no channel specified, autosearch the channel */ + state->channel_status.status = CHANNEL_STATUS_PARAMETERS_UNKNOWN; + } else + state->channel_status.status = CHANNEL_STATUS_PARAMETERS_SET; + + /* set mode and status for the different frontends */ + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + dib9000_fw_set_diversity_in(state->fe[index_frontend], 1); + + /* synchronization of the cache */ + memcpy(&state->fe[index_frontend]->dtv_property_cache, &fe->dtv_property_cache, sizeof(struct dtv_frontend_properties)); + + state->fe[index_frontend]->dtv_property_cache.delivery_system = SYS_DVBT; + dib9000_fw_set_output_mode(state->fe[index_frontend], OUTMODE_HIGH_Z); + + dib9000_set_channel_status(state->fe[index_frontend], &state->channel_status); + dib9000_set_tune_state(state->fe[index_frontend], CT_DEMOD_START); + } + + /* actual tune */ + exit_condition = 0; /* 0: tune pending; 1: tune failed; 2:tune success */ + index_frontend_success = 0; + do { + sleep_time = dib9000_fw_tune(state->fe[0], NULL); + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + sleep_time_slave = dib9000_fw_tune(state->fe[index_frontend], NULL); + if (sleep_time == FE_CALLBACK_TIME_NEVER) + sleep_time = sleep_time_slave; + else if ((sleep_time_slave != FE_CALLBACK_TIME_NEVER) && (sleep_time_slave > sleep_time)) + sleep_time = sleep_time_slave; + } + if (sleep_time != FE_CALLBACK_TIME_NEVER) + msleep(sleep_time / 10); + else + break; + + nbr_pending = 0; + exit_condition = 0; + index_frontend_success = 0; + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + frontend_status = -dib9000_get_status(state->fe[index_frontend]); + if (frontend_status > -FE_STATUS_TUNE_PENDING) { + exit_condition = 2; /* tune success */ + index_frontend_success = index_frontend; + break; + } + if (frontend_status == -FE_STATUS_TUNE_PENDING) + nbr_pending++; /* some frontends are still tuning */ + } + if ((exit_condition != 2) && (nbr_pending == 0)) + exit_condition = 1; /* if all tune are done and no success, exit: tune failed */ + + } while (exit_condition == 0); + + /* check the tune result */ + if (exit_condition == 1) { /* tune failed */ + dprintk("tune failed"); + return 0; + } + + dprintk("tune success on frontend%i", index_frontend_success); + + /* synchronize all the channel cache */ + dib9000_get_frontend(state->fe[0], fep); + + /* retune the other frontends with the found channel */ + channel_status.status = CHANNEL_STATUS_PARAMETERS_SET; + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + /* only retune the frontends which was not tuned success */ + if (index_frontend != index_frontend_success) { + dib9000_set_channel_status(state->fe[index_frontend], &channel_status); + dib9000_set_tune_state(state->fe[index_frontend], CT_DEMOD_START); + } + } + do { + sleep_time = FE_CALLBACK_TIME_NEVER; + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + if (index_frontend != index_frontend_success) { + sleep_time_slave = dib9000_fw_tune(state->fe[index_frontend], NULL); + if (sleep_time == FE_CALLBACK_TIME_NEVER) + sleep_time = sleep_time_slave; + else if ((sleep_time_slave != FE_CALLBACK_TIME_NEVER) && (sleep_time_slave > sleep_time)) + sleep_time = sleep_time_slave; + } + } + if (sleep_time != FE_CALLBACK_TIME_NEVER) + msleep(sleep_time / 10); + else + break; + + nbr_pending = 0; + for (index_frontend = 0; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + if (index_frontend != index_frontend_success) { + frontend_status = -dib9000_get_status(state->fe[index_frontend]); + if ((index_frontend != index_frontend_success) && (frontend_status == -FE_STATUS_TUNE_PENDING)) + nbr_pending++; /* some frontends are still tuning */ + } + } + } while (nbr_pending != 0); + + /* set the output mode */ + dib9000_fw_set_output_mode(state->fe[0], state->chip.d9.cfg.output_mode); + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) + dib9000_fw_set_output_mode(state->fe[index_frontend], OUTMODE_DIVERSITY); + + /* turn off the diversity for the last frontend */ + dib9000_fw_set_diversity_in(state->fe[index_frontend - 1], 0); + + return 0; +} + +static u16 dib9000_read_lock(struct dvb_frontend *fe) +{ + struct dib9000_state *state = fe->demodulator_priv; + + return dib9000_read_word(state, 535); +} + +static int dib9000_read_status(struct dvb_frontend *fe, fe_status_t * stat) +{ + struct dib9000_state *state = fe->demodulator_priv; + u8 index_frontend; + u16 lock = 0, lock_slave = 0; + + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) + lock_slave |= dib9000_read_lock(state->fe[index_frontend]); + + lock = dib9000_read_word(state, 535); + + *stat = 0; + + if ((lock & 0x8000) || (lock_slave & 0x8000)) + *stat |= FE_HAS_SIGNAL; + if ((lock & 0x3000) || (lock_slave & 0x3000)) + *stat |= FE_HAS_CARRIER; + if ((lock & 0x0100) || (lock_slave & 0x0100)) + *stat |= FE_HAS_VITERBI; + if (((lock & 0x0038) == 0x38) || ((lock_slave & 0x0038) == 0x38)) + *stat |= FE_HAS_SYNC; + if ((lock & 0x0008) || (lock_slave & 0x0008)) + *stat |= FE_HAS_LOCK; + + return 0; +} + +static int dib9000_read_ber(struct dvb_frontend *fe, u32 * ber) +{ + struct dib9000_state *state = fe->demodulator_priv; + u16 c[16]; + + DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) + return -EIO; + dib9000_risc_mem_read(state, FE_MM_R_FE_MONITOR, (u8 *) c, sizeof(c)); + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + + *ber = c[10] << 16 | c[11]; + return 0; +} + +static int dib9000_read_signal_strength(struct dvb_frontend *fe, u16 * strength) +{ + struct dib9000_state *state = fe->demodulator_priv; + u8 index_frontend; + u16 c[16]; + u16 val; + + *strength = 0; + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) { + state->fe[index_frontend]->ops.read_signal_strength(state->fe[index_frontend], &val); + if (val > 65535 - *strength) + *strength = 65535; + else + *strength += val; + } + + DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) + return -EIO; + dib9000_risc_mem_read(state, FE_MM_R_FE_MONITOR, (u8 *) c, sizeof(c)); + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + + val = 65535 - c[4]; + if (val > 65535 - *strength) + *strength = 65535; + else + *strength += val; + return 0; +} + +static u32 dib9000_get_snr(struct dvb_frontend *fe) +{ + struct dib9000_state *state = fe->demodulator_priv; + u16 c[16]; + u32 n, s, exp; + u16 val; + + DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) + return -EIO; + dib9000_risc_mem_read(state, FE_MM_R_FE_MONITOR, (u8 *) c, sizeof(c)); + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + + val = c[7]; + n = (val >> 4) & 0xff; + exp = ((val & 0xf) << 2); + val = c[8]; + exp += ((val >> 14) & 0x3); + if ((exp & 0x20) != 0) + exp -= 0x40; + n <<= exp + 16; + + s = (val >> 6) & 0xFF; + exp = (val & 0x3F); + if ((exp & 0x20) != 0) + exp -= 0x40; + s <<= exp + 16; + + if (n > 0) { + u32 t = (s / n) << 16; + return t + ((s << 16) - n * t) / n; + } + return 0xffffffff; +} + +static int dib9000_read_snr(struct dvb_frontend *fe, u16 * snr) +{ + struct dib9000_state *state = fe->demodulator_priv; + u8 index_frontend; + u32 snr_master; + + snr_master = dib9000_get_snr(fe); + for (index_frontend = 1; (index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL); index_frontend++) + snr_master += dib9000_get_snr(state->fe[index_frontend]); + + if ((snr_master >> 16) != 0) { + snr_master = 10 * intlog10(snr_master >> 16); + *snr = snr_master / ((1 << 24) / 10); + } else + *snr = 0; + + return 0; +} + +static int dib9000_read_unc_blocks(struct dvb_frontend *fe, u32 * unc) +{ + struct dib9000_state *state = fe->demodulator_priv; + u16 c[16]; + + DibAcquireLock(&state->platform.risc.mem_mbx_lock); + if (dib9000_fw_memmbx_sync(state, FE_SYNC_CHANNEL) < 0) + return -EIO; + dib9000_risc_mem_read(state, FE_MM_R_FE_MONITOR, (u8 *) c, sizeof(c)); + DibReleaseLock(&state->platform.risc.mem_mbx_lock); + + *unc = c[12]; + return 0; +} + +int dib9000_i2c_enumeration(struct i2c_adapter *i2c, int no_of_demods, u8 default_addr, u8 first_addr) +{ + int k = 0; + u8 new_addr = 0; + struct i2c_device client = {.i2c_adap = i2c }; + + client.i2c_addr = default_addr + 16; + dib9000_i2c_write16(&client, 1796, 0x0); + + for (k = no_of_demods - 1; k >= 0; k--) { + /* designated i2c address */ + new_addr = first_addr + (k << 1); + client.i2c_addr = default_addr; + + dib9000_i2c_write16(&client, 1817, 3); + dib9000_i2c_write16(&client, 1796, 0); + dib9000_i2c_write16(&client, 1227, 1); + dib9000_i2c_write16(&client, 1227, 0); + + client.i2c_addr = new_addr; + dib9000_i2c_write16(&client, 1817, 3); + dib9000_i2c_write16(&client, 1796, 0); + dib9000_i2c_write16(&client, 1227, 1); + dib9000_i2c_write16(&client, 1227, 0); + + if (dib9000_identify(&client) == 0) { + client.i2c_addr = default_addr; + if (dib9000_identify(&client) == 0) { + dprintk("DiB9000 #%d: not identified", k); + return -EIO; + } + } + + dib9000_i2c_write16(&client, 1795, (1 << 10) | (4 << 6)); + dib9000_i2c_write16(&client, 1794, (new_addr << 2) | 2); + + dprintk("IC %d initialized (to i2c_address 0x%x)", k, new_addr); + } + + for (k = 0; k < no_of_demods; k++) { + new_addr = first_addr | (k << 1); + client.i2c_addr = new_addr; + + dib9000_i2c_write16(&client, 1794, (new_addr << 2)); + dib9000_i2c_write16(&client, 1795, 0); + } + + return 0; +} +EXPORT_SYMBOL(dib9000_i2c_enumeration); + +int dib9000_set_slave_frontend(struct dvb_frontend *fe, struct dvb_frontend *fe_slave) +{ + struct dib9000_state *state = fe->demodulator_priv; + u8 index_frontend = 1; + + while ((index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL)) + index_frontend++; + if (index_frontend < MAX_NUMBER_OF_FRONTENDS) { + dprintk("set slave fe %p to index %i", fe_slave, index_frontend); + state->fe[index_frontend] = fe_slave; + return 0; + } + + dprintk("too many slave frontend"); + return -ENOMEM; +} +EXPORT_SYMBOL(dib9000_set_slave_frontend); + +int dib9000_remove_slave_frontend(struct dvb_frontend *fe) +{ + struct dib9000_state *state = fe->demodulator_priv; + u8 index_frontend = 1; + + while ((index_frontend < MAX_NUMBER_OF_FRONTENDS) && (state->fe[index_frontend] != NULL)) + index_frontend++; + if (index_frontend != 1) { + dprintk("remove slave fe %p (index %i)", state->fe[index_frontend - 1], index_frontend - 1); + state->fe[index_frontend] = NULL; + return 0; + } + + dprintk("no frontend to be removed"); + return -ENODEV; +} +EXPORT_SYMBOL(dib9000_remove_slave_frontend); + +struct dvb_frontend *dib9000_get_slave_frontend(struct dvb_frontend *fe, int slave_index) +{ + struct dib9000_state *state = fe->demodulator_priv; + + if (slave_index >= MAX_NUMBER_OF_FRONTENDS) + return NULL; + return state->fe[slave_index]; +} +EXPORT_SYMBOL(dib9000_get_slave_frontend); + +static struct dvb_frontend_ops dib9000_ops; +struct dvb_frontend *dib9000_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, const struct dib9000_config *cfg) +{ + struct dvb_frontend *fe; + struct dib9000_state *st; + st = kzalloc(sizeof(struct dib9000_state), GFP_KERNEL); + if (st == NULL) + return NULL; + fe = kzalloc(sizeof(struct dvb_frontend), GFP_KERNEL); + if (fe == NULL) + return NULL; + + memcpy(&st->chip.d9.cfg, cfg, sizeof(struct dib9000_config)); + st->i2c.i2c_adap = i2c_adap; + st->i2c.i2c_addr = i2c_addr; + + st->gpio_dir = DIB9000_GPIO_DEFAULT_DIRECTIONS; + st->gpio_val = DIB9000_GPIO_DEFAULT_VALUES; + st->gpio_pwm_pos = DIB9000_GPIO_DEFAULT_PWM_POS; + + DibInitLock(&st->platform.risc.mbx_if_lock); + DibInitLock(&st->platform.risc.mbx_lock); + DibInitLock(&st->platform.risc.mem_lock); + DibInitLock(&st->platform.risc.mem_mbx_lock); + + st->fe[0] = fe; + fe->demodulator_priv = st; + memcpy(&st->fe[0]->ops, &dib9000_ops, sizeof(struct dvb_frontend_ops)); + + /* Ensure the output mode remains at the previous default if it's + * not specifically set by the caller. + */ + if ((st->chip.d9.cfg.output_mode != OUTMODE_MPEG2_SERIAL) && (st->chip.d9.cfg.output_mode != OUTMODE_MPEG2_PAR_GATED_CLK)) + st->chip.d9.cfg.output_mode = OUTMODE_MPEG2_FIFO; + + if (dib9000_identify(&st->i2c) == 0) + goto error; + + dibx000_init_i2c_master(&st->i2c_master, DIB7000MC, st->i2c.i2c_adap, st->i2c.i2c_addr); + + st->tuner_adap.dev.parent = i2c_adap->dev.parent; + strncpy(st->tuner_adap.name, "DIB9000_FW TUNER ACCESS", sizeof(st->tuner_adap.name)); + st->tuner_adap.algo = &dib9000_tuner_algo; + st->tuner_adap.algo_data = NULL; + i2c_set_adapdata(&st->tuner_adap, st); + if (i2c_add_adapter(&st->tuner_adap) < 0) + goto error; + + st->component_bus.dev.parent = i2c_adap->dev.parent; + strncpy(st->component_bus.name, "DIB9000_FW COMPONENT BUS ACCESS", sizeof(st->component_bus.name)); + st->component_bus.algo = &dib9000_component_bus_algo; + st->component_bus.algo_data = NULL; + st->component_bus_speed = 340; + i2c_set_adapdata(&st->component_bus, st); + if (i2c_add_adapter(&st->component_bus) < 0) + goto component_bus_add_error; + + dib9000_fw_reset(fe); + + return fe; + +component_bus_add_error: + i2c_del_adapter(&st->tuner_adap); +error: + kfree(st); + return NULL; +} +EXPORT_SYMBOL(dib9000_attach); + +static struct dvb_frontend_ops dib9000_ops = { + .info = { + .name = "DiBcom 9000", + .type = FE_OFDM, + .frequency_min = 44250000, + .frequency_max = 867250000, + .frequency_stepsize = 62500, + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | FE_CAN_QAM_AUTO | + FE_CAN_TRANSMISSION_MODE_AUTO | FE_CAN_GUARD_INTERVAL_AUTO | FE_CAN_RECOVER | FE_CAN_HIERARCHY_AUTO, + }, + + .release = dib9000_release, + + .init = dib9000_wakeup, + .sleep = dib9000_sleep, + + .set_frontend = dib9000_set_frontend, + .get_tune_settings = dib9000_fe_get_tune_settings, + .get_frontend = dib9000_get_frontend, + + .read_status = dib9000_read_status, + .read_ber = dib9000_read_ber, + .read_signal_strength = dib9000_read_signal_strength, + .read_snr = dib9000_read_snr, + .read_ucblocks = dib9000_read_unc_blocks, +}; + +MODULE_AUTHOR("Patrick Boettcher <pboettcher@dibcom.fr>"); +MODULE_AUTHOR("Olivier Grenie <ogrenie@dibcom.fr>"); +MODULE_DESCRIPTION("Driver for the DiBcom 9000 COFDM demodulator"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/dvb/frontends/dib9000.h b/drivers/media/dvb/frontends/dib9000.h new file mode 100644 index 000000000000..b5781a48034c --- /dev/null +++ b/drivers/media/dvb/frontends/dib9000.h @@ -0,0 +1,131 @@ +#ifndef DIB9000_H +#define DIB9000_H + +#include "dibx000_common.h" + +struct dib9000_config { + u8 dvbt_mode; + u8 output_mpeg2_in_188_bytes; + u8 hostbus_diversity; + struct dibx000_bandwidth_config *bw; + + u16 if_drives; + + u32 timing_frequency; + u32 xtal_clock_khz; + u32 vcxo_timer; + u32 demod_clock_khz; + + const u8 *microcode_B_fe_buffer; + u32 microcode_B_fe_size; + + struct dibGPIOFunction gpio_function[2]; + struct dibSubbandSelection subband; + + u8 output_mode; +}; + +#define DEFAULT_DIB9000_I2C_ADDRESS 18 + +#if defined(CONFIG_DVB_DIB9000) || (defined(CONFIG_DVB_DIB9000_MODULE) && defined(MODULE)) +extern struct dvb_frontend *dib9000_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, const struct dib9000_config *cfg); +extern int dib9000_i2c_enumeration(struct i2c_adapter *host, int no_of_demods, u8 default_addr, u8 first_addr); +extern struct i2c_adapter *dib9000_get_tuner_interface(struct dvb_frontend *fe); +extern struct i2c_adapter *dib9000_get_i2c_master(struct dvb_frontend *fe, enum dibx000_i2c_interface intf, int gating); +extern int dib9000_set_gpio(struct dvb_frontend *fe, u8 num, u8 dir, u8 val); +extern int dib9000_fw_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff); +extern int dib9000_fw_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff); +extern int dib9000_firmware_post_pll_init(struct dvb_frontend *fe); +extern int dib9000_set_slave_frontend(struct dvb_frontend *fe, struct dvb_frontend *fe_slave); +extern int dib9000_remove_slave_frontend(struct dvb_frontend *fe); +extern struct dvb_frontend *dib9000_get_slave_frontend(struct dvb_frontend *fe, int slave_index); +extern struct i2c_adapter *dib9000_get_component_bus_interface(struct dvb_frontend *fe); +extern int dib9000_set_i2c_adapter(struct dvb_frontend *fe, struct i2c_adapter *i2c); +extern int dib9000_fw_set_component_bus_speed(struct dvb_frontend *fe, u16 speed); +#else +static inline struct dvb_frontend *dib9000_attach(struct i2c_adapter *i2c_adap, u8 i2c_addr, struct dib9000_config *cfg) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline struct i2c_adapter *dib9000_get_i2c_master(struct dvb_frontend *fe, enum dibx000_i2c_interface intf, int gating) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline int dib9000_i2c_enumeration(struct i2c_adapter *host, int no_of_demods, u8 default_addr, u8 first_addr) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline struct i2c_adapter *dib9000_get_tuner_interface(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline int dib9000_set_gpio(struct dvb_frontend *fe, u8 num, u8 dir, u8 val) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib9000_fw_pid_filter_ctrl(struct dvb_frontend *fe, u8 onoff) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib9000_fw_pid_filter(struct dvb_frontend *fe, u8 id, u16 pid, u8 onoff) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib9000_firmware_post_pll_init(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib9000_set_slave_frontend(struct dvb_frontend *fe, struct dvb_frontend *fe_slave) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +int dib9000_remove_slave_frontend(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline struct dvb_frontend *dib9000_get_slave_frontend(struct dvb_frontend *fe, int slave_index) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline struct i2c_adapter *dib9000_get_component_bus_interface(struct dvb_frontend *fe) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} + +static inline int dib9000_set_i2c_adapter(struct dvb_frontend *fe, struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} + +static inline int dib9000_fw_set_component_bus_speed(struct dvb_frontend *fe, u16 speed) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} +#endif + +#endif diff --git a/drivers/media/dvb/frontends/dibx000_common.c b/drivers/media/dvb/frontends/dibx000_common.c index 2311c0a3406c..f6938f97feb4 100644 --- a/drivers/media/dvb/frontends/dibx000_common.c +++ b/drivers/media/dvb/frontends/dibx000_common.c @@ -17,9 +17,145 @@ static int dibx000_write_word(struct dibx000_i2c_master *mst, u16 reg, u16 val) struct i2c_msg msg = { .addr = mst->i2c_addr,.flags = 0,.buf = b,.len = 4 }; + return i2c_transfer(mst->i2c_adap, &msg, 1) != 1 ? -EREMOTEIO : 0; } +static u16 dibx000_read_word(struct dibx000_i2c_master *mst, u16 reg) +{ + u8 wb[2] = { reg >> 8, reg & 0xff }; + u8 rb[2]; + struct i2c_msg msg[2] = { + {.addr = mst->i2c_addr, .flags = 0, .buf = wb, .len = 2}, + {.addr = mst->i2c_addr, .flags = I2C_M_RD, .buf = rb, .len = 2}, + }; + + if (i2c_transfer(mst->i2c_adap, msg, 2) != 2) + dprintk("i2c read error on %d", reg); + + return (rb[0] << 8) | rb[1]; +} + +static int dibx000_is_i2c_done(struct dibx000_i2c_master *mst) +{ + int i = 100; + u16 status; + + while (((status = dibx000_read_word(mst, mst->base_reg + 2)) & 0x0100) == 0 && --i > 0) + ; + + /* i2c timed out */ + if (i == 0) + return -EREMOTEIO; + + /* no acknowledge */ + if ((status & 0x0080) == 0) + return -EREMOTEIO; + + return 0; +} + +static int dibx000_master_i2c_write(struct dibx000_i2c_master *mst, struct i2c_msg *msg, u8 stop) +{ + u16 data; + u16 da; + u16 i; + u16 txlen = msg->len, len; + const u8 *b = msg->buf; + + while (txlen) { + dibx000_read_word(mst, mst->base_reg + 2); + + len = txlen > 8 ? 8 : txlen; + for (i = 0; i < len; i += 2) { + data = *b++ << 8; + if (i+1 < len) + data |= *b++; + dibx000_write_word(mst, mst->base_reg, data); + } + da = (((u8) (msg->addr)) << 9) | + (1 << 8) | + (1 << 7) | + (0 << 6) | + (0 << 5) | + ((len & 0x7) << 2) | + (0 << 1) | + (0 << 0); + + if (txlen == msg->len) + da |= 1 << 5; /* start */ + + if (txlen-len == 0 && stop) + da |= 1 << 6; /* stop */ + + dibx000_write_word(mst, mst->base_reg+1, da); + + if (dibx000_is_i2c_done(mst) != 0) + return -EREMOTEIO; + txlen -= len; + } + + return 0; +} + +static int dibx000_master_i2c_read(struct dibx000_i2c_master *mst, struct i2c_msg *msg) +{ + u16 da; + u8 *b = msg->buf; + u16 rxlen = msg->len, len; + + while (rxlen) { + len = rxlen > 8 ? 8 : rxlen; + da = (((u8) (msg->addr)) << 9) | + (1 << 8) | + (1 << 7) | + (0 << 6) | + (0 << 5) | + ((len & 0x7) << 2) | + (1 << 1) | + (0 << 0); + + if (rxlen == msg->len) + da |= 1 << 5; /* start */ + + if (rxlen-len == 0) + da |= 1 << 6; /* stop */ + dibx000_write_word(mst, mst->base_reg+1, da); + + if (dibx000_is_i2c_done(mst) != 0) + return -EREMOTEIO; + + rxlen -= len; + + while (len) { + da = dibx000_read_word(mst, mst->base_reg); + *b++ = (da >> 8) & 0xff; + len--; + if (len >= 1) { + *b++ = da & 0xff; + len--; + } + } + } + + return 0; +} + +int dibx000_i2c_set_speed(struct i2c_adapter *i2c_adap, u16 speed) +{ + struct dibx000_i2c_master *mst = i2c_get_adapdata(i2c_adap); + + if (mst->device_rev < DIB7000MC && speed < 235) + speed = 235; + return dibx000_write_word(mst, mst->base_reg + 3, (u16)(60000 / speed)); + +} +EXPORT_SYMBOL(dibx000_i2c_set_speed); + +static u32 dibx000_i2c_func(struct i2c_adapter *adapter) +{ + return I2C_FUNC_I2C; +} static int dibx000_i2c_select_interface(struct dibx000_i2c_master *mst, enum dibx000_i2c_interface intf) @@ -32,6 +168,60 @@ static int dibx000_i2c_select_interface(struct dibx000_i2c_master *mst, return 0; } +static int dibx000_i2c_master_xfer_gpio12(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + struct dibx000_i2c_master *mst = i2c_get_adapdata(i2c_adap); + int msg_index; + int ret = 0; + + dibx000_i2c_select_interface(mst, DIBX000_I2C_INTERFACE_GPIO_1_2); + for (msg_index = 0; msg_index < num; msg_index++) { + if (msg[msg_index].flags & I2C_M_RD) { + ret = dibx000_master_i2c_read(mst, &msg[msg_index]); + if (ret != 0) + return 0; + } else { + ret = dibx000_master_i2c_write(mst, &msg[msg_index], 1); + if (ret != 0) + return 0; + } + } + + return num; +} + +static int dibx000_i2c_master_xfer_gpio34(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) +{ + struct dibx000_i2c_master *mst = i2c_get_adapdata(i2c_adap); + int msg_index; + int ret = 0; + + dibx000_i2c_select_interface(mst, DIBX000_I2C_INTERFACE_GPIO_3_4); + for (msg_index = 0; msg_index < num; msg_index++) { + if (msg[msg_index].flags & I2C_M_RD) { + ret = dibx000_master_i2c_read(mst, &msg[msg_index]); + if (ret != 0) + return 0; + } else { + ret = dibx000_master_i2c_write(mst, &msg[msg_index], 1); + if (ret != 0) + return 0; + } + } + + return num; +} + +static struct i2c_algorithm dibx000_i2c_master_gpio12_xfer_algo = { + .master_xfer = dibx000_i2c_master_xfer_gpio12, + .functionality = dibx000_i2c_func, +}; + +static struct i2c_algorithm dibx000_i2c_master_gpio34_xfer_algo = { + .master_xfer = dibx000_i2c_master_xfer_gpio34, + .functionality = dibx000_i2c_func, +}; + static int dibx000_i2c_gate_ctrl(struct dibx000_i2c_master *mst, u8 tx[4], u8 addr, int onoff) { @@ -54,11 +244,37 @@ static int dibx000_i2c_gate_ctrl(struct dibx000_i2c_master *mst, u8 tx[4], return 0; } -static u32 dibx000_i2c_func(struct i2c_adapter *adapter) +static int dibx000_i2c_gated_gpio67_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msg[], int num) { - return I2C_FUNC_I2C; + struct dibx000_i2c_master *mst = i2c_get_adapdata(i2c_adap); + struct i2c_msg m[2 + num]; + u8 tx_open[4], tx_close[4]; + + memset(m, 0, sizeof(struct i2c_msg) * (2 + num)); + + dibx000_i2c_select_interface(mst, DIBX000_I2C_INTERFACE_GPIO_6_7); + + dibx000_i2c_gate_ctrl(mst, tx_open, msg[0].addr, 1); + m[0].addr = mst->i2c_addr; + m[0].buf = tx_open; + m[0].len = 4; + + memcpy(&m[1], msg, sizeof(struct i2c_msg) * num); + + dibx000_i2c_gate_ctrl(mst, tx_close, 0, 0); + m[num + 1].addr = mst->i2c_addr; + m[num + 1].buf = tx_close; + m[num + 1].len = 4; + + return i2c_transfer(mst->i2c_adap, m, 2 + num) == 2 + num ? num : -EIO; } +static struct i2c_algorithm dibx000_i2c_gated_gpio67_algo = { + .master_xfer = dibx000_i2c_gated_gpio67_xfer, + .functionality = dibx000_i2c_func, +}; + static int dibx000_i2c_gated_tuner_xfer(struct i2c_adapter *i2c_adap, struct i2c_msg msg[], int num) { @@ -91,8 +307,8 @@ static struct i2c_algorithm dibx000_i2c_gated_tuner_algo = { }; struct i2c_adapter *dibx000_get_i2c_adapter(struct dibx000_i2c_master *mst, - enum dibx000_i2c_interface intf, - int gating) + enum dibx000_i2c_interface intf, + int gating) { struct i2c_adapter *i2c = NULL; @@ -101,6 +317,18 @@ struct i2c_adapter *dibx000_get_i2c_adapter(struct dibx000_i2c_master *mst, if (gating) i2c = &mst->gated_tuner_i2c_adap; break; + case DIBX000_I2C_INTERFACE_GPIO_1_2: + if (!gating) + i2c = &mst->master_i2c_adap_gpio12; + break; + case DIBX000_I2C_INTERFACE_GPIO_3_4: + if (!gating) + i2c = &mst->master_i2c_adap_gpio34; + break; + case DIBX000_I2C_INTERFACE_GPIO_6_7: + if (gating) + i2c = &mst->master_i2c_adap_gpio67; + break; default: printk(KERN_ERR "DiBX000: incorrect I2C interface selected\n"); break; @@ -126,8 +354,8 @@ void dibx000_reset_i2c_master(struct dibx000_i2c_master *mst) EXPORT_SYMBOL(dibx000_reset_i2c_master); static int i2c_adapter_init(struct i2c_adapter *i2c_adap, - struct i2c_algorithm *algo, const char *name, - struct dibx000_i2c_master *mst) + struct i2c_algorithm *algo, const char *name, + struct dibx000_i2c_master *mst) { strncpy(i2c_adap->name, name, sizeof(i2c_adap->name)); i2c_adap->algo = algo; @@ -139,7 +367,7 @@ static int i2c_adapter_init(struct i2c_adapter *i2c_adap, } int dibx000_init_i2c_master(struct dibx000_i2c_master *mst, u16 device_rev, - struct i2c_adapter *i2c_adap, u8 i2c_addr) + struct i2c_adapter *i2c_adap, u8 i2c_addr) { u8 tx[4]; struct i2c_msg m = {.addr = i2c_addr >> 1,.buf = tx,.len = 4 }; @@ -153,11 +381,33 @@ int dibx000_init_i2c_master(struct dibx000_i2c_master *mst, u16 device_rev, else mst->base_reg = 768; + mst->gated_tuner_i2c_adap.dev.parent = mst->i2c_adap->dev.parent; + if (i2c_adapter_init + (&mst->gated_tuner_i2c_adap, &dibx000_i2c_gated_tuner_algo, + "DiBX000 tuner I2C bus", mst) != 0) + printk(KERN_ERR + "DiBX000: could not initialize the tuner i2c_adapter\n"); + + mst->master_i2c_adap_gpio12.dev.parent = mst->i2c_adap->dev.parent; + if (i2c_adapter_init + (&mst->master_i2c_adap_gpio12, &dibx000_i2c_master_gpio12_xfer_algo, + "DiBX000 master GPIO12 I2C bus", mst) != 0) + printk(KERN_ERR + "DiBX000: could not initialize the master i2c_adapter\n"); + + mst->master_i2c_adap_gpio34.dev.parent = mst->i2c_adap->dev.parent; + if (i2c_adapter_init + (&mst->master_i2c_adap_gpio34, &dibx000_i2c_master_gpio34_xfer_algo, + "DiBX000 master GPIO34 I2C bus", mst) != 0) + printk(KERN_ERR + "DiBX000: could not initialize the master i2c_adapter\n"); + + mst->master_i2c_adap_gpio67.dev.parent = mst->i2c_adap->dev.parent; if (i2c_adapter_init - (&mst->gated_tuner_i2c_adap, &dibx000_i2c_gated_tuner_algo, - "DiBX000 tuner I2C bus", mst) != 0) + (&mst->master_i2c_adap_gpio67, &dibx000_i2c_gated_gpio67_algo, + "DiBX000 master GPIO67 I2C bus", mst) != 0) printk(KERN_ERR - "DiBX000: could not initialize the tuner i2c_adapter\n"); + "DiBX000: could not initialize the master i2c_adapter\n"); /* initialize the i2c-master by closing the gate */ dibx000_i2c_gate_ctrl(mst, tx, 0, 0); @@ -170,16 +420,19 @@ EXPORT_SYMBOL(dibx000_init_i2c_master); void dibx000_exit_i2c_master(struct dibx000_i2c_master *mst) { i2c_del_adapter(&mst->gated_tuner_i2c_adap); + i2c_del_adapter(&mst->master_i2c_adap_gpio12); + i2c_del_adapter(&mst->master_i2c_adap_gpio34); + i2c_del_adapter(&mst->master_i2c_adap_gpio67); } EXPORT_SYMBOL(dibx000_exit_i2c_master); u32 systime(void) { - struct timespec t; + struct timespec t; - t = current_kernel_time(); - return (t.tv_sec * 10000) + (t.tv_nsec / 100000); + t = current_kernel_time(); + return (t.tv_sec * 10000) + (t.tv_nsec / 100000); } EXPORT_SYMBOL(systime); diff --git a/drivers/media/dvb/frontends/dibx000_common.h b/drivers/media/dvb/frontends/dibx000_common.h index 4f5d141a308d..977d343369aa 100644 --- a/drivers/media/dvb/frontends/dibx000_common.h +++ b/drivers/media/dvb/frontends/dibx000_common.h @@ -4,7 +4,8 @@ enum dibx000_i2c_interface { DIBX000_I2C_INTERFACE_TUNER = 0, DIBX000_I2C_INTERFACE_GPIO_1_2 = 1, - DIBX000_I2C_INTERFACE_GPIO_3_4 = 2 + DIBX000_I2C_INTERFACE_GPIO_3_4 = 2, + DIBX000_I2C_INTERFACE_GPIO_6_7 = 3 }; struct dibx000_i2c_master { @@ -17,8 +18,11 @@ struct dibx000_i2c_master { enum dibx000_i2c_interface selected_interface; -// struct i2c_adapter tuner_i2c_adap; +/* struct i2c_adapter tuner_i2c_adap; */ struct i2c_adapter gated_tuner_i2c_adap; + struct i2c_adapter master_i2c_adap_gpio12; + struct i2c_adapter master_i2c_adap_gpio34; + struct i2c_adapter master_i2c_adap_gpio67; struct i2c_adapter *i2c_adap; u8 i2c_addr; @@ -27,14 +31,15 @@ struct dibx000_i2c_master { }; extern int dibx000_init_i2c_master(struct dibx000_i2c_master *mst, - u16 device_rev, struct i2c_adapter *i2c_adap, - u8 i2c_addr); + u16 device_rev, struct i2c_adapter *i2c_adap, + u8 i2c_addr); extern struct i2c_adapter *dibx000_get_i2c_adapter(struct dibx000_i2c_master - *mst, - enum dibx000_i2c_interface - intf, int gating); + *mst, + enum dibx000_i2c_interface + intf, int gating); extern void dibx000_exit_i2c_master(struct dibx000_i2c_master *mst); extern void dibx000_reset_i2c_master(struct dibx000_i2c_master *mst); +extern int dibx000_i2c_set_speed(struct i2c_adapter *i2c_adap, u16 speed); extern u32 systime(void); @@ -42,7 +47,7 @@ extern u32 systime(void); #define BAND_UHF 0x02 #define BAND_VHF 0x04 #define BAND_SBAND 0x08 -#define BAND_FM 0x10 +#define BAND_FM 0x10 #define BAND_CBAND 0x20 #define BAND_OF_FREQUENCY(freq_kHz) ((freq_kHz) <= 170000 ? BAND_CBAND : \ @@ -135,9 +140,9 @@ enum dibx000_adc_states { DIBX000_VBG_DISABLE, }; -#define BANDWIDTH_TO_KHZ(v) ( (v) == BANDWIDTH_8_MHZ ? 8000 : \ - (v) == BANDWIDTH_7_MHZ ? 7000 : \ - (v) == BANDWIDTH_6_MHZ ? 6000 : 8000 ) +#define BANDWIDTH_TO_KHZ(v) ((v) == BANDWIDTH_8_MHZ ? 8000 : \ + (v) == BANDWIDTH_7_MHZ ? 7000 : \ + (v) == BANDWIDTH_6_MHZ ? 6000 : 8000) #define BANDWIDTH_TO_INDEX(v) ( \ (v) == 8000 ? BANDWIDTH_8_MHZ : \ @@ -153,53 +158,57 @@ enum dibx000_adc_states { #define OUTMODE_MPEG2_FIFO 5 #define OUTMODE_ANALOG_ADC 6 +#define INPUT_MODE_OFF 0x11 +#define INPUT_MODE_DIVERSITY 0x12 +#define INPUT_MODE_MPEG 0x13 + enum frontend_tune_state { - CT_TUNER_START = 10, - CT_TUNER_STEP_0, - CT_TUNER_STEP_1, - CT_TUNER_STEP_2, - CT_TUNER_STEP_3, - CT_TUNER_STEP_4, - CT_TUNER_STEP_5, - CT_TUNER_STEP_6, - CT_TUNER_STEP_7, - CT_TUNER_STOP, - - CT_AGC_START = 20, - CT_AGC_STEP_0, - CT_AGC_STEP_1, - CT_AGC_STEP_2, - CT_AGC_STEP_3, - CT_AGC_STEP_4, - CT_AGC_STOP, + CT_TUNER_START = 10, + CT_TUNER_STEP_0, + CT_TUNER_STEP_1, + CT_TUNER_STEP_2, + CT_TUNER_STEP_3, + CT_TUNER_STEP_4, + CT_TUNER_STEP_5, + CT_TUNER_STEP_6, + CT_TUNER_STEP_7, + CT_TUNER_STOP, + + CT_AGC_START = 20, + CT_AGC_STEP_0, + CT_AGC_STEP_1, + CT_AGC_STEP_2, + CT_AGC_STEP_3, + CT_AGC_STEP_4, + CT_AGC_STOP, CT_DEMOD_START = 30, - CT_DEMOD_STEP_1, - CT_DEMOD_STEP_2, - CT_DEMOD_STEP_3, - CT_DEMOD_STEP_4, - CT_DEMOD_STEP_5, - CT_DEMOD_STEP_6, - CT_DEMOD_STEP_7, - CT_DEMOD_STEP_8, - CT_DEMOD_STEP_9, - CT_DEMOD_STEP_10, - CT_DEMOD_SEARCH_NEXT = 41, - CT_DEMOD_STEP_LOCKED, - CT_DEMOD_STOP, - - CT_DONE = 100, - CT_SHUTDOWN, + CT_DEMOD_STEP_1, + CT_DEMOD_STEP_2, + CT_DEMOD_STEP_3, + CT_DEMOD_STEP_4, + CT_DEMOD_STEP_5, + CT_DEMOD_STEP_6, + CT_DEMOD_STEP_7, + CT_DEMOD_STEP_8, + CT_DEMOD_STEP_9, + CT_DEMOD_STEP_10, + CT_DEMOD_SEARCH_NEXT = 41, + CT_DEMOD_STEP_LOCKED, + CT_DEMOD_STOP, + + CT_DONE = 100, + CT_SHUTDOWN, }; struct dvb_frontend_parametersContext { #define CHANNEL_STATUS_PARAMETERS_UNKNOWN 0x01 #define CHANNEL_STATUS_PARAMETERS_SET 0x02 - u8 status; - u32 tune_time_estimation[2]; - s32 tps_available; - u16 tps[9]; + u8 status; + u32 tune_time_estimation[2]; + s32 tps_available; + u16 tps[9]; }; #define FE_STATUS_TUNE_FAILED 0 @@ -216,4 +225,49 @@ struct dvb_frontend_parametersContext { #define ABS(x) ((x < 0) ? (-x) : (x)) +#define DATA_BUS_ACCESS_MODE_8BIT 0x01 +#define DATA_BUS_ACCESS_MODE_16BIT 0x02 +#define DATA_BUS_ACCESS_MODE_NO_ADDRESS_INCREMENT 0x10 + +struct dibGPIOFunction { +#define BOARD_GPIO_COMPONENT_BUS_ADAPTER 1 +#define BOARD_GPIO_COMPONENT_DEMOD 2 + u8 component; + +#define BOARD_GPIO_FUNCTION_BOARD_ON 1 +#define BOARD_GPIO_FUNCTION_BOARD_OFF 2 +#define BOARD_GPIO_FUNCTION_COMPONENT_ON 3 +#define BOARD_GPIO_FUNCTION_COMPONENT_OFF 4 +#define BOARD_GPIO_FUNCTION_SUBBAND_PWM 5 +#define BOARD_GPIO_FUNCTION_SUBBAND_GPIO 6 + u8 function; + +/* mask, direction and value are used specify which GPIO to change GPIO0 + * is LSB and possible GPIO31 is MSB. The same bit-position as in the + * mask is used for the direction and the value. Direction == 1 is OUT, + * 0 == IN. For direction "OUT" value is either 1 or 0, for direction IN + * value has no meaning. + * + * In case of BOARD_GPIO_FUNCTION_PWM mask is giving the GPIO to be + * used to do the PWM. Direction gives the PWModulator to be used. + * Value gives the PWM value in device-dependent scale. + */ + u32 mask; + u32 direction; + u32 value; +}; + +#define MAX_NB_SUBBANDS 8 +struct dibSubbandSelection { + u8 size; /* Actual number of subbands. */ + struct { + u16 f_mhz; + struct dibGPIOFunction gpio; + } subband[MAX_NB_SUBBANDS]; +}; + +#define DEMOD_TIMF_SET 0x00 +#define DEMOD_TIMF_GET 0x01 +#define DEMOD_TIMF_UPDATE 0x02 + #endif diff --git a/drivers/media/dvb/frontends/stv090x.c b/drivers/media/dvb/frontends/stv090x.c index 4e0fc2c8a41c..d3362d0407eb 100644 --- a/drivers/media/dvb/frontends/stv090x.c +++ b/drivers/media/dvb/frontends/stv090x.c @@ -767,8 +767,12 @@ static int stv090x_i2c_gate_ctrl(struct stv090x_state *state, int enable) * In case of any error, the lock is unlocked and exit within the * relevant operations themselves. */ - if (enable) - mutex_lock(&state->internal->tuner_lock); + if (enable) { + if (state->config->tuner_i2c_lock) + state->config->tuner_i2c_lock(&state->frontend, 1); + else + mutex_lock(&state->internal->tuner_lock); + } reg = STV090x_READ_DEMOD(state, I2CRPT); if (enable) { @@ -784,13 +788,20 @@ static int stv090x_i2c_gate_ctrl(struct stv090x_state *state, int enable) goto err; } - if (!enable) - mutex_unlock(&state->internal->tuner_lock); + if (!enable) { + if (state->config->tuner_i2c_lock) + state->config->tuner_i2c_lock(&state->frontend, 0); + else + mutex_unlock(&state->internal->tuner_lock); + } return 0; err: dprintk(FE_ERROR, 1, "I/O error"); - mutex_unlock(&state->internal->tuner_lock); + if (state->config->tuner_i2c_lock) + state->config->tuner_i2c_lock(&state->frontend, 0); + else + mutex_unlock(&state->internal->tuner_lock); return -1; } @@ -2883,10 +2894,12 @@ static int stv090x_optimize_track(struct stv090x_state *state) STV090x_SETFIELD_Px(reg, DVBS2_ENABLE_FIELD, 1); if (STV090x_WRITE_DEMOD(state, DMDCFGMD, reg) < 0) goto err; - if (STV090x_WRITE_DEMOD(state, ACLC, 0) < 0) - goto err; - if (STV090x_WRITE_DEMOD(state, BCLC, 0) < 0) - goto err; + if (state->internal->dev_ver >= 0x30) { + if (STV090x_WRITE_DEMOD(state, ACLC, 0) < 0) + goto err; + if (STV090x_WRITE_DEMOD(state, BCLC, 0) < 0) + goto err; + } if (state->frame_len == STV090x_LONG_FRAME) { reg = STV090x_READ_DEMOD(state, DMDMODCOD); modcod = STV090x_GETFIELD_Px(reg, DEMOD_MODCOD_FIELD); @@ -3846,6 +3859,7 @@ static int stv090x_sleep(struct dvb_frontend *fe) { struct stv090x_state *state = fe->demodulator_priv; u32 reg; + u8 full_standby = 0; if (stv090x_i2c_gate_ctrl(state, 1) < 0) goto err; @@ -3858,24 +3872,119 @@ static int stv090x_sleep(struct dvb_frontend *fe) if (stv090x_i2c_gate_ctrl(state, 0) < 0) goto err; - dprintk(FE_DEBUG, 1, "Set %s to sleep", - state->device == STV0900 ? "STV0900" : "STV0903"); + dprintk(FE_DEBUG, 1, "Set %s(%d) to sleep", + state->device == STV0900 ? "STV0900" : "STV0903", + state->demod); - reg = stv090x_read_reg(state, STV090x_SYNTCTRL); - STV090x_SETFIELD(reg, STANDBY_FIELD, 0x01); - if (stv090x_write_reg(state, STV090x_SYNTCTRL, reg) < 0) - goto err; + mutex_lock(&state->internal->demod_lock); - reg = stv090x_read_reg(state, STV090x_TSTTNR1); - STV090x_SETFIELD(reg, ADC1_PON_FIELD, 0); - if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) - goto err; + switch (state->demod) { + case STV090x_DEMODULATOR_0: + /* power off ADC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR1); + STV090x_SETFIELD(reg, ADC1_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) + goto err; + /* power off DiSEqC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR2); + STV090x_SETFIELD(reg, DISEQC1_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR2, reg) < 0) + goto err; + + /* check whether path 2 is already sleeping, that is when + ADC2 is off */ + reg = stv090x_read_reg(state, STV090x_TSTTNR3); + if (STV090x_GETFIELD(reg, ADC2_PON_FIELD) == 0) + full_standby = 1; + + /* stop clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT1_FIELD, 1); + /* ADC 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI1_FIELD, 1); + /* FEC clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP1_FIELD, 1); + /* viterbi 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, 1); + /* TS clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + + case STV090x_DEMODULATOR_1: + /* power off ADC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR3); + STV090x_SETFIELD(reg, ADC2_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) + goto err; + /* power off DiSEqC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR4); + STV090x_SETFIELD(reg, DISEQC2_PON_FIELD, 0); + if (stv090x_write_reg(state, STV090x_TSTTNR4, reg) < 0) + goto err; + + /* check whether path 1 is already sleeping, that is when + ADC1 is off */ + reg = stv090x_read_reg(state, STV090x_TSTTNR1); + if (STV090x_GETFIELD(reg, ADC1_PON_FIELD) == 0) + full_standby = 1; + + /* stop clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT2_FIELD, 1); + /* ADC 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI2_FIELD, 1); + /* FEC clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP2_FIELD, 1); + /* viterbi 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, 1); + /* TS clock is shared between the two paths, only stop it + when full standby is possible */ + if (full_standby) + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 1); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + default: + dprintk(FE_ERROR, 1, "Wrong demodulator!"); + break; + } + + if (full_standby) { + /* general power off */ + reg = stv090x_read_reg(state, STV090x_SYNTCTRL); + STV090x_SETFIELD(reg, STANDBY_FIELD, 0x01); + if (stv090x_write_reg(state, STV090x_SYNTCTRL, reg) < 0) + goto err; + } + + mutex_unlock(&state->internal->demod_lock); return 0; err_gateoff: stv090x_i2c_gate_ctrl(state, 0); err: + mutex_unlock(&state->internal->demod_lock); dprintk(FE_ERROR, 1, "I/O error"); return -1; } @@ -3885,21 +3994,94 @@ static int stv090x_wakeup(struct dvb_frontend *fe) struct stv090x_state *state = fe->demodulator_priv; u32 reg; - dprintk(FE_DEBUG, 1, "Wake %s from standby", - state->device == STV0900 ? "STV0900" : "STV0903"); + dprintk(FE_DEBUG, 1, "Wake %s(%d) from standby", + state->device == STV0900 ? "STV0900" : "STV0903", + state->demod); + + mutex_lock(&state->internal->demod_lock); + /* general power on */ reg = stv090x_read_reg(state, STV090x_SYNTCTRL); STV090x_SETFIELD(reg, STANDBY_FIELD, 0x00); if (stv090x_write_reg(state, STV090x_SYNTCTRL, reg) < 0) goto err; - reg = stv090x_read_reg(state, STV090x_TSTTNR1); - STV090x_SETFIELD(reg, ADC1_PON_FIELD, 1); - if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) - goto err; + switch (state->demod) { + case STV090x_DEMODULATOR_0: + /* power on ADC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR1); + STV090x_SETFIELD(reg, ADC1_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR1, reg) < 0) + goto err; + /* power on DiSEqC 1 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR2); + STV090x_SETFIELD(reg, DISEQC1_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR2, reg) < 0) + goto err; + + /* activate clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT1_FIELD, 0); + /* ADC 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI1_FIELD, 0); + /* FEC clock */ + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP1_FIELD, 0); + /* viterbi 1 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT1_FIELD, 0); + /* TS clock */ + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + + case STV090x_DEMODULATOR_1: + /* power on ADC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR3); + STV090x_SETFIELD(reg, ADC2_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR3, reg) < 0) + goto err; + /* power on DiSEqC 2 */ + reg = stv090x_read_reg(state, STV090x_TSTTNR4); + STV090x_SETFIELD(reg, DISEQC2_PON_FIELD, 1); + if (stv090x_write_reg(state, STV090x_TSTTNR4, reg) < 0) + goto err; + + /* activate clocks */ + reg = stv090x_read_reg(state, STV090x_STOPCLK1); + /* packet delineator 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKPKDT2_FIELD, 0); + /* ADC 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKADCI2_FIELD, 0); + /* FEC clock */ + STV090x_SETFIELD(reg, STOP_CLKFEC_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK1, reg) < 0) + goto err; + reg = stv090x_read_reg(state, STV090x_STOPCLK2); + /* sampling 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKSAMP2_FIELD, 0); + /* viterbi 2 clock */ + STV090x_SETFIELD(reg, STOP_CLKVIT2_FIELD, 0); + /* TS clock */ + STV090x_SETFIELD(reg, STOP_CLKTS_FIELD, 0); + if (stv090x_write_reg(state, STV090x_STOPCLK2, reg) < 0) + goto err; + break; + + default: + dprintk(FE_ERROR, 1, "Wrong demodulator!"); + break; + } + mutex_unlock(&state->internal->demod_lock); return 0; err: + mutex_unlock(&state->internal->demod_lock); dprintk(FE_ERROR, 1, "I/O error"); return -1; } @@ -4169,6 +4351,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) switch (state->config->ts1_mode) { case STV090x_TSMODE_PARALLEL_PUNCTURED: reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) @@ -4177,6 +4360,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) case STV090x_TSMODE_DVBCI: reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) @@ -4185,6 +4369,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) case STV090x_TSMODE_SERIAL_PUNCTURED: reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) @@ -4193,6 +4378,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) case STV090x_TSMODE_SERIAL_CONTINUOUS: reg = stv090x_read_reg(state, STV090x_P1_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts1_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); if (stv090x_write_reg(state, STV090x_P1_TSCFGH, reg) < 0) @@ -4206,6 +4392,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) switch (state->config->ts2_mode) { case STV090x_TSMODE_PARALLEL_PUNCTURED: reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) @@ -4214,6 +4401,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) case STV090x_TSMODE_DVBCI: reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x00); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) @@ -4222,6 +4410,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) case STV090x_TSMODE_SERIAL_PUNCTURED: reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x00); if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) @@ -4230,6 +4419,7 @@ static int stv090x_set_tspath(struct stv090x_state *state) case STV090x_TSMODE_SERIAL_CONTINUOUS: reg = stv090x_read_reg(state, STV090x_P2_TSCFGH); + STV090x_SETFIELD_Px(reg, TSFIFO_TEIUPDATE_FIELD, state->config->ts2_tei); STV090x_SETFIELD_Px(reg, TSFIFO_SERIAL_FIELD, 0x01); STV090x_SETFIELD_Px(reg, TSFIFO_DVBCI_FIELD, 0x01); if (stv090x_write_reg(state, STV090x_P2_TSCFGH, reg) < 0) @@ -4506,16 +4696,26 @@ static int stv090x_setup(struct dvb_frontend *fe) if (stv090x_write_reg(state, STV090x_TSTRES0, 0x00) < 0) goto err; - /* workaround for stuck DiSEqC output */ - if (config->diseqc_envelope_mode) - stv090x_send_diseqc_burst(fe, SEC_MINI_A); - return 0; err: dprintk(FE_ERROR, 1, "I/O error"); return -1; } +int stv090x_set_gpio(struct dvb_frontend *fe, u8 gpio, u8 dir, u8 value, + u8 xor_value) +{ + struct stv090x_state *state = fe->demodulator_priv; + u8 reg = 0; + + STV090x_SETFIELD(reg, GPIOx_OPD_FIELD, dir); + STV090x_SETFIELD(reg, GPIOx_CONFIG_FIELD, value); + STV090x_SETFIELD(reg, GPIOx_XOR_FIELD, xor_value); + + return stv090x_write_reg(state, STV090x_GPIOxCFG(gpio), reg); +} +EXPORT_SYMBOL(stv090x_set_gpio); + static struct dvb_frontend_ops stv090x_ops = { .info = { @@ -4580,11 +4780,6 @@ struct dvb_frontend *stv090x_attach(const struct stv090x_config *config, state->internal = temp_int->internal; state->internal->num_used++; dprintk(FE_INFO, 1, "Found Internal Structure!"); - dprintk(FE_ERROR, 1, "Attaching %s demodulator(%d) Cut=0x%02x", - state->device == STV0900 ? "STV0900" : "STV0903", - demod, - state->internal->dev_ver); - return &state->frontend; } else { state->internal = kmalloc(sizeof(struct stv090x_internal), GFP_KERNEL); @@ -4595,24 +4790,19 @@ struct dvb_frontend *stv090x_attach(const struct stv090x_config *config, state->internal->i2c_adap = state->i2c; state->internal->i2c_addr = state->config->address; dprintk(FE_INFO, 1, "Create New Internal Structure!"); - } - mutex_init(&state->internal->demod_lock); - mutex_init(&state->internal->tuner_lock); + mutex_init(&state->internal->demod_lock); + mutex_init(&state->internal->tuner_lock); - if (stv090x_sleep(&state->frontend) < 0) { - dprintk(FE_ERROR, 1, "Error putting device to sleep"); - goto error; + if (stv090x_setup(&state->frontend) < 0) { + dprintk(FE_ERROR, 1, "Error setting up device"); + goto error; + } } - if (stv090x_setup(&state->frontend) < 0) { - dprintk(FE_ERROR, 1, "Error setting up device"); - goto error; - } - if (stv090x_wakeup(&state->frontend) < 0) { - dprintk(FE_ERROR, 1, "Error waking device"); - goto error; - } + /* workaround for stuck DiSEqC output */ + if (config->diseqc_envelope_mode) + stv090x_send_diseqc_burst(&state->frontend, SEC_MINI_A); dprintk(FE_ERROR, 1, "Attaching %s demodulator(%d) Cut=0x%02x", state->device == STV0900 ? "STV0900" : "STV0903", diff --git a/drivers/media/dvb/frontends/stv090x.h b/drivers/media/dvb/frontends/stv090x.h index dd1b93ae4e9d..29cdc2b71314 100644 --- a/drivers/media/dvb/frontends/stv090x.h +++ b/drivers/media/dvb/frontends/stv090x.h @@ -78,6 +78,9 @@ struct stv090x_config { u32 ts1_clk; u32 ts2_clk; + u8 ts1_tei : 1; + u8 ts2_tei : 1; + enum stv090x_i2crpt repeater_level; u8 tuner_bbgain; /* default: 10db */ @@ -97,6 +100,7 @@ struct stv090x_config { int (*tuner_get_bbgain) (struct dvb_frontend *fe, u32 *gain); int (*tuner_set_refclk) (struct dvb_frontend *fe, u32 refclk); int (*tuner_get_status) (struct dvb_frontend *fe, u32 *status); + void (*tuner_i2c_lock) (struct dvb_frontend *fe, int lock); }; #if defined(CONFIG_DVB_STV090x) || (defined(CONFIG_DVB_STV090x_MODULE) && defined(MODULE)) @@ -104,6 +108,11 @@ struct stv090x_config { extern struct dvb_frontend *stv090x_attach(const struct stv090x_config *config, struct i2c_adapter *i2c, enum stv090x_demodulator demod); + +/* dir = 0 -> output, dir = 1 -> input/open-drain */ +extern int stv090x_set_gpio(struct dvb_frontend *fe, u8 gpio, + u8 dir, u8 value, u8 xor_value); + #else static inline struct dvb_frontend *stv090x_attach(const struct stv090x_config *config, @@ -113,6 +122,13 @@ static inline struct dvb_frontend *stv090x_attach(const struct stv090x_config *c printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); return NULL; } + +static inline int stv090x_set_gpio(struct dvb_frontend *fe, u8 gpio, + u8 opd, u8 value, u8 xor_value) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return -ENODEV; +} #endif /* CONFIG_DVB_STV090x */ #endif /* __STV090x_H */ diff --git a/drivers/media/dvb/frontends/stv090x_reg.h b/drivers/media/dvb/frontends/stv090x_reg.h index 2502855dd784..93741ee14297 100644 --- a/drivers/media/dvb/frontends/stv090x_reg.h +++ b/drivers/media/dvb/frontends/stv090x_reg.h @@ -1327,10 +1327,10 @@ #define STV090x_WIDTH_Px_NOSPLHT_UNNORMED_FIELD 8 #define STV090x_Px_NOSPLHy(__x, __y) (0xf48f - (__x - 1) * 0x200 - __y * 0x1) -#define STv090x_P1_NOSPLH0 STV090x_Px_NOSPLHy(1, 0) -#define STv090x_P1_NOSPLH1 STV090x_Px_NOSPLHy(1, 1) -#define STv090x_P2_NOSPLH0 STV090x_Px_NOSPLHy(2, 0) -#define STv090x_P2_NOSPLH1 STV090x_Px_NOSPLHy(2, 1) +#define STV090x_P1_NOSPLH0 STV090x_Px_NOSPLHy(1, 0) +#define STV090x_P1_NOSPLH1 STV090x_Px_NOSPLHy(1, 1) +#define STV090x_P2_NOSPLH0 STV090x_Px_NOSPLHy(2, 0) +#define STV090x_P2_NOSPLH1 STV090x_Px_NOSPLHy(2, 1) #define STV090x_OFFST_Px_NOSPLH_UNNORMED_FIELD 0 #define STV090x_WIDTH_Px_NOSPLH_UNNORMED_FIELD 8 @@ -1406,7 +1406,7 @@ #define STV090x_Px_BCLC2S28(__x) (0xf49d - (__x - 1) * 0x200) #define STV090x_P1_BCLC2S28 STV090x_Px_BCLC2S28(1) -#define STV090x_P2_BCLC2S28 STV090x_Px_BCLC2S28(1) +#define STV090x_P2_BCLC2S28 STV090x_Px_BCLC2S28(2) #define STV090x_OFFST_Px_CAR2S2_8_BETA_M_FIELD 4 #define STV090x_WIDTH_Px_CAR2S2_8_BETA_M_FIELD 2 #define STV090x_OFFST_Px_CAR2S2_8_BETA_E_FIELD 0 @@ -1414,7 +1414,7 @@ #define STV090x_Px_BCLC2S216A(__x) (0xf49e - (__x - 1) * 0x200) #define STV090x_P1_BCLC2S216A STV090x_Px_BCLC2S216A(1) -#define STV090x_P2_BCLC2S216A STV090x_Px_BCLC2S216A(1) +#define STV090x_P2_BCLC2S216A STV090x_Px_BCLC2S216A(2) #define STV090x_OFFST_Px_CAR2S2_16A_BETA_M_FIELD 4 #define STV090x_WIDTH_Px_CAR2S2_16A_BETA_M_FIELD 2 #define STV090x_OFFST_Px_CAR2S2_16A_BETA_E_FIELD 0 @@ -1422,7 +1422,7 @@ #define STV090x_Px_BCLC2S232A(__x) (0xf49f - (__x - 1) * 0x200) #define STV090x_P1_BCLC2S232A STV090x_Px_BCLC2S232A(1) -#define STV090x_P2_BCLC2S232A STV090x_Px_BCLC2S232A(1) +#define STV090x_P2_BCLC2S232A STV090x_Px_BCLC2S232A(2) #define STV090x_OFFST_Px_CAR2S2_32A_BETA_M_FIELD 4 #define STV090x_WIDTH_Px_CAR2S2_32A_BETA_M_FIELD 2 #define STV090x_OFFST_Px_CAR2S2_32A_BETA_E_FIELD 0 @@ -1602,7 +1602,7 @@ #define STV090x_Px_CCIACC(__x) (0xf4c4 - (__x - 1) * 0x200) #define STV090x_P1_CCIACC STV090x_Px_CCIACC(1) -#define STV090x_P2_CCIACC STV090x_Px_CCIACC(1) +#define STV090x_P2_CCIACC STV090x_Px_CCIACC(2) #define STV090x_OFFST_Px_CCI_VALUE_FIELD 0 #define STV090x_WIDTH_Px_CCI_VALUE_FIELD 8 diff --git a/drivers/media/dvb/ngene/Makefile b/drivers/media/dvb/ngene/Makefile index 0608aabb14ee..2bc96874d044 100644 --- a/drivers/media/dvb/ngene/Makefile +++ b/drivers/media/dvb/ngene/Makefile @@ -9,3 +9,6 @@ obj-$(CONFIG_DVB_NGENE) += ngene.o EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core/ EXTRA_CFLAGS += -Idrivers/media/dvb/frontends/ EXTRA_CFLAGS += -Idrivers/media/common/tuners/ + +# For the staging CI driver cxd2099 +EXTRA_CFLAGS += -Idrivers/staging/cxd2099/ diff --git a/drivers/media/dvb/ngene/ngene-cards.c b/drivers/media/dvb/ngene/ngene-cards.c index 4692a41ad95b..fcf4be901ec8 100644 --- a/drivers/media/dvb/ngene/ngene-cards.c +++ b/drivers/media/dvb/ngene/ngene-cards.c @@ -48,20 +48,27 @@ static int tuner_attach_stv6110(struct ngene_channel *chan) { + struct i2c_adapter *i2c; struct stv090x_config *feconf = (struct stv090x_config *) chan->dev->card_info->fe_config[chan->number]; struct stv6110x_config *tunerconf = (struct stv6110x_config *) chan->dev->card_info->tuner_config[chan->number]; struct stv6110x_devctl *ctl; - ctl = dvb_attach(stv6110x_attach, chan->fe, tunerconf, - &chan->i2c_adapter); + /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ + if (chan->number < 2) + i2c = &chan->dev->channel[0].i2c_adapter; + else + i2c = &chan->dev->channel[1].i2c_adapter; + + ctl = dvb_attach(stv6110x_attach, chan->fe, tunerconf, i2c); if (ctl == NULL) { printk(KERN_ERR DEVICE_NAME ": No STV6110X found!\n"); return -ENODEV; } feconf->tuner_init = ctl->tuner_init; + feconf->tuner_sleep = ctl->tuner_sleep; feconf->tuner_set_mode = ctl->tuner_set_mode; feconf->tuner_set_frequency = ctl->tuner_set_frequency; feconf->tuner_get_frequency = ctl->tuner_get_frequency; @@ -78,29 +85,106 @@ static int tuner_attach_stv6110(struct ngene_channel *chan) static int demod_attach_stv0900(struct ngene_channel *chan) { + struct i2c_adapter *i2c; struct stv090x_config *feconf = (struct stv090x_config *) chan->dev->card_info->fe_config[chan->number]; - chan->fe = dvb_attach(stv090x_attach, - feconf, - &chan->i2c_adapter, - chan->number == 0 ? STV090x_DEMODULATOR_0 : - STV090x_DEMODULATOR_1); + /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ + /* Note: Both adapters share the same i2c bus, but the demod */ + /* driver requires that each demod has its own i2c adapter */ + if (chan->number < 2) + i2c = &chan->dev->channel[0].i2c_adapter; + else + i2c = &chan->dev->channel[1].i2c_adapter; + + chan->fe = dvb_attach(stv090x_attach, feconf, i2c, + (chan->number & 1) == 0 ? STV090x_DEMODULATOR_0 + : STV090x_DEMODULATOR_1); if (chan->fe == NULL) { printk(KERN_ERR DEVICE_NAME ": No STV0900 found!\n"); return -ENODEV; } - if (!dvb_attach(lnbh24_attach, chan->fe, &chan->i2c_adapter, 0, + /* store channel info */ + if (feconf->tuner_i2c_lock) + chan->fe->analog_demod_priv = chan; + + if (!dvb_attach(lnbh24_attach, chan->fe, i2c, 0, 0, chan->dev->card_info->lnb[chan->number])) { printk(KERN_ERR DEVICE_NAME ": No LNBH24 found!\n"); dvb_frontend_detach(chan->fe); + chan->fe = NULL; + return -ENODEV; + } + + return 0; +} + +static void cineS2_tuner_i2c_lock(struct dvb_frontend *fe, int lock) +{ + struct ngene_channel *chan = fe->analog_demod_priv; + + if (lock) + down(&chan->dev->pll_mutex); + else + up(&chan->dev->pll_mutex); +} + +static int cineS2_probe(struct ngene_channel *chan) +{ + struct i2c_adapter *i2c; + struct stv090x_config *fe_conf; + u8 buf[3]; + struct i2c_msg i2c_msg = { .flags = 0, .buf = buf }; + int rc; + + /* tuner 1+2: i2c adapter #0, tuner 3+4: i2c adapter #1 */ + if (chan->number < 2) + i2c = &chan->dev->channel[0].i2c_adapter; + else + i2c = &chan->dev->channel[1].i2c_adapter; + + fe_conf = chan->dev->card_info->fe_config[chan->number]; + i2c_msg.addr = fe_conf->address; + + /* probe demod */ + i2c_msg.len = 2; + buf[0] = 0xf1; + buf[1] = 0x00; + rc = i2c_transfer(i2c, &i2c_msg, 1); + if (rc != 1) + return -ENODEV; + + /* demod found, attach it */ + rc = demod_attach_stv0900(chan); + if (rc < 0 || chan->number < 2) + return rc; + + /* demod #2: reprogram outputs DPN1 & DPN2 */ + i2c_msg.len = 3; + buf[0] = 0xf1; + switch (chan->number) { + case 2: + buf[1] = 0x5c; + buf[2] = 0xc2; + break; + case 3: + buf[1] = 0x61; + buf[2] = 0xcc; + break; + default: return -ENODEV; } + rc = i2c_transfer(i2c, &i2c_msg, 1); + if (rc != 1) { + printk(KERN_ERR DEVICE_NAME ": could not setup DPNx\n"); + return -EIO; + } return 0; } + static struct lgdt330x_config aver_m780 = { .demod_address = 0xb2 >> 1, .demod_chip = LGDT3303, @@ -151,6 +235,29 @@ static struct stv090x_config fe_cineS2 = { .adc2_range = STV090x_ADC_1Vpp, .diseqc_envelope_mode = true, + + .tuner_i2c_lock = cineS2_tuner_i2c_lock, +}; + +static struct stv090x_config fe_cineS2_2 = { + .device = STV0900, + .demod_mode = STV090x_DUAL, + .clk_mode = STV090x_CLK_EXT, + + .xtal = 27000000, + .address = 0x69, + + .ts1_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + .ts2_mode = STV090x_TSMODE_SERIAL_PUNCTURED, + + .repeater_level = STV090x_RPTLEVEL_16, + + .adc1_range = STV090x_ADC_1Vpp, + .adc2_range = STV090x_ADC_1Vpp, + + .diseqc_envelope_mode = true, + + .tuner_i2c_lock = cineS2_tuner_i2c_lock, }; static struct stv6110x_config tuner_cineS2_0 = { @@ -175,7 +282,8 @@ static struct ngene_info ngene_info_cineS2 = { .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, .lnb = {0x0b, 0x08}, .tsf = {3, 3}, - .fw_version = 15, + .fw_version = 18, + .msi_supported = true, }; static struct ngene_info ngene_info_satixS2 = { @@ -188,46 +296,54 @@ static struct ngene_info ngene_info_satixS2 = { .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, .lnb = {0x0b, 0x08}, .tsf = {3, 3}, - .fw_version = 15, + .fw_version = 18, + .msi_supported = true, }; static struct ngene_info ngene_info_satixS2v2 = { .type = NGENE_SIDEWINDER, .name = "Mystique SaTiX-S2 Dual (v2)", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, - .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, - .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, - .fe_config = {&fe_cineS2, &fe_cineS2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0a, 0x08}, + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_stv6110}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, .tsf = {3, 3}, - .fw_version = 15, + .fw_version = 18, + .msi_supported = true, }; static struct ngene_info ngene_info_cineS2v5 = { .type = NGENE_SIDEWINDER, .name = "Linux4Media cineS2 DVB-S2 Twin Tuner (v5)", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, - .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, - .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, - .fe_config = {&fe_cineS2, &fe_cineS2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0a, 0x08}, + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {demod_attach_stv0900, demod_attach_stv0900, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_stv6110}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, .tsf = {3, 3}, - .fw_version = 15, + .fw_version = 18, + .msi_supported = true, }; + static struct ngene_info ngene_info_duoFlexS2 = { .type = NGENE_SIDEWINDER, .name = "Digital Devices DuoFlex S2 miniPCIe", - .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN}, - .demod_attach = {demod_attach_stv0900, demod_attach_stv0900}, - .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110}, - .fe_config = {&fe_cineS2, &fe_cineS2}, - .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1}, - .lnb = {0x0a, 0x08}, + .io_type = {NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, NGENE_IO_TSIN, + NGENE_IO_TSOUT}, + .demod_attach = {cineS2_probe, cineS2_probe, cineS2_probe, cineS2_probe}, + .tuner_attach = {tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_stv6110, tuner_attach_stv6110}, + .fe_config = {&fe_cineS2, &fe_cineS2, &fe_cineS2_2, &fe_cineS2_2}, + .tuner_config = {&tuner_cineS2_0, &tuner_cineS2_1, &tuner_cineS2_0, &tuner_cineS2_1}, + .lnb = {0x0a, 0x08, 0x0b, 0x09}, .tsf = {3, 3}, - .fw_version = 15, + .fw_version = 18, + .msi_supported = true, }; static struct ngene_info ngene_info_m780 = { @@ -321,6 +437,7 @@ static struct pci_driver ngene_pci_driver = { .probe = ngene_probe, .remove = __devexit_p(ngene_remove), .err_handler = &ngene_errors, + .shutdown = ngene_shutdown, }; static __init int module_init_ngene(void) diff --git a/drivers/media/dvb/ngene/ngene-core.c b/drivers/media/dvb/ngene/ngene-core.c index dc073bdc623a..175a0f6c2a4c 100644 --- a/drivers/media/dvb/ngene/ngene-core.c +++ b/drivers/media/dvb/ngene/ngene-core.c @@ -45,6 +45,9 @@ static int one_adapter = 1; module_param(one_adapter, int, 0444); MODULE_PARM_DESC(one_adapter, "Use only one adapter."); +static int shutdown_workaround; +module_param(shutdown_workaround, int, 0644); +MODULE_PARM_DESC(shutdown_workaround, "Activate workaround for shutdown problem with some chipsets."); static int debug; module_param(debug, int, 0444); @@ -143,7 +146,7 @@ static void demux_tasklet(unsigned long data) } } else { if (chan->HWState == HWSTATE_RUN) { - u32 Flags = 0; + u32 Flags = chan->DataFormatFlags; IBufferExchange *exch1 = chan->pBufferExchange; IBufferExchange *exch2 = chan->pBufferExchange2; if (Cur->ngeneBuffer.SR.Flags & 0x01) @@ -474,9 +477,9 @@ static u8 SPDIFConfiguration[10] = { /* Set NGENE I2S Config to transport stream compatible mode */ -static u8 TS_I2SConfiguration[4] = { 0x3E, 0x1A, 0x00, 0x00 }; /*3e 18 00 00 ?*/ +static u8 TS_I2SConfiguration[4] = { 0x3E, 0x18, 0x00, 0x00 }; -static u8 TS_I2SOutConfiguration[4] = { 0x80, 0x20, 0x00, 0x00 }; +static u8 TS_I2SOutConfiguration[4] = { 0x80, 0x04, 0x00, 0x00 }; static u8 ITUDecoderSetup[4][16] = { {0x1c, 0x13, 0x01, 0x68, 0x3d, 0x90, 0x14, 0x20, /* SDTV */ @@ -749,13 +752,11 @@ void set_transfer(struct ngene_channel *chan, int state) if (chan->mode & NGENE_IO_TSOUT) { chan->pBufferExchange = tsout_exchange; /* 0x66666666 = 50MHz *2^33 /250MHz */ - chan->AudioDTOValue = 0x66666666; - /* set_dto(chan, 38810700+1000); */ - /* set_dto(chan, 19392658); */ + chan->AudioDTOValue = 0x80000000; + chan->AudioDTOUpdated = 1; } if (chan->mode & NGENE_IO_TSIN) chan->pBufferExchange = tsin_exchange; - /* ngwritel(0, 0x9310); */ spin_unlock_irq(&chan->state_lock); } else ;/* printk(KERN_INFO DEVICE_NAME ": lock=%08x\n", @@ -1168,6 +1169,7 @@ static void ngene_release_buffers(struct ngene *dev) iounmap(dev->iomem); free_common_buffers(dev); vfree(dev->tsout_buf); + vfree(dev->tsin_buf); vfree(dev->ain_buf); vfree(dev->vin_buf); vfree(dev); @@ -1184,6 +1186,13 @@ static int ngene_get_buffers(struct ngene *dev) dvb_ringbuffer_init(&dev->tsout_rbuf, dev->tsout_buf, TSOUT_BUF_SIZE); } + if (dev->card_info->io_type[2]&NGENE_IO_TSIN) { + dev->tsin_buf = vmalloc(TSIN_BUF_SIZE); + if (!dev->tsin_buf) + return -ENOMEM; + dvb_ringbuffer_init(&dev->tsin_rbuf, + dev->tsin_buf, TSIN_BUF_SIZE); + } if (dev->card_info->io_type[2] & NGENE_IO_AIN) { dev->ain_buf = vmalloc(AIN_BUF_SIZE); if (!dev->ain_buf) @@ -1257,6 +1266,10 @@ static int ngene_load_firm(struct ngene *dev) fw_name = "ngene_17.fw"; dev->cmd_timeout_workaround = true; break; + case 18: + size = 0; + fw_name = "ngene_18.fw"; + break; } if (request_firmware(&fw, fw_name, &dev->pci_dev->dev) < 0) { @@ -1266,6 +1279,8 @@ static int ngene_load_firm(struct ngene *dev) ": Copy %s to your hotplug directory!\n", fw_name); return -1; } + if (size == 0) + size = fw->size; if (size != fw->size) { printk(KERN_ERR DEVICE_NAME ": Firmware %s has invalid size!", fw_name); @@ -1301,6 +1316,35 @@ static void ngene_stop(struct ngene *dev) #endif } +static int ngene_buffer_config(struct ngene *dev) +{ + int stat; + + if (dev->card_info->fw_version >= 17) { + u8 tsin12_config[6] = { 0x60, 0x60, 0x00, 0x00, 0x00, 0x00 }; + u8 tsin1234_config[6] = { 0x30, 0x30, 0x00, 0x30, 0x30, 0x00 }; + u8 tsio1235_config[6] = { 0x30, 0x30, 0x00, 0x28, 0x00, 0x38 }; + u8 *bconf = tsin12_config; + + if (dev->card_info->io_type[2]&NGENE_IO_TSIN && + dev->card_info->io_type[3]&NGENE_IO_TSIN) { + bconf = tsin1234_config; + if (dev->card_info->io_type[4]&NGENE_IO_TSOUT && + dev->ci.en) + bconf = tsio1235_config; + } + stat = ngene_command_config_free_buf(dev, bconf); + } else { + int bconf = BUFFER_CONFIG_4422; + + if (dev->card_info->io_type[3] == NGENE_IO_TSIN) + bconf = BUFFER_CONFIG_3333; + stat = ngene_command_config_buf(dev, bconf); + } + return stat; +} + + static int ngene_start(struct ngene *dev) { int stat; @@ -1365,23 +1409,6 @@ static int ngene_start(struct ngene *dev) if (stat < 0) goto fail; - if (dev->card_info->fw_version == 17) { - u8 tsin4_config[6] = { - 3072 / 64, 3072 / 64, 0, 3072 / 64, 3072 / 64, 0}; - u8 default_config[6] = { - 4096 / 64, 4096 / 64, 0, 2048 / 64, 2048 / 64, 0}; - u8 *bconf = default_config; - - if (dev->card_info->io_type[3] == NGENE_IO_TSIN) - bconf = tsin4_config; - dprintk(KERN_DEBUG DEVICE_NAME ": FW 17 buffer config\n"); - stat = ngene_command_config_free_buf(dev, bconf); - } else { - int bconf = BUFFER_CONFIG_4422; - if (dev->card_info->io_type[3] == NGENE_IO_TSIN) - bconf = BUFFER_CONFIG_3333; - stat = ngene_command_config_buf(dev, bconf); - } if (!stat) return stat; @@ -1397,9 +1424,6 @@ fail2: return stat; } - - - /****************************************************************************/ /****************************************************************************/ /****************************************************************************/ @@ -1408,20 +1432,25 @@ static void release_channel(struct ngene_channel *chan) { struct dvb_demux *dvbdemux = &chan->demux; struct ngene *dev = chan->dev; - struct ngene_info *ni = dev->card_info; - int io = ni->io_type[chan->number]; - if (chan->dev->cmd_timeout_workaround && chan->running) + if (chan->running) set_transfer(chan, 0); tasklet_kill(&chan->demux_tasklet); - if (io & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { - if (chan->fe) { - dvb_unregister_frontend(chan->fe); - dvb_frontend_detach(chan->fe); - chan->fe = NULL; - } + if (chan->ci_dev) { + dvb_unregister_device(chan->ci_dev); + chan->ci_dev = NULL; + } + + if (chan->fe) { + dvb_unregister_frontend(chan->fe); + dvb_frontend_detach(chan->fe); + chan->fe = NULL; + } + + if (chan->has_demux) { + dvb_net_release(&chan->dvbnet); dvbdemux->dmx.close(&dvbdemux->dmx); dvbdemux->dmx.remove_frontend(&dvbdemux->dmx, &chan->hw_frontend); @@ -1429,9 +1458,12 @@ static void release_channel(struct ngene_channel *chan) &chan->mem_frontend); dvb_dmxdev_release(&chan->dmxdev); dvb_dmx_release(&chan->demux); + chan->has_demux = false; + } - if (chan->number == 0 || !one_adapter) - dvb_unregister_adapter(&dev->adapter[chan->number]); + if (chan->has_adapter) { + dvb_unregister_adapter(&dev->adapter[chan->number]); + chan->has_adapter = false; } } @@ -1449,9 +1481,27 @@ static int init_channel(struct ngene_channel *chan) chan->type = io; chan->mode = chan->type; /* for now only one mode */ + if (io & NGENE_IO_TSIN) { + chan->fe = NULL; + if (ni->demod_attach[nr]) { + ret = ni->demod_attach[nr](chan); + if (ret < 0) + goto err; + } + if (chan->fe && ni->tuner_attach[nr]) { + ret = ni->tuner_attach[nr](chan); + if (ret < 0) + goto err; + } + } + + if (!dev->ci.en && (io & NGENE_IO_TSOUT)) + return 0; + if (io & (NGENE_IO_TSIN | NGENE_IO_TSOUT)) { if (nr >= STREAM_AUDIOIN1) chan->DataFormatFlags = DF_SWAP32; + if (nr == 0 || !one_adapter || dev->first_adapter == NULL) { adapter = &dev->adapter[nr]; ret = dvb_register_adapter(adapter, "nGene", @@ -1459,40 +1509,50 @@ static int init_channel(struct ngene_channel *chan) &chan->dev->pci_dev->dev, adapter_nr); if (ret < 0) - return ret; + goto err; if (dev->first_adapter == NULL) dev->first_adapter = adapter; - } else { + chan->has_adapter = true; + } else adapter = dev->first_adapter; - } + } + if (dev->ci.en && (io & NGENE_IO_TSOUT)) { + dvb_ca_en50221_init(adapter, dev->ci.en, 0, 1); + set_transfer(chan, 1); + set_transfer(&chan->dev->channel[2], 1); + dvb_register_device(adapter, &chan->ci_dev, + &ngene_dvbdev_ci, (void *) chan, + DVB_DEVICE_SEC); + if (!chan->ci_dev) + goto err; + } + + if (chan->fe) { + if (dvb_register_frontend(adapter, chan->fe) < 0) + goto err; + chan->has_demux = true; + } + + if (chan->has_demux) { ret = my_dvb_dmx_ts_card_init(dvbdemux, "SW demux", ngene_start_feed, ngene_stop_feed, chan); ret = my_dvb_dmxdev_ts_card_init(&chan->dmxdev, &chan->demux, &chan->hw_frontend, &chan->mem_frontend, adapter); + ret = dvb_net_init(adapter, &chan->dvbnet, &chan->demux.dmx); } - if (io & NGENE_IO_TSIN) { + return ret; + +err: + if (chan->fe) { + dvb_frontend_detach(chan->fe); chan->fe = NULL; - if (ni->demod_attach[nr]) - ni->demod_attach[nr](chan); - if (chan->fe) { - if (dvb_register_frontend(adapter, chan->fe) < 0) { - if (chan->fe->ops.release) - chan->fe->ops.release(chan->fe); - chan->fe = NULL; - } - } - if (chan->fe && ni->tuner_attach[nr]) - if (ni->tuner_attach[nr] (chan) < 0) { - printk(KERN_ERR DEVICE_NAME - ": Tuner attach failed on channel %d!\n", - nr); - } } - return ret; + release_channel(chan); + return 0; } static int init_channels(struct ngene *dev) @@ -1510,6 +1570,57 @@ static int init_channels(struct ngene *dev) return 0; } +static void cxd_attach(struct ngene *dev) +{ + struct ngene_ci *ci = &dev->ci; + + ci->en = cxd2099_attach(0x40, dev, &dev->channel[0].i2c_adapter); + ci->dev = dev; + return; +} + +static void cxd_detach(struct ngene *dev) +{ + struct ngene_ci *ci = &dev->ci; + + dvb_ca_en50221_release(ci->en); + kfree(ci->en); + ci->en = 0; +} + +/***********************************/ +/* workaround for shutdown failure */ +/***********************************/ + +static void ngene_unlink(struct ngene *dev) +{ + struct ngene_command com; + + com.cmd.hdr.Opcode = CMD_MEM_WRITE; + com.cmd.hdr.Length = 3; + com.cmd.MemoryWrite.address = 0x910c; + com.cmd.MemoryWrite.data = 0xff; + com.in_len = 3; + com.out_len = 1; + + down(&dev->cmd_mutex); + ngwritel(0, NGENE_INT_ENABLE); + ngene_command_mutex(dev, &com); + up(&dev->cmd_mutex); +} + +void ngene_shutdown(struct pci_dev *pdev) +{ + struct ngene *dev = (struct ngene *)pci_get_drvdata(pdev); + + if (!dev || !shutdown_workaround) + return; + + printk(KERN_INFO DEVICE_NAME ": shutdown workaround...\n"); + ngene_unlink(dev); + pci_disable_device(pdev); +} + /****************************************************************************/ /* device probe/remove calls ************************************************/ /****************************************************************************/ @@ -1522,6 +1633,8 @@ void __devexit ngene_remove(struct pci_dev *pdev) tasklet_kill(&dev->event_tasklet); for (i = MAX_STREAM - 1; i >= 0; i--) release_channel(&dev->channel[i]); + if (dev->ci.en) + cxd_detach(dev); ngene_stop(dev); ngene_release_buffers(dev); pci_set_drvdata(pdev, NULL); @@ -1557,6 +1670,13 @@ int __devinit ngene_probe(struct pci_dev *pci_dev, if (stat < 0) goto fail1; + cxd_attach(dev); + + stat = ngene_buffer_config(dev); + if (stat < 0) + goto fail1; + + dev->i2c_current_bus = -1; /* Register DVB adapters and devices for both channels */ diff --git a/drivers/media/dvb/ngene/ngene-dvb.c b/drivers/media/dvb/ngene/ngene-dvb.c index 3832e5983c19..0b4943233166 100644 --- a/drivers/media/dvb/ngene/ngene-dvb.c +++ b/drivers/media/dvb/ngene/ngene-dvb.c @@ -47,6 +47,64 @@ /* COMMAND API interface ****************************************************/ /****************************************************************************/ +static ssize_t ts_write(struct file *file, const char *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ngene_channel *chan = dvbdev->priv; + struct ngene *dev = chan->dev; + + if (wait_event_interruptible(dev->tsout_rbuf.queue, + dvb_ringbuffer_free + (&dev->tsout_rbuf) >= count) < 0) + return 0; + + dvb_ringbuffer_write(&dev->tsout_rbuf, buf, count); + + return count; +} + +static ssize_t ts_read(struct file *file, char *buf, + size_t count, loff_t *ppos) +{ + struct dvb_device *dvbdev = file->private_data; + struct ngene_channel *chan = dvbdev->priv; + struct ngene *dev = chan->dev; + int left, avail; + + left = count; + while (left) { + if (wait_event_interruptible( + dev->tsin_rbuf.queue, + dvb_ringbuffer_avail(&dev->tsin_rbuf) > 0) < 0) + return -EAGAIN; + avail = dvb_ringbuffer_avail(&dev->tsin_rbuf); + if (avail > left) + avail = left; + dvb_ringbuffer_read_user(&dev->tsin_rbuf, buf, avail); + left -= avail; + buf += avail; + } + return count; +} + +static const struct file_operations ci_fops = { + .owner = THIS_MODULE, + .read = ts_read, + .write = ts_write, + .open = dvb_generic_open, + .release = dvb_generic_release, +}; + +struct dvb_device ngene_dvbdev_ci = { + .priv = 0, + .readers = -1, + .writers = -1, + .users = -1, + .fops = &ci_fops, +}; + + /****************************************************************************/ /* DVB functions and API interface ******************************************/ /****************************************************************************/ @@ -63,10 +121,21 @@ static void swap_buffer(u32 *p, u32 len) void *tsin_exchange(void *priv, void *buf, u32 len, u32 clock, u32 flags) { struct ngene_channel *chan = priv; + struct ngene *dev = chan->dev; - if (chan->users > 0) + if (flags & DF_SWAP32) + swap_buffer(buf, len); + if (dev->ci.en && chan->number == 2) { + if (dvb_ringbuffer_free(&dev->tsin_rbuf) > len) { + dvb_ringbuffer_write(&dev->tsin_rbuf, buf, len); + wake_up_interruptible(&dev->tsin_rbuf.queue); + } + return 0; + } + if (chan->users > 0) { dvb_dmx_swfilter(&chan->demux, buf, len); + } return NULL; } diff --git a/drivers/media/dvb/ngene/ngene.h b/drivers/media/dvb/ngene/ngene.h index 8fb4200f83f8..40fce9e3ae66 100644 --- a/drivers/media/dvb/ngene/ngene.h +++ b/drivers/media/dvb/ngene/ngene.h @@ -36,8 +36,11 @@ #include "dmxdev.h" #include "dvbdev.h" #include "dvb_demux.h" +#include "dvb_ca_en50221.h" #include "dvb_frontend.h" #include "dvb_ringbuffer.h" +#include "dvb_net.h" +#include "cxd2099.h" #define DEVICE_NAME "ngene" @@ -636,14 +639,18 @@ struct ngene_channel { int number; int type; int mode; + bool has_adapter; + bool has_demux; struct dvb_frontend *fe; struct dmxdev dmxdev; struct dvb_demux demux; + struct dvb_net dvbnet; struct dmx_frontend hw_frontend; struct dmx_frontend mem_frontend; int users; struct video_device *v4l_dev; + struct dvb_device *ci_dev; struct tasklet_struct demux_tasklet; struct SBufferHeader *nextBuffer; @@ -710,6 +717,15 @@ struct ngene_channel { int running; }; + +struct ngene_ci { + struct device device; + struct i2c_adapter i2c_adapter; + + struct ngene *dev; + struct dvb_ca_en50221 *en; +}; + struct ngene; typedef void (rx_cb_t)(struct ngene *, u32, u8); @@ -774,6 +790,10 @@ struct ngene { #define TSOUT_BUF_SIZE (512*188*8) struct dvb_ringbuffer tsout_rbuf; + u8 *tsin_buf; +#define TSIN_BUF_SIZE (512*188*8) + struct dvb_ringbuffer tsin_rbuf; + u8 *ain_buf; #define AIN_BUF_SIZE (128*1024) struct dvb_ringbuffer ain_rbuf; @@ -785,6 +805,8 @@ struct ngene { unsigned long exp_val; int prev_cmd; + + struct ngene_ci ci; }; struct ngene_info { @@ -863,6 +885,7 @@ struct ngene_buffer { int __devinit ngene_probe(struct pci_dev *pci_dev, const struct pci_device_id *id); void __devexit ngene_remove(struct pci_dev *pdev); +void ngene_shutdown(struct pci_dev *pdev); int ngene_command(struct ngene *dev, struct ngene_command *com); int ngene_command_gpio_set(struct ngene *dev, u8 select, u8 level); void set_transfer(struct ngene_channel *chan, int state); @@ -872,6 +895,7 @@ void FillTSBuffer(void *Buffer, int Length, u32 Flags); int ngene_i2c_init(struct ngene *dev, int dev_nr); /* Provided by ngene-dvb.c */ +extern struct dvb_device ngene_dvbdev_ci; void *tsout_exchange(void *priv, void *buf, u32 len, u32 clock, u32 flags); void *tsin_exchange(void *priv, void *buf, u32 len, u32 clock, u32 flags); int ngene_start_feed(struct dvb_demux_feed *dvbdmxfeed); diff --git a/drivers/media/rc/keymaps/Makefile b/drivers/media/rc/keymaps/Makefile index 0659e9f50144..f0c80559b303 100644 --- a/drivers/media/rc/keymaps/Makefile +++ b/drivers/media/rc/keymaps/Makefile @@ -74,6 +74,7 @@ obj-$(CONFIG_RC_MAP) += rc-adstech-dvb-t-pci.o \ rc-real-audio-220-32-keys.o \ rc-streamzap.o \ rc-tbs-nec.o \ + rc-technisat-usb2.o \ rc-terratec-cinergy-xs.o \ rc-terratec-slim.o \ rc-tevii-nec.o \ diff --git a/drivers/media/rc/keymaps/rc-technisat-usb2.c b/drivers/media/rc/keymaps/rc-technisat-usb2.c new file mode 100644 index 000000000000..4afe5774f192 --- /dev/null +++ b/drivers/media/rc/keymaps/rc-technisat-usb2.c @@ -0,0 +1,93 @@ +/* rc-technisat-usb2.c - Keytable for SkyStar HD USB + * + * Copyright (C) 2010 Patrick Boettcher, + * Kernel Labs Inc. PO Box 745, St James, NY 11780 + * + * Development was sponsored by Technisat Digital UK Limited, whose + * registered office is Witan Gate House 500 - 600 Witan Gate West, + * Milton Keynes, MK9 1SH + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + * + * THIS PROGRAM IS PROVIDED "AS IS" AND BOTH THE COPYRIGHT HOLDER AND + * TECHNISAT DIGITAL UK LTD DISCLAIM ALL WARRANTIES WITH REGARD TO + * THIS PROGRAM INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY OR + * FITNESS FOR A PARTICULAR PURPOSE. NEITHER THE COPYRIGHT HOLDER + * NOR TECHNISAT DIGITAL UK LIMITED SHALL BE LIABLE FOR ANY SPECIAL, + * DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER + * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION + * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR + * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS PROGRAM. See the + * GNU General Public License for more details. + */ + +#include <media/rc-map.h> + +static struct rc_map_table technisat_usb2[] = { + {0x0a0c, KEY_POWER}, + {0x0a01, KEY_1}, + {0x0a02, KEY_2}, + {0x0a03, KEY_3}, + {0x0a0d, KEY_MUTE}, + {0x0a04, KEY_4}, + {0x0a05, KEY_5}, + {0x0a06, KEY_6}, + {0x0a38, KEY_VIDEO}, /* EXT */ + {0x0a07, KEY_7}, + {0x0a08, KEY_8}, + {0x0a09, KEY_9}, + {0x0a00, KEY_0}, + {0x0a4f, KEY_INFO}, + {0x0a20, KEY_CHANNELUP}, + {0x0a52, KEY_MENU}, + {0x0a11, KEY_VOLUMEUP}, + {0x0a57, KEY_OK}, + {0x0a10, KEY_VOLUMEDOWN}, + {0x0a2f, KEY_EPG}, + {0x0a21, KEY_CHANNELDOWN}, + {0x0a22, KEY_REFRESH}, + {0x0a3c, KEY_TEXT}, + {0x0a76, KEY_ENTER}, /* HOOK */ + {0x0a0f, KEY_HELP}, + {0x0a6b, KEY_RED}, + {0x0a6c, KEY_GREEN}, + {0x0a6d, KEY_YELLOW}, + {0x0a6e, KEY_BLUE}, + {0x0a29, KEY_STOP}, + {0x0a23, KEY_LANGUAGE}, + {0x0a53, KEY_TV}, + {0x0a0a, KEY_PROGRAM}, +}; + +static struct rc_map_list technisat_usb2_map = { + .map = { + .scan = technisat_usb2, + .size = ARRAY_SIZE(technisat_usb2), + .rc_type = RC_TYPE_RC5, + .name = RC_MAP_TECHNISAT_USB2, + } +}; + +static int __init init_rc_map(void) +{ + return rc_map_register(&technisat_usb2_map); +} + +static void __exit exit_rc_map(void) +{ + rc_map_unregister(&technisat_usb2_map); +} + +module_init(init_rc_map) +module_exit(exit_rc_map) + +MODULE_AUTHOR("Patrick Boettcher <pboettcher@kernellabs.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index aa021600e9df..d40a8fc01bfd 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -42,8 +42,30 @@ config VIDEO_TUNER config V4L2_MEM2MEM_DEV tristate - depends on VIDEOBUF_GEN + depends on VIDEOBUF2_CORE +config VIDEOBUF2_CORE + tristate + +config VIDEOBUF2_MEMOPS + tristate + +config VIDEOBUF2_DMA_CONTIG + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + tristate + +config VIDEOBUF2_VMALLOC + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + tristate + + +config VIDEOBUF2_DMA_SG + #depends on HAS_DMA + select VIDEOBUF2_CORE + select VIDEOBUF2_MEMOPS + tristate # # Multimedia Video device configuration # @@ -527,7 +549,7 @@ config VIDEO_VIVI depends on VIDEO_DEV && VIDEO_V4L2 && !SPARC32 && !SPARC64 depends on FRAMEBUFFER_CONSOLE || STI_CONSOLE select FONT_8x16 - select VIDEOBUF_VMALLOC + select VIDEOBUF2_VMALLOC default n ---help--- Enables a virtual video driver. This device shows a color bar @@ -718,6 +740,12 @@ config VIDEO_VIA_CAMERA Chrome9 chipsets. Currently only tested on OLPC xo-1.5 systems with ov7670 sensors. +config VIDEO_NOON010PC30 + tristate "NOON010PC30 CIF camera sensor support" + depends on I2C && VIDEO_V4L2 + ---help--- + This driver supports NOON010PC30 CIF camera from Siliconfile + config SOC_CAMERA tristate "SoC camera support" depends on VIDEO_V4L2 && HAS_DMA && I2C @@ -967,7 +995,7 @@ if V4L_MEM2MEM_DRIVERS config VIDEO_MEM2MEM_TESTDEV tristate "Virtual test device for mem2mem framework" depends on VIDEO_DEV && VIDEO_V4L2 - select VIDEOBUF_VMALLOC + select VIDEOBUF2_VMALLOC select V4L2_MEM2MEM_DEV default n ---help--- @@ -977,7 +1005,7 @@ config VIDEO_MEM2MEM_TESTDEV config VIDEO_SAMSUNG_S5P_FIMC tristate "Samsung S5P FIMC (video postprocessor) driver" depends on VIDEO_DEV && VIDEO_V4L2 && PLAT_S5P - select VIDEOBUF_DMA_CONTIG + select VIDEOBUF2_DMA_CONTIG select V4L2_MEM2MEM_DEV help This is a v4l2 driver for the S5P camera interface diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index a509d317e258..251b7cac3f91 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -67,6 +67,7 @@ obj-$(CONFIG_VIDEO_TCM825X) += tcm825x.o obj-$(CONFIG_VIDEO_TVEEPROM) += tveeprom.o obj-$(CONFIG_VIDEO_MT9V011) += mt9v011.o obj-$(CONFIG_VIDEO_SR030PC30) += sr030pc30.o +obj-$(CONFIG_VIDEO_NOON010PC30) += noon010pc30.o obj-$(CONFIG_SOC_CAMERA_IMX074) += imx074.o obj-$(CONFIG_SOC_CAMERA_MT9M001) += mt9m001.o @@ -111,6 +112,12 @@ obj-$(CONFIG_VIDEOBUF_VMALLOC) += videobuf-vmalloc.o obj-$(CONFIG_VIDEOBUF_DVB) += videobuf-dvb.o obj-$(CONFIG_VIDEO_BTCX) += btcx-risc.o +obj-$(CONFIG_VIDEOBUF2_CORE) += videobuf2-core.o +obj-$(CONFIG_VIDEOBUF2_MEMOPS) += videobuf2-memops.o +obj-$(CONFIG_VIDEOBUF2_VMALLOC) += videobuf2-vmalloc.o +obj-$(CONFIG_VIDEOBUF2_DMA_CONTIG) += videobuf2-dma-contig.o +obj-$(CONFIG_VIDEOBUF2_DMA_SG) += videobuf2-dma-sg.o + obj-$(CONFIG_V4L2_MEM2MEM_DEV) += v4l2-mem2mem.o obj-$(CONFIG_VIDEO_M32R_AR_M64278) += arv.o diff --git a/drivers/media/video/adv7343.c b/drivers/media/video/adv7343.c index 41b2930d0ce4..021fab23070d 100644 --- a/drivers/media/video/adv7343.c +++ b/drivers/media/video/adv7343.c @@ -29,6 +29,7 @@ #include <media/adv7343.h> #include <media/v4l2-device.h> #include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> #include "adv7343_regs.h" @@ -41,15 +42,13 @@ MODULE_PARM_DESC(debug, "Debug level 0-1"); struct adv7343_state { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; u8 reg00; u8 reg01; u8 reg02; u8 reg35; u8 reg80; u8 reg82; - int bright; - int hue; - int gain; u32 output; v4l2_std_id std; }; @@ -59,6 +58,11 @@ static inline struct adv7343_state *to_state(struct v4l2_subdev *sd) return container_of(sd, struct adv7343_state, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct adv7343_state, hdl)->sd; +} + static inline int adv7343_write(struct v4l2_subdev *sd, u8 reg, u8 value) { struct i2c_client *client = v4l2_get_subdevdata(sd); @@ -268,111 +272,22 @@ static int adv7343_log_status(struct v4l2_subdev *sd) return 0; } -static int adv7343_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_BRIGHTNESS: - return v4l2_ctrl_query_fill(qc, ADV7343_BRIGHTNESS_MIN, - ADV7343_BRIGHTNESS_MAX, 1, - ADV7343_BRIGHTNESS_DEF); - case V4L2_CID_HUE: - return v4l2_ctrl_query_fill(qc, ADV7343_HUE_MIN, - ADV7343_HUE_MAX, 1 , - ADV7343_HUE_DEF); - case V4L2_CID_GAIN: - return v4l2_ctrl_query_fill(qc, ADV7343_GAIN_MIN, - ADV7343_GAIN_MAX, 1, - ADV7343_GAIN_DEF); - default: - break; - } - - return 0; -} - -static int adv7343_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct adv7343_state *state = to_state(sd); - int err = 0; - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - if (ctrl->value < ADV7343_BRIGHTNESS_MIN || - ctrl->value > ADV7343_BRIGHTNESS_MAX) { - v4l2_dbg(1, debug, sd, - "invalid brightness settings %d\n", - ctrl->value); - return -ERANGE; - } - - state->bright = ctrl->value; - err = adv7343_write(sd, ADV7343_SD_BRIGHTNESS_WSS, - state->bright); - break; - - case V4L2_CID_HUE: - if (ctrl->value < ADV7343_HUE_MIN || - ctrl->value > ADV7343_HUE_MAX) { - v4l2_dbg(1, debug, sd, "invalid hue settings %d\n", - ctrl->value); - return -ERANGE; - } - - state->hue = ctrl->value; - err = adv7343_write(sd, ADV7343_SD_HUE_REG, state->hue); - break; - - case V4L2_CID_GAIN: - if (ctrl->value < ADV7343_GAIN_MIN || - ctrl->value > ADV7343_GAIN_MAX) { - v4l2_dbg(1, debug, sd, "invalid gain settings %d\n", - ctrl->value); - return -ERANGE; - } - - if ((ctrl->value > POSITIVE_GAIN_MAX) && - (ctrl->value < NEGATIVE_GAIN_MIN)) { - v4l2_dbg(1, debug, sd, - "gain settings not within the specified range\n"); - return -ERANGE; - } - - state->gain = ctrl->value; - err = adv7343_write(sd, ADV7343_DAC2_OUTPUT_LEVEL, state->gain); - break; - - default: - return -EINVAL; - } - - if (err < 0) - v4l2_err(sd, "Failed to set the encoder controls\n"); - - return err; -} - -static int adv7343_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int adv7343_s_ctrl(struct v4l2_ctrl *ctrl) { - struct adv7343_state *state = to_state(sd); + struct v4l2_subdev *sd = to_sd(ctrl); switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - ctrl->value = state->bright; - break; + return adv7343_write(sd, ADV7343_SD_BRIGHTNESS_WSS, + ctrl->val); case V4L2_CID_HUE: - ctrl->value = state->hue; - break; + return adv7343_write(sd, ADV7343_SD_HUE_REG, ctrl->val); case V4L2_CID_GAIN: - ctrl->value = state->gain; - break; - - default: - return -EINVAL; + return adv7343_write(sd, ADV7343_DAC2_OUTPUT_LEVEL, ctrl->val); } - - return 0; + return -EINVAL; } static int adv7343_g_chip_ident(struct v4l2_subdev *sd, @@ -383,12 +298,20 @@ static int adv7343_g_chip_ident(struct v4l2_subdev *sd, return v4l2_chip_ident_i2c_client(client, chip, V4L2_IDENT_ADV7343, 0); } +static const struct v4l2_ctrl_ops adv7343_ctrl_ops = { + .s_ctrl = adv7343_s_ctrl, +}; + static const struct v4l2_subdev_core_ops adv7343_core_ops = { - .log_status = adv7343_log_status, - .g_chip_ident = adv7343_g_chip_ident, - .g_ctrl = adv7343_g_ctrl, - .s_ctrl = adv7343_s_ctrl, - .queryctrl = adv7343_queryctrl, + .log_status = adv7343_log_status, + .g_chip_ident = adv7343_g_chip_ident, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, }; static int adv7343_s_std_output(struct v4l2_subdev *sd, v4l2_std_id std) @@ -468,6 +391,7 @@ static int adv7343_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct adv7343_state *state; + int err; if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) return -ENODEV; @@ -490,15 +414,46 @@ static int adv7343_probe(struct i2c_client *client, state->std = V4L2_STD_NTSC; v4l2_i2c_subdev_init(&state->sd, client, &adv7343_ops); - return adv7343_initialize(&state->sd); + + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_BRIGHTNESS, ADV7343_BRIGHTNESS_MIN, + ADV7343_BRIGHTNESS_MAX, 1, + ADV7343_BRIGHTNESS_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_HUE, ADV7343_HUE_MIN, + ADV7343_HUE_MAX, 1, + ADV7343_HUE_DEF); + v4l2_ctrl_new_std(&state->hdl, &adv7343_ctrl_ops, + V4L2_CID_GAIN, ADV7343_GAIN_MIN, + ADV7343_GAIN_MAX, 1, + ADV7343_GAIN_DEF); + state->sd.ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + return err; + } + v4l2_ctrl_handler_setup(&state->hdl); + + err = adv7343_initialize(&state->sd); + if (err) { + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + } + return err; } static int adv7343_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct adv7343_state *state = to_state(sd); v4l2_device_unregister_subdev(sd); - kfree(to_state(sd)); + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); return 0; } diff --git a/drivers/media/video/adv7343_regs.h b/drivers/media/video/adv7343_regs.h index 3431045b33da..446606764346 100644 --- a/drivers/media/video/adv7343_regs.h +++ b/drivers/media/video/adv7343_regs.h @@ -102,10 +102,6 @@ struct adv7343_std_info { /* Bit masks for DAC output levels */ #define DAC_OUTPUT_LEVEL_MASK (0xFF) -#define POSITIVE_GAIN_MAX (0x40) -#define POSITIVE_GAIN_MIN (0x00) -#define NEGATIVE_GAIN_MAX (0xFF) -#define NEGATIVE_GAIN_MIN (0xC0) /* Bit masks for soft reset register */ #define SOFT_RESET (0x02) @@ -178,8 +174,8 @@ struct adv7343_std_info { #define ADV7343_HUE_MAX (255) #define ADV7343_HUE_MIN (0) #define ADV7343_HUE_DEF (127) -#define ADV7343_GAIN_MAX (255) -#define ADV7343_GAIN_MIN (0) +#define ADV7343_GAIN_MAX (64) +#define ADV7343_GAIN_MIN (-64) #define ADV7343_GAIN_DEF (0) #endif diff --git a/drivers/media/video/bt819.c b/drivers/media/video/bt819.c index c38300fc0b1d..f87204461cb4 100644 --- a/drivers/media/video/bt819.c +++ b/drivers/media/video/bt819.c @@ -37,6 +37,7 @@ #include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> #include <media/bt819.h> MODULE_DESCRIPTION("Brooktree-819 video decoder driver"); @@ -52,16 +53,13 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)"); struct bt819 { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; unsigned char reg[32]; v4l2_std_id norm; int ident; int input; int enable; - int bright; - int contrast; - int hue; - int sat; }; static inline struct bt819 *to_bt819(struct v4l2_subdev *sd) @@ -69,6 +67,11 @@ static inline struct bt819 *to_bt819(struct v4l2_subdev *sd) return container_of(sd, struct bt819, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct bt819, hdl)->sd; +} + struct timing { int hactive; int hdelay; @@ -333,71 +336,35 @@ static int bt819_s_stream(struct v4l2_subdev *sd, int enable) return 0; } -static int bt819_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_BRIGHTNESS: - v4l2_ctrl_query_fill(qc, -128, 127, 1, 0); - break; - - case V4L2_CID_CONTRAST: - v4l2_ctrl_query_fill(qc, 0, 511, 1, 256); - break; - - case V4L2_CID_SATURATION: - v4l2_ctrl_query_fill(qc, 0, 511, 1, 256); - break; - - case V4L2_CID_HUE: - v4l2_ctrl_query_fill(qc, -128, 127, 1, 0); - break; - - default: - return -EINVAL; - } - return 0; -} - -static int bt819_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int bt819_s_ctrl(struct v4l2_ctrl *ctrl) { + struct v4l2_subdev *sd = to_sd(ctrl); struct bt819 *decoder = to_bt819(sd); int temp; switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - if (decoder->bright == ctrl->value) - break; - decoder->bright = ctrl->value; - bt819_write(decoder, 0x0a, decoder->bright); + bt819_write(decoder, 0x0a, ctrl->val); break; case V4L2_CID_CONTRAST: - if (decoder->contrast == ctrl->value) - break; - decoder->contrast = ctrl->value; - bt819_write(decoder, 0x0c, decoder->contrast & 0xff); - bt819_setbit(decoder, 0x0b, 2, ((decoder->contrast >> 8) & 0x01)); + bt819_write(decoder, 0x0c, ctrl->val & 0xff); + bt819_setbit(decoder, 0x0b, 2, ((ctrl->val >> 8) & 0x01)); break; case V4L2_CID_SATURATION: - if (decoder->sat == ctrl->value) - break; - decoder->sat = ctrl->value; - bt819_write(decoder, 0x0d, (decoder->sat >> 7) & 0xff); - bt819_setbit(decoder, 0x0b, 1, ((decoder->sat >> 15) & 0x01)); + bt819_write(decoder, 0x0d, (ctrl->val >> 7) & 0xff); + bt819_setbit(decoder, 0x0b, 1, ((ctrl->val >> 15) & 0x01)); /* Ratio between U gain and V gain must stay the same as the ratio between the default U and V gain values. */ - temp = (decoder->sat * 180) / 254; + temp = (ctrl->val * 180) / 254; bt819_write(decoder, 0x0e, (temp >> 7) & 0xff); bt819_setbit(decoder, 0x0b, 0, (temp >> 15) & 0x01); break; case V4L2_CID_HUE: - if (decoder->hue == ctrl->value) - break; - decoder->hue = ctrl->value; - bt819_write(decoder, 0x0f, decoder->hue); + bt819_write(decoder, 0x0f, ctrl->val); break; default: @@ -406,29 +373,6 @@ static int bt819_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) return 0; } -static int bt819_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct bt819 *decoder = to_bt819(sd); - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - ctrl->value = decoder->bright; - break; - case V4L2_CID_CONTRAST: - ctrl->value = decoder->contrast; - break; - case V4L2_CID_SATURATION: - ctrl->value = decoder->sat; - break; - case V4L2_CID_HUE: - ctrl->value = decoder->hue; - break; - default: - return -EINVAL; - } - return 0; -} - static int bt819_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) { struct bt819 *decoder = to_bt819(sd); @@ -439,11 +383,19 @@ static int bt819_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident /* ----------------------------------------------------------------------- */ +static const struct v4l2_ctrl_ops bt819_ctrl_ops = { + .s_ctrl = bt819_s_ctrl, +}; + static const struct v4l2_subdev_core_ops bt819_core_ops = { .g_chip_ident = bt819_g_chip_ident, - .g_ctrl = bt819_g_ctrl, - .s_ctrl = bt819_s_ctrl, - .queryctrl = bt819_queryctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, .s_std = bt819_s_std, }; @@ -505,23 +457,40 @@ static int bt819_probe(struct i2c_client *client, decoder->norm = V4L2_STD_NTSC; decoder->input = 0; decoder->enable = 1; - decoder->bright = 0; - decoder->contrast = 0xd8; /* 100% of original signal */ - decoder->hue = 0; - decoder->sat = 0xfe; /* 100% of original signal */ i = bt819_init(sd); if (i < 0) v4l2_dbg(1, debug, sd, "init status %d\n", i); + + v4l2_ctrl_handler_init(&decoder->hdl, 4); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_CONTRAST, 0, 511, 1, 0xd8); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_SATURATION, 0, 511, 1, 0xfe); + v4l2_ctrl_new_std(&decoder->hdl, &bt819_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); return 0; } static int bt819_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct bt819 *decoder = to_bt819(sd); v4l2_device_unregister_subdev(sd); - kfree(to_bt819(sd)); + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); return 0; } diff --git a/drivers/media/video/cs5345.c b/drivers/media/video/cs5345.c index 9358fe77e562..5909f2557ab4 100644 --- a/drivers/media/video/cs5345.c +++ b/drivers/media/video/cs5345.c @@ -25,6 +25,7 @@ #include <linux/slab.h> #include <media/v4l2-device.h> #include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> MODULE_DESCRIPTION("i2c device driver for cs5345 Audio ADC"); MODULE_AUTHOR("Hans Verkuil"); @@ -36,6 +37,20 @@ module_param(debug, bool, 0644); MODULE_PARM_DESC(debug, "Debugging messages, 0=Off (default), 1=On"); +struct cs5345_state { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; +}; + +static inline struct cs5345_state *to_state(struct v4l2_subdev *sd) +{ + return container_of(sd, struct cs5345_state, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cs5345_state, hdl)->sd; +} /* ----------------------------------------------------------------------- */ @@ -65,33 +80,20 @@ static int cs5345_s_routing(struct v4l2_subdev *sd, return 0; } -static int cs5345_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int cs5345_s_ctrl(struct v4l2_ctrl *ctrl) { - if (ctrl->id == V4L2_CID_AUDIO_MUTE) { - ctrl->value = (cs5345_read(sd, 0x04) & 0x08) != 0; - return 0; - } - if (ctrl->id != V4L2_CID_AUDIO_VOLUME) - return -EINVAL; - ctrl->value = cs5345_read(sd, 0x07) & 0x3f; - if (ctrl->value >= 32) - ctrl->value = ctrl->value - 64; - return 0; -} + struct v4l2_subdev *sd = to_sd(ctrl); -static int cs5345_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - if (ctrl->id == V4L2_CID_AUDIO_MUTE) { - cs5345_write(sd, 0x04, ctrl->value ? 0x80 : 0); + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + cs5345_write(sd, 0x04, ctrl->val ? 0x80 : 0); + return 0; + case V4L2_CID_AUDIO_VOLUME: + cs5345_write(sd, 0x07, ((u8)ctrl->val) & 0x3f); + cs5345_write(sd, 0x08, ((u8)ctrl->val) & 0x3f); return 0; } - if (ctrl->id != V4L2_CID_AUDIO_VOLUME) - return -EINVAL; - if (ctrl->value > 24 || ctrl->value < -24) - return -EINVAL; - cs5345_write(sd, 0x07, ((u8)ctrl->value) & 0x3f); - cs5345_write(sd, 0x08, ((u8)ctrl->value) & 0x3f); - return 0; + return -EINVAL; } #ifdef CONFIG_VIDEO_ADV_DEBUG @@ -144,11 +146,20 @@ static int cs5345_log_status(struct v4l2_subdev *sd) /* ----------------------------------------------------------------------- */ +static const struct v4l2_ctrl_ops cs5345_ctrl_ops = { + .s_ctrl = cs5345_s_ctrl, +}; + static const struct v4l2_subdev_core_ops cs5345_core_ops = { .log_status = cs5345_log_status, .g_chip_ident = cs5345_g_chip_ident, - .g_ctrl = cs5345_g_ctrl, - .s_ctrl = cs5345_s_ctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, #ifdef CONFIG_VIDEO_ADV_DEBUG .g_register = cs5345_g_register, .s_register = cs5345_s_register, @@ -169,6 +180,7 @@ static const struct v4l2_subdev_ops cs5345_ops = { static int cs5345_probe(struct i2c_client *client, const struct i2c_device_id *id) { + struct cs5345_state *state; struct v4l2_subdev *sd; /* Check if the adapter supports the needed features */ @@ -178,11 +190,28 @@ static int cs5345_probe(struct i2c_client *client, v4l_info(client, "chip found @ 0x%x (%s)\n", client->addr << 1, client->adapter->name); - sd = kzalloc(sizeof(struct v4l2_subdev), GFP_KERNEL); - if (sd == NULL) + state = kzalloc(sizeof(struct cs5345_state), GFP_KERNEL); + if (state == NULL) return -ENOMEM; + sd = &state->sd; v4l2_i2c_subdev_init(sd, client, &cs5345_ops); + v4l2_ctrl_handler_init(&state->hdl, 2); + v4l2_ctrl_new_std(&state->hdl, &cs5345_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cs5345_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, -24, 24, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + return err; + } + /* set volume/mute */ + v4l2_ctrl_handler_setup(&state->hdl); + cs5345_write(sd, 0x02, 0x00); cs5345_write(sd, 0x04, 0x01); cs5345_write(sd, 0x09, 0x01); @@ -194,9 +223,11 @@ static int cs5345_probe(struct i2c_client *client, static int cs5345_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct cs5345_state *state = to_state(sd); v4l2_device_unregister_subdev(sd); - kfree(sd); + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); return 0; } diff --git a/drivers/media/video/cx18/cx18-av-audio.c b/drivers/media/video/cx18/cx18-av-audio.c index 43d09a24b262..4a24ffb17a7d 100644 --- a/drivers/media/video/cx18/cx18-av-audio.c +++ b/drivers/media/video/cx18/cx18-av-audio.c @@ -342,17 +342,6 @@ void cx18_av_audio_set_path(struct cx18 *cx) } } -static int get_volume(struct cx18 *cx) -{ - /* Volume runs +18dB to -96dB in 1/2dB steps - * change to fit the msp3400 -114dB to +12dB range */ - - /* check PATH1_VOLUME */ - int vol = 228 - cx18_av_read(cx, 0x8d4); - vol = (vol / 2) + 23; - return vol << 9; -} - static void set_volume(struct cx18 *cx, int volume) { /* First convert the volume to msp3400 values (0-127) */ @@ -369,52 +358,18 @@ static void set_volume(struct cx18 *cx, int volume) cx18_av_write(cx, 0x8d4, 228 - (vol * 2)); } -static int get_bass(struct cx18 *cx) -{ - /* bass is 49 steps +12dB to -12dB */ - - /* check PATH1_EQ_BASS_VOL */ - int bass = cx18_av_read(cx, 0x8d9) & 0x3f; - bass = (((48 - bass) * 0xffff) + 47) / 48; - return bass; -} - static void set_bass(struct cx18 *cx, int bass) { /* PATH1_EQ_BASS_VOL */ cx18_av_and_or(cx, 0x8d9, ~0x3f, 48 - (bass * 48 / 0xffff)); } -static int get_treble(struct cx18 *cx) -{ - /* treble is 49 steps +12dB to -12dB */ - - /* check PATH1_EQ_TREBLE_VOL */ - int treble = cx18_av_read(cx, 0x8db) & 0x3f; - treble = (((48 - treble) * 0xffff) + 47) / 48; - return treble; -} - static void set_treble(struct cx18 *cx, int treble) { /* PATH1_EQ_TREBLE_VOL */ cx18_av_and_or(cx, 0x8db, ~0x3f, 48 - (treble * 48 / 0xffff)); } -static int get_balance(struct cx18 *cx) -{ - /* balance is 7 bit, 0 to -96dB */ - - /* check PATH1_BAL_LEVEL */ - int balance = cx18_av_read(cx, 0x8d5) & 0x7f; - /* check PATH1_BAL_LEFT */ - if ((cx18_av_read(cx, 0x8d5) & 0x80) == 0) - balance = 0x80 - balance; - else - balance = 0x80 + balance; - return balance << 8; -} - static void set_balance(struct cx18 *cx, int balance) { int bal = balance >> 8; @@ -431,12 +386,6 @@ static void set_balance(struct cx18 *cx, int balance) } } -static int get_mute(struct cx18 *cx) -{ - /* check SRC1_MUTE_EN */ - return cx18_av_read(cx, 0x8d3) & 0x2 ? 1 : 0; -} - static void set_mute(struct cx18 *cx, int mute) { struct cx18_av_state *state = &cx->av_state; @@ -490,50 +439,33 @@ int cx18_av_s_clock_freq(struct v4l2_subdev *sd, u32 freq) return retval; } -int cx18_av_audio_g_ctrl(struct cx18 *cx, struct v4l2_control *ctrl) +static int cx18_av_audio_s_ctrl(struct v4l2_ctrl *ctrl) { - switch (ctrl->id) { - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = get_volume(cx); - break; - case V4L2_CID_AUDIO_BASS: - ctrl->value = get_bass(cx); - break; - case V4L2_CID_AUDIO_TREBLE: - ctrl->value = get_treble(cx); - break; - case V4L2_CID_AUDIO_BALANCE: - ctrl->value = get_balance(cx); - break; - case V4L2_CID_AUDIO_MUTE: - ctrl->value = get_mute(cx); - break; - default: - return -EINVAL; - } - return 0; -} + struct v4l2_subdev *sd = to_sd(ctrl); + struct cx18 *cx = v4l2_get_subdevdata(sd); -int cx18_av_audio_s_ctrl(struct cx18 *cx, struct v4l2_control *ctrl) -{ switch (ctrl->id) { case V4L2_CID_AUDIO_VOLUME: - set_volume(cx, ctrl->value); + set_volume(cx, ctrl->val); break; case V4L2_CID_AUDIO_BASS: - set_bass(cx, ctrl->value); + set_bass(cx, ctrl->val); break; case V4L2_CID_AUDIO_TREBLE: - set_treble(cx, ctrl->value); + set_treble(cx, ctrl->val); break; case V4L2_CID_AUDIO_BALANCE: - set_balance(cx, ctrl->value); + set_balance(cx, ctrl->val); break; case V4L2_CID_AUDIO_MUTE: - set_mute(cx, ctrl->value); + set_mute(cx, ctrl->val); break; default: return -EINVAL; } return 0; } + +const struct v4l2_ctrl_ops cx18_av_audio_ctrl_ops = { + .s_ctrl = cx18_av_audio_s_ctrl, +}; diff --git a/drivers/media/video/cx18/cx18-av-core.c b/drivers/media/video/cx18/cx18-av-core.c index a41951cab276..f164b7f610a5 100644 --- a/drivers/media/video/cx18/cx18-av-core.c +++ b/drivers/media/video/cx18/cx18-av-core.c @@ -129,6 +129,7 @@ static void cx18_av_initialize(struct v4l2_subdev *sd) { struct cx18_av_state *state = to_cx18_av_state(sd); struct cx18 *cx = v4l2_get_subdevdata(sd); + int default_volume; u32 v; cx18_av_loadfw(cx); @@ -247,8 +248,23 @@ static void cx18_av_initialize(struct v4l2_subdev *sd) /* CxDevWrReg(CXADEC_SRC_COMB_CFG, 0x6628021F); */ /* } */ cx18_av_write4(cx, CXADEC_SRC_COMB_CFG, 0x6628021F); - state->default_volume = 228 - cx18_av_read(cx, 0x8d4); - state->default_volume = ((state->default_volume / 2) + 23) << 9; + default_volume = cx18_av_read(cx, 0x8d4); + /* + * Enforce the legacy volume scale mapping limits to avoid + * -ERANGE errors when initializing the volume control + */ + if (default_volume > 228) { + /* Bottom out at -96 dB, v4l2 vol range 0x2e00-0x2fff */ + default_volume = 228; + cx18_av_write(cx, 0x8d4, 228); + } else if (default_volume < 20) { + /* Top out at + 8 dB, v4l2 vol range 0xfe00-0xffff */ + default_volume = 20; + cx18_av_write(cx, 0x8d4, 20); + } + default_volume = (((228 - default_volume) >> 1) + 23) << 9; + state->volume->cur.val = state->volume->default_value = default_volume; + v4l2_ctrl_handler_setup(&state->hdl); } static int cx18_av_reset(struct v4l2_subdev *sd, u32 val) @@ -901,126 +917,35 @@ static int cx18_av_s_radio(struct v4l2_subdev *sd) return 0; } -static int cx18_av_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int cx18_av_s_ctrl(struct v4l2_ctrl *ctrl) { + struct v4l2_subdev *sd = to_sd(ctrl); struct cx18 *cx = v4l2_get_subdevdata(sd); switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - if (ctrl->value < 0 || ctrl->value > 255) { - CX18_ERR_DEV(sd, "invalid brightness setting %d\n", - ctrl->value); - return -ERANGE; - } - - cx18_av_write(cx, 0x414, ctrl->value - 128); + cx18_av_write(cx, 0x414, ctrl->val - 128); break; case V4L2_CID_CONTRAST: - if (ctrl->value < 0 || ctrl->value > 127) { - CX18_ERR_DEV(sd, "invalid contrast setting %d\n", - ctrl->value); - return -ERANGE; - } - - cx18_av_write(cx, 0x415, ctrl->value << 1); + cx18_av_write(cx, 0x415, ctrl->val << 1); break; case V4L2_CID_SATURATION: - if (ctrl->value < 0 || ctrl->value > 127) { - CX18_ERR_DEV(sd, "invalid saturation setting %d\n", - ctrl->value); - return -ERANGE; - } - - cx18_av_write(cx, 0x420, ctrl->value << 1); - cx18_av_write(cx, 0x421, ctrl->value << 1); + cx18_av_write(cx, 0x420, ctrl->val << 1); + cx18_av_write(cx, 0x421, ctrl->val << 1); break; case V4L2_CID_HUE: - if (ctrl->value < -128 || ctrl->value > 127) { - CX18_ERR_DEV(sd, "invalid hue setting %d\n", - ctrl->value); - return -ERANGE; - } - - cx18_av_write(cx, 0x422, ctrl->value); + cx18_av_write(cx, 0x422, ctrl->val); break; - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_MUTE: - return cx18_av_audio_s_ctrl(cx, ctrl); - - default: - return -EINVAL; - } - return 0; -} - -static int cx18_av_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct cx18 *cx = v4l2_get_subdevdata(sd); - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - ctrl->value = (s8)cx18_av_read(cx, 0x414) + 128; - break; - case V4L2_CID_CONTRAST: - ctrl->value = cx18_av_read(cx, 0x415) >> 1; - break; - case V4L2_CID_SATURATION: - ctrl->value = cx18_av_read(cx, 0x420) >> 1; - break; - case V4L2_CID_HUE: - ctrl->value = (s8)cx18_av_read(cx, 0x422); - break; - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_MUTE: - return cx18_av_audio_g_ctrl(cx, ctrl); default: return -EINVAL; } return 0; } -static int cx18_av_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - struct cx18_av_state *state = to_cx18_av_state(sd); - - switch (qc->id) { - case V4L2_CID_BRIGHTNESS: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 128); - case V4L2_CID_CONTRAST: - case V4L2_CID_SATURATION: - return v4l2_ctrl_query_fill(qc, 0, 127, 1, 64); - case V4L2_CID_HUE: - return v4l2_ctrl_query_fill(qc, -128, 127, 1, 0); - default: - break; - } - - switch (qc->id) { - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 65535, - 65535 / 100, state->default_volume); - case V4L2_CID_AUDIO_MUTE: - return v4l2_ctrl_query_fill(qc, 0, 1, 1, 0); - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - return v4l2_ctrl_query_fill(qc, 0, 65535, 65535 / 100, 32768); - default: - return -EINVAL; - } - return -EINVAL; -} - static int cx18_av_s_mbus_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *fmt) { struct cx18_av_state *state = to_cx18_av_state(sd); @@ -1356,14 +1281,22 @@ static int cx18_av_s_register(struct v4l2_subdev *sd, } #endif +static const struct v4l2_ctrl_ops cx18_av_ctrl_ops = { + .s_ctrl = cx18_av_s_ctrl, +}; + static const struct v4l2_subdev_core_ops cx18_av_general_ops = { .g_chip_ident = cx18_av_g_chip_ident, .log_status = cx18_av_log_status, .load_fw = cx18_av_load_fw, .reset = cx18_av_reset, - .queryctrl = cx18_av_queryctrl, - .g_ctrl = cx18_av_g_ctrl, - .s_ctrl = cx18_av_s_ctrl, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, .s_std = cx18_av_s_std, #ifdef CONFIG_VIDEO_ADV_DEBUG .g_register = cx18_av_g_register, @@ -1427,8 +1360,42 @@ int cx18_av_probe(struct cx18 *cx) snprintf(sd->name, sizeof(sd->name), "%s %03x", cx->v4l2_dev.name, (state->rev >> 4)); sd->grp_id = CX18_HW_418_AV; + v4l2_ctrl_handler_init(&state->hdl, 9); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + + state->volume = v4l2_ctrl_new_std(&state->hdl, + &cx18_av_audio_ctrl_ops, V4L2_CID_AUDIO_VOLUME, + 0, 65535, 65535 / 100, 0); + v4l2_ctrl_new_std(&state->hdl, + &cx18_av_audio_ctrl_ops, V4L2_CID_AUDIO_MUTE, + 0, 1, 1, 0); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_audio_ctrl_ops, + V4L2_CID_AUDIO_BALANCE, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_audio_ctrl_ops, + V4L2_CID_AUDIO_BASS, + 0, 65535, 65535 / 100, 32768); + v4l2_ctrl_new_std(&state->hdl, &cx18_av_audio_ctrl_ops, + V4L2_CID_AUDIO_TREBLE, + 0, 65535, 65535 / 100, 32768); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + return err; + } err = v4l2_device_register_subdev(&cx->v4l2_dev, sd); - if (!err) + if (err) + v4l2_ctrl_handler_free(&state->hdl); + else cx18_av_init(cx); return err; } diff --git a/drivers/media/video/cx18/cx18-av-core.h b/drivers/media/video/cx18/cx18-av-core.h index 1956991795e3..188c9c3d2db1 100644 --- a/drivers/media/video/cx18/cx18-av-core.h +++ b/drivers/media/video/cx18/cx18-av-core.h @@ -26,6 +26,7 @@ #define _CX18_AV_CORE_H_ #include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> struct cx18; @@ -95,13 +96,14 @@ enum cx18_av_audio_input { struct cx18_av_state { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + struct v4l2_ctrl *volume; int radio; v4l2_std_id std; enum cx18_av_video_input vid_input; enum cx18_av_audio_input aud_input; u32 audclk_freq; int audmode; - int default_volume; u32 id; u32 rev; int is_initialized; @@ -347,6 +349,11 @@ static inline struct cx18_av_state *to_cx18_av_state(struct v4l2_subdev *sd) return container_of(sd, struct cx18_av_state, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct cx18_av_state, hdl)->sd; +} + /* ----------------------------------------------------------------------- */ /* cx18_av-core.c */ int cx18_av_write(struct cx18 *cx, u16 addr, u8 value); @@ -369,10 +376,9 @@ int cx18_av_loadfw(struct cx18 *cx); /* ----------------------------------------------------------------------- */ /* cx18_av-audio.c */ -int cx18_av_audio_g_ctrl(struct cx18 *cx, struct v4l2_control *ctrl); -int cx18_av_audio_s_ctrl(struct cx18 *cx, struct v4l2_control *ctrl); int cx18_av_s_clock_freq(struct v4l2_subdev *sd, u32 freq); void cx18_av_audio_set_path(struct cx18 *cx); +extern const struct v4l2_ctrl_ops cx18_av_audio_ctrl_ops; /* ----------------------------------------------------------------------- */ /* cx18_av-vbi.c */ diff --git a/drivers/media/video/cx18/cx18-controls.c b/drivers/media/video/cx18/cx18-controls.c index 97d7b7e100a3..282a3d29fdaa 100644 --- a/drivers/media/video/cx18/cx18-controls.c +++ b/drivers/media/video/cx18/cx18-controls.c @@ -30,152 +30,11 @@ #include "cx18-mailbox.h" #include "cx18-controls.h" -/* Must be sorted from low to high control ID! */ -static const u32 user_ctrls[] = { - V4L2_CID_USER_CLASS, - V4L2_CID_BRIGHTNESS, - V4L2_CID_CONTRAST, - V4L2_CID_SATURATION, - V4L2_CID_HUE, - V4L2_CID_AUDIO_VOLUME, - V4L2_CID_AUDIO_BALANCE, - V4L2_CID_AUDIO_BASS, - V4L2_CID_AUDIO_TREBLE, - V4L2_CID_AUDIO_MUTE, - V4L2_CID_AUDIO_LOUDNESS, - 0 -}; - -static const u32 *ctrl_classes[] = { - user_ctrls, - cx2341x_mpeg_ctrls, - NULL -}; - -int cx18_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *qctrl) -{ - struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; - const char *name; - - qctrl->id = v4l2_ctrl_next(ctrl_classes, qctrl->id); - if (qctrl->id == 0) - return -EINVAL; - - switch (qctrl->id) { - /* Standard V4L2 controls */ - case V4L2_CID_USER_CLASS: - return v4l2_ctrl_query_fill(qctrl, 0, 0, 0, 0); - case V4L2_CID_BRIGHTNESS: - case V4L2_CID_HUE: - case V4L2_CID_SATURATION: - case V4L2_CID_CONTRAST: - if (v4l2_subdev_call(cx->sd_av, core, queryctrl, qctrl)) - qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; - return 0; - - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_MUTE: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_AUDIO_LOUDNESS: - if (v4l2_subdev_call(cx->sd_av, core, queryctrl, qctrl)) - qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; - return 0; - - default: - if (cx2341x_ctrl_query(&cx->params, qctrl)) - qctrl->flags |= V4L2_CTRL_FLAG_DISABLED; - return 0; - } - strncpy(qctrl->name, name, sizeof(qctrl->name) - 1); - qctrl->name[sizeof(qctrl->name) - 1] = 0; - return 0; -} - -int cx18_querymenu(struct file *file, void *fh, struct v4l2_querymenu *qmenu) +static int cx18_s_stream_vbi_fmt(struct cx2341x_handler *cxhdl, u32 fmt) { - struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; - struct v4l2_queryctrl qctrl; - - qctrl.id = qmenu->id; - cx18_queryctrl(file, fh, &qctrl); - return v4l2_ctrl_query_menu(qmenu, &qctrl, - cx2341x_ctrl_get_menu(&cx->params, qmenu->id)); -} - -static int cx18_try_ctrl(struct file *file, void *fh, - struct v4l2_ext_control *vctrl) -{ - struct v4l2_queryctrl qctrl; - const char * const *menu_items = NULL; - int err; - - qctrl.id = vctrl->id; - err = cx18_queryctrl(file, fh, &qctrl); - if (err) - return err; - if (qctrl.type == V4L2_CTRL_TYPE_MENU) - menu_items = v4l2_ctrl_get_menu(qctrl.id); - return v4l2_ctrl_check(vctrl, &qctrl, menu_items); -} - -static int cx18_s_ctrl(struct cx18 *cx, struct v4l2_control *vctrl) -{ - switch (vctrl->id) { - /* Standard V4L2 controls */ - case V4L2_CID_BRIGHTNESS: - case V4L2_CID_HUE: - case V4L2_CID_SATURATION: - case V4L2_CID_CONTRAST: - return v4l2_subdev_call(cx->sd_av, core, s_ctrl, vctrl); - - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_MUTE: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_AUDIO_LOUDNESS: - return v4l2_subdev_call(cx->sd_av, core, s_ctrl, vctrl); - - default: - CX18_DEBUG_IOCTL("invalid control 0x%x\n", vctrl->id); - return -EINVAL; - } - return 0; -} - -static int cx18_g_ctrl(struct cx18 *cx, struct v4l2_control *vctrl) -{ - switch (vctrl->id) { - /* Standard V4L2 controls */ - case V4L2_CID_BRIGHTNESS: - case V4L2_CID_HUE: - case V4L2_CID_SATURATION: - case V4L2_CID_CONTRAST: - return v4l2_subdev_call(cx->sd_av, core, g_ctrl, vctrl); - - case V4L2_CID_AUDIO_VOLUME: - case V4L2_CID_AUDIO_MUTE: - case V4L2_CID_AUDIO_BALANCE: - case V4L2_CID_AUDIO_BASS: - case V4L2_CID_AUDIO_TREBLE: - case V4L2_CID_AUDIO_LOUDNESS: - return v4l2_subdev_call(cx->sd_av, core, g_ctrl, vctrl); + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); + int type = cxhdl->stream_type->val; - default: - CX18_DEBUG_IOCTL("invalid control 0x%x\n", vctrl->id); - return -EINVAL; - } - return 0; -} - -static int cx18_setup_vbi_fmt(struct cx18 *cx, - enum v4l2_mpeg_stream_vbi_fmt fmt, - enum v4l2_mpeg_stream_type type) -{ - if (!(cx->v4l2_cap & V4L2_CAP_SLICED_VBI_CAPTURE)) - return -EINVAL; if (atomic_read(&cx->ana_capturing) > 0) return -EBUSY; @@ -230,121 +89,43 @@ static int cx18_setup_vbi_fmt(struct cx18 *cx, return 0; } -int cx18_g_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) +static int cx18_s_video_encoding(struct cx2341x_handler *cxhdl, u32 val) { - struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; - struct v4l2_control ctrl; - - if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { - int i; - int err = 0; - - for (i = 0; i < c->count; i++) { - ctrl.id = c->controls[i].id; - ctrl.value = c->controls[i].value; - err = cx18_g_ctrl(cx, &ctrl); - c->controls[i].value = ctrl.value; - if (err) { - c->error_idx = i; - break; - } - } - return err; - } - if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) - return cx2341x_ext_ctrls(&cx->params, 0, c, VIDIOC_G_EXT_CTRLS); - return -EINVAL; + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); + int is_mpeg1 = val == V4L2_MPEG_VIDEO_ENCODING_MPEG_1; + struct v4l2_mbus_framefmt fmt; + + /* fix videodecoder resolution */ + fmt.width = cxhdl->width / (is_mpeg1 ? 2 : 1); + fmt.height = cxhdl->height; + fmt.code = V4L2_MBUS_FMT_FIXED; + v4l2_subdev_call(cx->sd_av, video, s_mbus_fmt, &fmt); + return 0; } -int cx18_s_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) +static int cx18_s_audio_sampling_freq(struct cx2341x_handler *cxhdl, u32 idx) { - struct cx18_open_id *id = fh; - struct cx18 *cx = id->cx; - int ret; - struct v4l2_control ctrl; - - ret = v4l2_prio_check(&cx->prio, id->prio); - if (ret) - return ret; - - if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { - int i; - int err = 0; - - for (i = 0; i < c->count; i++) { - ctrl.id = c->controls[i].id; - ctrl.value = c->controls[i].value; - err = cx18_s_ctrl(cx, &ctrl); - c->controls[i].value = ctrl.value; - if (err) { - c->error_idx = i; - break; - } - } - return err; - } - if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) { - static u32 freqs[3] = { 44100, 48000, 32000 }; - struct cx18_api_func_private priv; - struct cx2341x_mpeg_params p = cx->params; - int err = cx2341x_ext_ctrls(&p, atomic_read(&cx->ana_capturing), - c, VIDIOC_S_EXT_CTRLS); - unsigned int idx; - - if (err) - return err; + static const u32 freqs[3] = { 44100, 48000, 32000 }; + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); - if (p.video_encoding != cx->params.video_encoding) { - int is_mpeg1 = p.video_encoding == - V4L2_MPEG_VIDEO_ENCODING_MPEG_1; - struct v4l2_mbus_framefmt fmt; - - /* fix videodecoder resolution */ - fmt.width = cx->params.width / (is_mpeg1 ? 2 : 1); - fmt.height = cx->params.height; - fmt.code = V4L2_MBUS_FMT_FIXED; - v4l2_subdev_call(cx->sd_av, video, s_mbus_fmt, &fmt); - } - priv.cx = cx; - priv.s = &cx->streams[id->type]; - err = cx2341x_update(&priv, cx18_api_func, &cx->params, &p); - if (!err && - (cx->params.stream_vbi_fmt != p.stream_vbi_fmt || - cx->params.stream_type != p.stream_type)) - err = cx18_setup_vbi_fmt(cx, p.stream_vbi_fmt, - p.stream_type); - cx->params = p; - cx->dualwatch_stereo_mode = p.audio_properties & 0x0300; - idx = p.audio_properties & 0x03; - /* The audio clock of the digitizer must match the codec sample - rate otherwise you get some very strange effects. */ - if (idx < ARRAY_SIZE(freqs)) - cx18_call_all(cx, audio, s_clock_freq, freqs[idx]); - return err; - } - return -EINVAL; + /* The audio clock of the digitizer must match the codec sample + rate otherwise you get some very strange effects. */ + if (idx < ARRAY_SIZE(freqs)) + cx18_call_all(cx, audio, s_clock_freq, freqs[idx]); + return 0; } -int cx18_try_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *c) +static int cx18_s_audio_mode(struct cx2341x_handler *cxhdl, u32 val) { - struct cx18 *cx = ((struct cx18_open_id *)fh)->cx; + struct cx18 *cx = container_of(cxhdl, struct cx18, cxhdl); - if (c->ctrl_class == V4L2_CTRL_CLASS_USER) { - int i; - int err = 0; - - for (i = 0; i < c->count; i++) { - err = cx18_try_ctrl(file, fh, &c->controls[i]); - if (err) { - c->error_idx = i; - break; - } - } - return err; - } - if (c->ctrl_class == V4L2_CTRL_CLASS_MPEG) - return cx2341x_ext_ctrls(&cx->params, - atomic_read(&cx->ana_capturing), - c, VIDIOC_TRY_EXT_CTRLS); - return -EINVAL; + cx->dualwatch_stereo_mode = val; + return 0; } + +struct cx2341x_handler_ops cx18_cxhdl_ops = { + .s_audio_mode = cx18_s_audio_mode, + .s_audio_sampling_freq = cx18_s_audio_sampling_freq, + .s_video_encoding = cx18_s_video_encoding, + .s_stream_vbi_fmt = cx18_s_stream_vbi_fmt, +}; diff --git a/drivers/media/video/cx18/cx18-controls.h b/drivers/media/video/cx18/cx18-controls.h index e46323700b81..cb5dfc7b2054 100644 --- a/drivers/media/video/cx18/cx18-controls.h +++ b/drivers/media/video/cx18/cx18-controls.h @@ -21,9 +21,4 @@ * 02111-1307 USA */ -int cx18_queryctrl(struct file *file, void *fh, struct v4l2_queryctrl *a); -int cx18_g_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *a); -int cx18_s_ext_ctrls(struct file *file, void *fh, struct v4l2_ext_controls *a); -int cx18_try_ext_ctrls(struct file *file, void *fh, - struct v4l2_ext_controls *a); -int cx18_querymenu(struct file *file, void *fh, struct v4l2_querymenu *a); +extern struct cx2341x_handler_ops cx18_cxhdl_ops; diff --git a/drivers/media/video/cx18/cx18-driver.c b/drivers/media/video/cx18/cx18-driver.c index 944af8adbe0c..869690bcb767 100644 --- a/drivers/media/video/cx18/cx18-driver.c +++ b/drivers/media/video/cx18/cx18-driver.c @@ -36,6 +36,7 @@ #include "cx18-scb.h" #include "cx18-mailbox.h" #include "cx18-ioctl.h" +#include "cx18-controls.h" #include "tuner-xc2028.h" #include <media/tveeprom.h> @@ -708,15 +709,21 @@ static int __devinit cx18_init_struct1(struct cx18 *cx) cx->open_id = 1; /* Initial settings */ - cx2341x_fill_defaults(&cx->params); - cx->temporal_strength = cx->params.video_temporal_filter; - cx->spatial_strength = cx->params.video_spatial_filter; - cx->filter_mode = cx->params.video_spatial_filter_mode | - (cx->params.video_temporal_filter_mode << 1) | - (cx->params.video_median_filter_type << 2); - cx->params.port = CX2341X_PORT_MEMORY; - cx->params.capabilities = - CX2341X_CAP_HAS_TS | CX2341X_CAP_HAS_SLICED_VBI; + cx->cxhdl.port = CX2341X_PORT_MEMORY; + cx->cxhdl.capabilities = CX2341X_CAP_HAS_TS | CX2341X_CAP_HAS_SLICED_VBI; + cx->cxhdl.ops = &cx18_cxhdl_ops; + cx->cxhdl.func = cx18_api_func; + ret = cx2341x_handler_init(&cx->cxhdl, 50); + if (ret) + return ret; + cx->v4l2_dev.ctrl_handler = &cx->cxhdl.hdl; + + cx->temporal_strength = cx->cxhdl.video_temporal_filter->cur.val; + cx->spatial_strength = cx->cxhdl.video_spatial_filter->cur.val; + cx->filter_mode = cx->cxhdl.video_spatial_filter_mode->cur.val | + (cx->cxhdl.video_temporal_filter_mode->cur.val << 1) | + (cx->cxhdl.video_median_filter_type->cur.val << 2); + init_waitqueue_head(&cx->cap_w); init_waitqueue_head(&cx->mb_apu_waitq); init_waitqueue_head(&cx->mb_cpu_waitq); @@ -1028,7 +1035,7 @@ static int __devinit cx18_probe(struct pci_dev *pci_dev, else cx->is_50hz = 1; - cx->params.video_gop_size = cx->is_60hz ? 15 : 12; + cx2341x_handler_set_50hz(&cx->cxhdl, !cx->is_60hz); if (cx->options.radio > 0) cx->v4l2_cap |= V4L2_CAP_RADIO; @@ -1074,7 +1081,6 @@ static int __devinit cx18_probe(struct pci_dev *pci_dev, /* Load cx18 submodules (cx18-alsa) */ request_modules(cx); - return 0; free_streams: @@ -1257,6 +1263,8 @@ static void cx18_remove(struct pci_dev *pci_dev) for (i = 0; i < CX18_VBI_FRAMES; i++) kfree(cx->vbi.sliced_mpeg_data[i]); + v4l2_ctrl_handler_free(&cx->av_state.hdl); + CX18_INFO("Removed %s\n", cx->card_name); v4l2_device_unregister(v4l2_dev); diff --git a/drivers/media/video/cx18/cx18-driver.h b/drivers/media/video/cx18/cx18-driver.h index 306caac6d3fc..26eb910f6daf 100644 --- a/drivers/media/video/cx18/cx18-driver.h +++ b/drivers/media/video/cx18/cx18-driver.h @@ -564,7 +564,7 @@ struct cx18 { struct cx18_av_state av_state; /* codec settings */ - struct cx2341x_mpeg_params params; + struct cx2341x_handler cxhdl; u32 filter_mode; u32 temporal_strength; u32 spatial_strength; diff --git a/drivers/media/video/cx18/cx18-fileops.c b/drivers/media/video/cx18/cx18-fileops.c index 9f23b90732f2..98ef33e4326a 100644 --- a/drivers/media/video/cx18/cx18-fileops.c +++ b/drivers/media/video/cx18/cx18-fileops.c @@ -160,13 +160,10 @@ EXPORT_SYMBOL(cx18_release_stream); static void cx18_dualwatch(struct cx18 *cx) { struct v4l2_tuner vt; - u32 new_bitmap; u32 new_stereo_mode; - const u32 stereo_mask = 0x0300; const u32 dual = 0x0200; - u32 h; - new_stereo_mode = cx->params.audio_properties & stereo_mask; + new_stereo_mode = v4l2_ctrl_g_ctrl(cx->cxhdl.audio_mode); memset(&vt, 0, sizeof(vt)); cx18_call_all(cx, tuner, g_tuner, &vt); if (vt.audmode == V4L2_TUNER_MODE_LANG1_LANG2 && @@ -176,25 +173,10 @@ static void cx18_dualwatch(struct cx18 *cx) if (new_stereo_mode == cx->dualwatch_stereo_mode) return; - new_bitmap = new_stereo_mode - | (cx->params.audio_properties & ~stereo_mask); - - CX18_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x. " - "new audio_bitmask=0x%ux\n", - cx->dualwatch_stereo_mode, new_stereo_mode, new_bitmap); - - h = cx18_find_handle(cx); - if (h == CX18_INVALID_TASK_HANDLE) { - CX18_DEBUG_INFO("dualwatch: can't find valid task handle\n"); - return; - } - - if (cx18_vapi(cx, - CX18_CPU_SET_AUDIO_PARAMETERS, 2, h, new_bitmap) == 0) { - cx->dualwatch_stereo_mode = new_stereo_mode; - return; - } - CX18_DEBUG_INFO("dualwatch: changing stereo flag failed\n"); + CX18_DEBUG_INFO("dualwatch: change stereo flag from 0x%x to 0x%x.\n", + cx->dualwatch_stereo_mode, new_stereo_mode); + if (v4l2_ctrl_s_ctrl(cx->cxhdl.audio_mode, new_stereo_mode)) + CX18_DEBUG_INFO("dualwatch: changing stereo flag failed\n"); } @@ -724,8 +706,8 @@ int cx18_v4l2_close(struct file *filp) if (atomic_read(&cx->ana_capturing) > 0) { /* Undo video mute */ cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle, - cx->params.video_mute | - (cx->params.video_mute_yuv << 8)); + (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute) | + (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute_yuv) << 8))); } /* Done! Unmute and continue. */ cx18_unmute(cx); diff --git a/drivers/media/video/cx18/cx18-ioctl.c b/drivers/media/video/cx18/cx18-ioctl.c index 7150195740dc..36b018c943e5 100644 --- a/drivers/media/video/cx18/cx18-ioctl.c +++ b/drivers/media/video/cx18/cx18-ioctl.c @@ -152,8 +152,8 @@ static int cx18_g_fmt_vid_cap(struct file *file, void *fh, struct cx18 *cx = id->cx; struct v4l2_pix_format *pixfmt = &fmt->fmt.pix; - pixfmt->width = cx->params.width; - pixfmt->height = cx->params.height; + pixfmt->width = cx->cxhdl.width; + pixfmt->height = cx->cxhdl.height; pixfmt->colorspace = V4L2_COLORSPACE_SMPTE170M; pixfmt->field = V4L2_FIELD_INTERLACED; pixfmt->priv = 0; @@ -287,14 +287,14 @@ static int cx18_s_fmt_vid_cap(struct file *file, void *fh, w = fmt->fmt.pix.width; h = fmt->fmt.pix.height; - if (cx->params.width == w && cx->params.height == h) + if (cx->cxhdl.width == w && cx->cxhdl.height == h) return 0; if (atomic_read(&cx->ana_capturing) > 0) return -EBUSY; - mbus_fmt.width = cx->params.width = w; - mbus_fmt.height = cx->params.height = h; + mbus_fmt.width = cx->cxhdl.width = w; + mbus_fmt.height = cx->cxhdl.height = h; mbus_fmt.code = V4L2_MBUS_FMT_FIXED; v4l2_subdev_call(cx->sd_av, video, s_mbus_fmt, &mbus_fmt); return cx18_g_fmt_vid_cap(file, fh, fmt); @@ -696,9 +696,10 @@ int cx18_s_std(struct file *file, void *fh, v4l2_std_id *std) cx->std = *std; cx->is_60hz = (*std & V4L2_STD_525_60) ? 1 : 0; - cx->params.is_50hz = cx->is_50hz = !cx->is_60hz; - cx->params.width = 720; - cx->params.height = cx->is_50hz ? 576 : 480; + cx->is_50hz = !cx->is_60hz; + cx2341x_handler_set_50hz(&cx->cxhdl, cx->is_50hz); + cx->cxhdl.width = 720; + cx->cxhdl.height = cx->is_50hz ? 576 : 480; cx->vbi.count = cx->is_50hz ? 18 : 12; cx->vbi.start[0] = cx->is_50hz ? 6 : 10; cx->vbi.start[1] = cx->is_50hz ? 318 : 273; @@ -1035,7 +1036,7 @@ static int cx18_log_status(struct file *file, void *fh) mutex_unlock(&cx->gpio_lock); CX18_INFO("Tuner: %s\n", test_bit(CX18_F_I_RADIO_USER, &cx->i_flags) ? "Radio" : "TV"); - cx2341x_log_status(&cx->params, cx->v4l2_dev.name); + v4l2_ctrl_handler_log_status(&cx->cxhdl.hdl, cx->v4l2_dev.name); CX18_INFO("Status flags: 0x%08lx\n", cx->i_flags); for (i = 0; i < CX18_MAX_STREAMS; i++) { struct cx18_stream *s = &cx->streams[i]; @@ -1136,11 +1137,6 @@ static const struct v4l2_ioctl_ops cx18_ioctl_ops = { .vidioc_s_register = cx18_s_register, #endif .vidioc_default = cx18_default, - .vidioc_queryctrl = cx18_queryctrl, - .vidioc_querymenu = cx18_querymenu, - .vidioc_g_ext_ctrls = cx18_g_ext_ctrls, - .vidioc_s_ext_ctrls = cx18_s_ext_ctrls, - .vidioc_try_ext_ctrls = cx18_try_ext_ctrls, }; void cx18_set_funcs(struct video_device *vdev) diff --git a/drivers/media/video/cx18/cx18-mailbox.c b/drivers/media/video/cx18/cx18-mailbox.c index c545f3beef78..9605d54bd083 100644 --- a/drivers/media/video/cx18/cx18-mailbox.c +++ b/drivers/media/video/cx18/cx18-mailbox.c @@ -716,9 +716,8 @@ static int cx18_set_filter_param(struct cx18_stream *s) int cx18_api_func(void *priv, u32 cmd, int in, int out, u32 data[CX2341X_MBOX_MAX_DATA]) { - struct cx18_api_func_private *api_priv = priv; - struct cx18 *cx = api_priv->cx; - struct cx18_stream *s = api_priv->s; + struct cx18_stream *s = priv; + struct cx18 *cx = s->cx; switch (cmd) { case CX2341X_ENC_SET_OUTPUT_PORT: diff --git a/drivers/media/video/cx18/cx18-mailbox.h b/drivers/media/video/cx18/cx18-mailbox.h index 077952fcbcca..05fe6bdbe062 100644 --- a/drivers/media/video/cx18/cx18-mailbox.h +++ b/drivers/media/video/cx18/cx18-mailbox.h @@ -81,11 +81,6 @@ struct cx18_mailbox { struct cx18_stream; -struct cx18_api_func_private { - struct cx18 *cx; - struct cx18_stream *s; -}; - int cx18_api(struct cx18 *cx, u32 cmd, int args, u32 data[]); int cx18_vapi_result(struct cx18 *cx, u32 data[MAX_MB_ARGUMENTS], u32 cmd, int args, ...); diff --git a/drivers/media/video/cx18/cx18-streams.c b/drivers/media/video/cx18/cx18-streams.c index 94f5d7967c5c..2d248560770e 100644 --- a/drivers/media/video/cx18/cx18-streams.c +++ b/drivers/media/video/cx18/cx18-streams.c @@ -572,7 +572,7 @@ static void cx18_stream_configure_mdls(struct cx18_stream *s) * Set the MDL size to the exact size needed for one frame. * Use enough buffers per MDL to cover the MDL size */ - s->mdl_size = 720 * s->cx->params.height * 3 / 2; + s->mdl_size = 720 * s->cx->cxhdl.height * 3 / 2; s->bufs_per_mdl = s->mdl_size / s->buf_size; if (s->mdl_size % s->buf_size) s->bufs_per_mdl++; @@ -607,7 +607,6 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s) u32 data[MAX_MB_ARGUMENTS]; struct cx18 *cx = s->cx; int captype = 0; - struct cx18_api_func_private priv; struct cx18_stream *s_idx; if (!cx18_stream_enabled(s)) @@ -620,7 +619,7 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s) captype = CAPTURE_CHANNEL_TYPE_MPEG; cx->mpg_data_received = cx->vbi_data_inserted = 0; cx->dualwatch_jiffies = jiffies; - cx->dualwatch_stereo_mode = cx->params.audio_properties & 0x300; + cx->dualwatch_stereo_mode = v4l2_ctrl_g_ctrl(cx->cxhdl.audio_mode); cx->search_pack_header = 0; break; @@ -710,21 +709,21 @@ int cx18_start_v4l2_encode_stream(struct cx18_stream *s) s->handle, cx18_stream_enabled(s_idx) ? 7 : 0); /* Call out to the common CX2341x API setup for user controls */ - priv.cx = cx; - priv.s = s; - cx2341x_update(&priv, cx18_api_func, NULL, &cx->params); + cx->cxhdl.priv = s; + cx2341x_handler_setup(&cx->cxhdl); /* * When starting a capture and we're set for radio, * ensure the video is muted, despite the user control. */ - if (!cx->params.video_mute && + if (!cx->cxhdl.video_mute && test_bit(CX18_F_I_RADIO_USER, &cx->i_flags)) cx18_vapi(cx, CX18_CPU_SET_VIDEO_MUTE, 2, s->handle, - (cx->params.video_mute_yuv << 8) | 1); + (v4l2_ctrl_g_ctrl(cx->cxhdl.video_mute_yuv) << 8) | 1); } if (atomic_read(&cx->tot_capturing) == 0) { + cx2341x_handler_set_busy(&cx->cxhdl, 1); clear_bit(CX18_F_I_EOS, &cx->i_flags); cx18_write_reg(cx, 7, CX18_DSP0_INTERRUPT_MASK); } @@ -826,6 +825,7 @@ int cx18_stop_v4l2_encode_stream(struct cx18_stream *s, int gop_end) if (atomic_read(&cx->tot_capturing) > 0) return 0; + cx2341x_handler_set_busy(&cx->cxhdl, 0); cx18_write_reg(cx, 5, CX18_DSP0_INTERRUPT_MASK); wake_up(&s->waitq); diff --git a/drivers/media/video/mem2mem_testdev.c b/drivers/media/video/mem2mem_testdev.c index c179041d91f8..6ab2d4f26342 100644 --- a/drivers/media/video/mem2mem_testdev.c +++ b/drivers/media/video/mem2mem_testdev.c @@ -28,7 +28,7 @@ #include <media/v4l2-mem2mem.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> -#include <media/videobuf-vmalloc.h> +#include <media/videobuf2-vmalloc.h> #define MEM2MEM_TEST_MODULE_NAME "mem2mem-testdev" @@ -201,11 +201,6 @@ struct m2mtest_ctx { struct v4l2_m2m_ctx *m2m_ctx; }; -struct m2mtest_buffer { - /* vb must be first! */ - struct videobuf_buffer vb; -}; - static struct v4l2_queryctrl *get_ctrl(int id) { int i; @@ -219,37 +214,41 @@ static struct v4l2_queryctrl *get_ctrl(int id) } static int device_process(struct m2mtest_ctx *ctx, - struct m2mtest_buffer *in_buf, - struct m2mtest_buffer *out_buf) + struct vb2_buffer *in_vb, + struct vb2_buffer *out_vb) { struct m2mtest_dev *dev = ctx->dev; + struct m2mtest_q_data *q_data; u8 *p_in, *p_out; int x, y, t, w; int tile_w, bytes_left; - struct videobuf_queue *src_q; - struct videobuf_queue *dst_q; + int width, height, bytesperline; - src_q = v4l2_m2m_get_src_vq(ctx->m2m_ctx); - dst_q = v4l2_m2m_get_dst_vq(ctx->m2m_ctx); - p_in = videobuf_queue_to_vaddr(src_q, &in_buf->vb); - p_out = videobuf_queue_to_vaddr(dst_q, &out_buf->vb); + q_data = get_q_data(V4L2_BUF_TYPE_VIDEO_OUTPUT); + + width = q_data->width; + height = q_data->height; + bytesperline = (q_data->width * q_data->fmt->depth) >> 3; + + p_in = vb2_plane_vaddr(in_vb, 0); + p_out = vb2_plane_vaddr(out_vb, 0); if (!p_in || !p_out) { v4l2_err(&dev->v4l2_dev, "Acquiring kernel pointers to buffers failed\n"); return -EFAULT; } - if (in_buf->vb.size > out_buf->vb.size) { + if (vb2_plane_size(in_vb, 0) > vb2_plane_size(out_vb, 0)) { v4l2_err(&dev->v4l2_dev, "Output buffer is too small\n"); return -EINVAL; } - tile_w = (in_buf->vb.width * (q_data[V4L2_M2M_DST].fmt->depth >> 3)) + tile_w = (width * (q_data[V4L2_M2M_DST].fmt->depth >> 3)) / MEM2MEM_NUM_TILES; - bytes_left = in_buf->vb.bytesperline - tile_w * MEM2MEM_NUM_TILES; + bytes_left = bytesperline - tile_w * MEM2MEM_NUM_TILES; w = 0; - for (y = 0; y < in_buf->vb.height; ++y) { + for (y = 0; y < height; ++y) { for (t = 0; t < MEM2MEM_NUM_TILES; ++t) { if (w & 0x1) { for (x = 0; x < tile_w; ++x) @@ -301,6 +300,21 @@ static void job_abort(void *priv) ctx->aborting = 1; } +static void m2mtest_lock(void *priv) +{ + struct m2mtest_ctx *ctx = priv; + struct m2mtest_dev *dev = ctx->dev; + mutex_lock(&dev->dev_mutex); +} + +static void m2mtest_unlock(void *priv) +{ + struct m2mtest_ctx *ctx = priv; + struct m2mtest_dev *dev = ctx->dev; + mutex_unlock(&dev->dev_mutex); +} + + /* device_run() - prepares and starts the device * * This simulates all the immediate preparations required before starting @@ -311,7 +325,7 @@ static void device_run(void *priv) { struct m2mtest_ctx *ctx = priv; struct m2mtest_dev *dev = ctx->dev; - struct m2mtest_buffer *src_buf, *dst_buf; + struct vb2_buffer *src_buf, *dst_buf; src_buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); dst_buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); @@ -322,12 +336,11 @@ static void device_run(void *priv) schedule_irq(dev, ctx->transtime); } - static void device_isr(unsigned long priv) { struct m2mtest_dev *m2mtest_dev = (struct m2mtest_dev *)priv; struct m2mtest_ctx *curr_ctx; - struct m2mtest_buffer *src_buf, *dst_buf; + struct vb2_buffer *src_vb, *dst_vb; unsigned long flags; curr_ctx = v4l2_m2m_get_curr_priv(m2mtest_dev->m2m_dev); @@ -338,31 +351,26 @@ static void device_isr(unsigned long priv) return; } - src_buf = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); - dst_buf = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + src_vb = v4l2_m2m_src_buf_remove(curr_ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(curr_ctx->m2m_ctx); + curr_ctx->num_processed++; + spin_lock_irqsave(&m2mtest_dev->irqlock, flags); + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); + spin_unlock_irqrestore(&m2mtest_dev->irqlock, flags); + if (curr_ctx->num_processed == curr_ctx->translen || curr_ctx->aborting) { dprintk(curr_ctx->dev, "Finishing transaction\n"); curr_ctx->num_processed = 0; - spin_lock_irqsave(&m2mtest_dev->irqlock, flags); - src_buf->vb.state = dst_buf->vb.state = VIDEOBUF_DONE; - wake_up(&src_buf->vb.done); - wake_up(&dst_buf->vb.done); - spin_unlock_irqrestore(&m2mtest_dev->irqlock, flags); v4l2_m2m_job_finish(m2mtest_dev->m2m_dev, curr_ctx->m2m_ctx); } else { - spin_lock_irqsave(&m2mtest_dev->irqlock, flags); - src_buf->vb.state = dst_buf->vb.state = VIDEOBUF_DONE; - wake_up(&src_buf->vb.done); - wake_up(&dst_buf->vb.done); - spin_unlock_irqrestore(&m2mtest_dev->irqlock, flags); device_run(curr_ctx); } } - /* * video ioctls */ @@ -423,7 +431,7 @@ static int vidioc_enum_fmt_vid_out(struct file *file, void *priv, static int vidioc_g_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) { - struct videobuf_queue *vq; + struct vb2_queue *vq; struct m2mtest_q_data *q_data; vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); @@ -434,7 +442,7 @@ static int vidioc_g_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) f->fmt.pix.width = q_data->width; f->fmt.pix.height = q_data->height; - f->fmt.pix.field = vq->field; + f->fmt.pix.field = V4L2_FIELD_NONE; f->fmt.pix.pixelformat = q_data->fmt->fourcc; f->fmt.pix.bytesperline = (q_data->width * q_data->fmt->depth) >> 3; f->fmt.pix.sizeimage = q_data->sizeimage; @@ -523,7 +531,7 @@ static int vidioc_try_fmt_vid_out(struct file *file, void *priv, static int vidioc_s_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) { struct m2mtest_q_data *q_data; - struct videobuf_queue *vq; + struct vb2_queue *vq; vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); if (!vq) @@ -533,7 +541,7 @@ static int vidioc_s_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) if (!q_data) return -EINVAL; - if (videobuf_queue_is_busy(vq)) { + if (vb2_is_busy(vq)) { v4l2_err(&ctx->dev->v4l2_dev, "%s queue busy\n", __func__); return -EBUSY; } @@ -543,7 +551,6 @@ static int vidioc_s_fmt(struct m2mtest_ctx *ctx, struct v4l2_format *f) q_data->height = f->fmt.pix.height; q_data->sizeimage = q_data->width * q_data->height * q_data->fmt->depth >> 3; - vq->field = f->fmt.pix.field; dprintk(ctx->dev, "Setting format for type %d, wxh: %dx%d, fmt: %d\n", @@ -733,120 +740,94 @@ static const struct v4l2_ioctl_ops m2mtest_ioctl_ops = { * Queue operations */ -static void m2mtest_buf_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) -{ - struct m2mtest_ctx *ctx = vq->priv_data; - - dprintk(ctx->dev, "type: %d, index: %d, state: %d\n", - vq->type, vb->i, vb->state); - - videobuf_vmalloc_free(vb); - vb->state = VIDEOBUF_NEEDS_INIT; -} - -static int m2mtest_buf_setup(struct videobuf_queue *vq, unsigned int *count, - unsigned int *size) +static int m2mtest_queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned long sizes[], + void *alloc_ctxs[]) { - struct m2mtest_ctx *ctx = vq->priv_data; + struct m2mtest_ctx *ctx = vb2_get_drv_priv(vq); struct m2mtest_q_data *q_data; + unsigned int size, count = *nbuffers; q_data = get_q_data(vq->type); - *size = q_data->width * q_data->height * q_data->fmt->depth >> 3; - dprintk(ctx->dev, "size:%d, w/h %d/%d, depth: %d\n", - *size, q_data->width, q_data->height, q_data->fmt->depth); + size = q_data->width * q_data->height * q_data->fmt->depth >> 3; - if (0 == *count) - *count = MEM2MEM_DEF_NUM_BUFS; + while (size * count > MEM2MEM_VID_MEM_LIMIT) + (count)--; - while (*size * *count > MEM2MEM_VID_MEM_LIMIT) - (*count)--; + *nplanes = 1; + *nbuffers = count; + sizes[0] = size; - v4l2_info(&ctx->dev->v4l2_dev, - "%d buffers of size %d set up.\n", *count, *size); + /* + * videobuf2-vmalloc allocator is context-less so no need to set + * alloc_ctxs array. + */ + + dprintk(ctx->dev, "get %d buffer(s) of size %d each.\n", count, size); return 0; } -static int m2mtest_buf_prepare(struct videobuf_queue *vq, - struct videobuf_buffer *vb, - enum v4l2_field field) +static int m2mtest_buf_prepare(struct vb2_buffer *vb) { - struct m2mtest_ctx *ctx = vq->priv_data; + struct m2mtest_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); struct m2mtest_q_data *q_data; - int ret; - dprintk(ctx->dev, "type: %d, index: %d, state: %d\n", - vq->type, vb->i, vb->state); + dprintk(ctx->dev, "type: %d\n", vb->vb2_queue->type); - q_data = get_q_data(vq->type); + q_data = get_q_data(vb->vb2_queue->type); - if (vb->baddr) { - /* User-provided buffer */ - if (vb->bsize < q_data->sizeimage) { - /* Buffer too small to fit a frame */ - v4l2_err(&ctx->dev->v4l2_dev, - "User-provided buffer too small\n"); - return -EINVAL; - } - } else if (vb->state != VIDEOBUF_NEEDS_INIT - && vb->bsize < q_data->sizeimage) { - /* We provide the buffer, but it's already been initialized - * and is too small */ + if (vb2_plane_size(vb, 0) < q_data->sizeimage) { + dprintk(ctx->dev, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), (long)q_data->sizeimage); return -EINVAL; } - vb->width = q_data->width; - vb->height = q_data->height; - vb->bytesperline = (q_data->width * q_data->fmt->depth) >> 3; - vb->size = q_data->sizeimage; - vb->field = field; - - if (VIDEOBUF_NEEDS_INIT == vb->state) { - ret = videobuf_iolock(vq, vb, NULL); - if (ret) { - v4l2_err(&ctx->dev->v4l2_dev, - "Iolock failed\n"); - goto fail; - } - } - - vb->state = VIDEOBUF_PREPARED; + vb2_set_plane_payload(vb, 0, q_data->sizeimage); return 0; -fail: - m2mtest_buf_release(vq, vb); - return ret; } -static void m2mtest_buf_queue(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void m2mtest_buf_queue(struct vb2_buffer *vb) { - struct m2mtest_ctx *ctx = vq->priv_data; - - v4l2_m2m_buf_queue(ctx->m2m_ctx, vq, vb); + struct m2mtest_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); } -static struct videobuf_queue_ops m2mtest_qops = { - .buf_setup = m2mtest_buf_setup, - .buf_prepare = m2mtest_buf_prepare, - .buf_queue = m2mtest_buf_queue, - .buf_release = m2mtest_buf_release, +static struct vb2_ops m2mtest_qops = { + .queue_setup = m2mtest_queue_setup, + .buf_prepare = m2mtest_buf_prepare, + .buf_queue = m2mtest_buf_queue, }; -static void queue_init(void *priv, struct videobuf_queue *vq, - enum v4l2_buf_type type) +static int queue_init(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq) { struct m2mtest_ctx *ctx = priv; - struct m2mtest_dev *dev = ctx->dev; + int ret; - videobuf_queue_vmalloc_init(vq, &m2mtest_qops, dev->v4l2_dev.dev, - &dev->irqlock, type, V4L2_FIELD_NONE, - sizeof(struct m2mtest_buffer), priv, - &dev->dev_mutex); -} + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + src_vq->io_modes = VB2_MMAP; + src_vq->drv_priv = ctx; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + src_vq->ops = &m2mtest_qops; + src_vq->mem_ops = &vb2_vmalloc_memops; + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dst_vq->io_modes = VB2_MMAP; + dst_vq->drv_priv = ctx; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + dst_vq->ops = &m2mtest_qops; + dst_vq->mem_ops = &vb2_vmalloc_memops; + + return vb2_queue_init(dst_vq); +} /* * File operations @@ -866,7 +847,8 @@ static int m2mtest_open(struct file *file) ctx->transtime = MEM2MEM_DEF_TRANSTIME; ctx->num_processed = 0; - ctx->m2m_ctx = v4l2_m2m_ctx_init(ctx, dev->m2m_dev, queue_init); + ctx->m2m_ctx = v4l2_m2m_ctx_init(dev->m2m_dev, ctx, &queue_init); + if (IS_ERR(ctx->m2m_ctx)) { int ret = PTR_ERR(ctx->m2m_ctx); @@ -932,6 +914,8 @@ static struct v4l2_m2m_ops m2m_ops = { .device_run = device_run, .job_ready = job_ready, .job_abort = job_abort, + .lock = m2mtest_lock, + .unlock = m2mtest_unlock, }; static int m2mtest_probe(struct platform_device *pdev) @@ -990,6 +974,7 @@ static int m2mtest_probe(struct platform_device *pdev) return 0; + v4l2_m2m_release(dev->m2m_dev); err_m2m: video_unregister_device(dev->vfd); rel_vdev: diff --git a/drivers/media/video/noon010pc30.c b/drivers/media/video/noon010pc30.c new file mode 100644 index 000000000000..35f722a88f76 --- /dev/null +++ b/drivers/media/video/noon010pc30.c @@ -0,0 +1,792 @@ +/* + * Driver for SiliconFile NOON010PC30 CIF (1/11") Image Sensor with ISP + * + * Copyright (C) 2010 Samsung Electronics + * Contact: Sylwester Nawrocki, <s.nawrocki@samsung.com> + * + * Initial register configuration based on a driver authored by + * HeungJun Kim <riverful.kim@samsung.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, or + * (at your option) any later vergsion. + */ + +#include <linux/delay.h> +#include <linux/gpio.h> +#include <linux/i2c.h> +#include <linux/slab.h> +#include <linux/regulator/consumer.h> +#include <media/noon010pc30.h> +#include <media/v4l2-chip-ident.h> +#include <linux/videodev2.h> +#include <media/v4l2-ctrls.h> +#include <media/v4l2-device.h> +#include <media/v4l2-mediabus.h> +#include <media/v4l2-subdev.h> + +static int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "Enable module debug trace. Set to 1 to enable."); + +#define MODULE_NAME "NOON010PC30" + +/* + * Register offsets within a page + * b15..b8 - page id, b7..b0 - register address + */ +#define POWER_CTRL_REG 0x0001 +#define PAGEMODE_REG 0x03 +#define DEVICE_ID_REG 0x0004 +#define NOON010PC30_ID 0x86 +#define VDO_CTL_REG(n) (0x0010 + (n)) +#define SYNC_CTL_REG 0x0012 +/* Window size and position */ +#define WIN_ROWH_REG 0x0013 +#define WIN_ROWL_REG 0x0014 +#define WIN_COLH_REG 0x0015 +#define WIN_COLL_REG 0x0016 +#define WIN_HEIGHTH_REG 0x0017 +#define WIN_HEIGHTL_REG 0x0018 +#define WIN_WIDTHH_REG 0x0019 +#define WIN_WIDTHL_REG 0x001A +#define HBLANKH_REG 0x001B +#define HBLANKL_REG 0x001C +#define VSYNCH_REG 0x001D +#define VSYNCL_REG 0x001E +/* VSYNC control */ +#define VS_CTL_REG(n) (0x00A1 + (n)) +/* page 1 */ +#define ISP_CTL_REG(n) (0x0110 + (n)) +#define YOFS_REG 0x0119 +#define DARK_YOFS_REG 0x011A +#define SAT_CTL_REG 0x0120 +#define BSAT_REG 0x0121 +#define RSAT_REG 0x0122 +/* Color correction */ +#define CMC_CTL_REG 0x0130 +#define CMC_OFSGH_REG 0x0133 +#define CMC_OFSGL_REG 0x0135 +#define CMC_SIGN_REG 0x0136 +#define CMC_GOFS_REG 0x0137 +#define CMC_COEF_REG(n) (0x0138 + (n)) +#define CMC_OFS_REG(n) (0x0141 + (n)) +/* Gamma correction */ +#define GMA_CTL_REG 0x0160 +#define GMA_COEF_REG(n) (0x0161 + (n)) +/* Lens Shading */ +#define LENS_CTRL_REG 0x01D0 +#define LENS_XCEN_REG 0x01D1 +#define LENS_YCEN_REG 0x01D2 +#define LENS_RC_REG 0x01D3 +#define LENS_GC_REG 0x01D4 +#define LENS_BC_REG 0x01D5 +#define L_AGON_REG 0x01D6 +#define L_AGOFF_REG 0x01D7 +/* Page 3 - Auto Exposure */ +#define AE_CTL_REG(n) (0x0310 + (n)) +#define AE_CTL9_REG 0x032C +#define AE_CTL10_REG 0x032D +#define AE_YLVL_REG 0x031C +#define AE_YTH_REG(n) (0x031D + (n)) +#define AE_WGT_REG 0x0326 +#define EXP_TIMEH_REG 0x0333 +#define EXP_TIMEM_REG 0x0334 +#define EXP_TIMEL_REG 0x0335 +#define EXP_MMINH_REG 0x0336 +#define EXP_MMINL_REG 0x0337 +#define EXP_MMAXH_REG 0x0338 +#define EXP_MMAXM_REG 0x0339 +#define EXP_MMAXL_REG 0x033A +/* Page 4 - Auto White Balance */ +#define AWB_CTL_REG(n) (0x0410 + (n)) +#define AWB_ENABE 0x80 +#define AWB_WGHT_REG 0x0419 +#define BGAIN_PAR_REG(n) (0x044F + (n)) +/* Manual white balance, when AWB_CTL2[0]=1 */ +#define MWB_RGAIN_REG 0x0466 +#define MWB_BGAIN_REG 0x0467 + +/* The token to mark an array end */ +#define REG_TERM 0xFFFF + +struct noon010_format { + enum v4l2_mbus_pixelcode code; + enum v4l2_colorspace colorspace; + u16 ispctl1_reg; +}; + +struct noon010_frmsize { + u16 width; + u16 height; + int vid_ctl1; +}; + +static const char * const noon010_supply_name[] = { + "vdd_core", "vddio", "vdda" +}; + +#define NOON010_NUM_SUPPLIES ARRAY_SIZE(noon010_supply_name) + +struct noon010_info { + struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; + const struct noon010pc30_platform_data *pdata; + const struct noon010_format *curr_fmt; + const struct noon010_frmsize *curr_win; + unsigned int hflip:1; + unsigned int vflip:1; + unsigned int power:1; + u8 i2c_reg_page; + struct regulator_bulk_data supply[NOON010_NUM_SUPPLIES]; + u32 gpio_nreset; + u32 gpio_nstby; +}; + +struct i2c_regval { + u16 addr; + u16 val; +}; + +/* Supported resolutions. */ +static const struct noon010_frmsize noon010_sizes[] = { + { + .width = 352, + .height = 288, + .vid_ctl1 = 0, + }, { + .width = 176, + .height = 144, + .vid_ctl1 = 0x10, + }, { + .width = 88, + .height = 72, + .vid_ctl1 = 0x20, + }, +}; + +/* Supported pixel formats. */ +static const struct noon010_format noon010_formats[] = { + { + .code = V4L2_MBUS_FMT_YUYV8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x03, + }, { + .code = V4L2_MBUS_FMT_YVYU8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x02, + }, { + .code = V4L2_MBUS_FMT_VYUY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0, + }, { + .code = V4L2_MBUS_FMT_UYVY8_2X8, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x01, + }, { + .code = V4L2_MBUS_FMT_RGB565_2X8_BE, + .colorspace = V4L2_COLORSPACE_JPEG, + .ispctl1_reg = 0x40, + }, +}; + +static const struct i2c_regval noon010_base_regs[] = { + { WIN_COLL_REG, 0x06 }, { HBLANKL_REG, 0x7C }, + /* Color corection and saturation */ + { ISP_CTL_REG(0), 0x30 }, { ISP_CTL_REG(2), 0x30 }, + { YOFS_REG, 0x80 }, { DARK_YOFS_REG, 0x04 }, + { SAT_CTL_REG, 0x1F }, { BSAT_REG, 0x90 }, + { CMC_CTL_REG, 0x0F }, { CMC_OFSGH_REG, 0x3C }, + { CMC_OFSGL_REG, 0x2C }, { CMC_SIGN_REG, 0x3F }, + { CMC_COEF_REG(0), 0x79 }, { CMC_OFS_REG(0), 0x00 }, + { CMC_COEF_REG(1), 0x39 }, { CMC_OFS_REG(1), 0x00 }, + { CMC_COEF_REG(2), 0x00 }, { CMC_OFS_REG(2), 0x00 }, + { CMC_COEF_REG(3), 0x11 }, { CMC_OFS_REG(3), 0x8B }, + { CMC_COEF_REG(4), 0x65 }, { CMC_OFS_REG(4), 0x07 }, + { CMC_COEF_REG(5), 0x14 }, { CMC_OFS_REG(5), 0x04 }, + { CMC_COEF_REG(6), 0x01 }, { CMC_OFS_REG(6), 0x9C }, + { CMC_COEF_REG(7), 0x33 }, { CMC_OFS_REG(7), 0x89 }, + { CMC_COEF_REG(8), 0x74 }, { CMC_OFS_REG(8), 0x25 }, + /* Automatic white balance */ + { AWB_CTL_REG(0), 0x78 }, { AWB_CTL_REG(1), 0x2E }, + { AWB_CTL_REG(2), 0x20 }, { AWB_CTL_REG(3), 0x85 }, + /* Auto exposure */ + { AE_CTL_REG(0), 0xDC }, { AE_CTL_REG(1), 0x81 }, + { AE_CTL_REG(2), 0x30 }, { AE_CTL_REG(3), 0xA5 }, + { AE_CTL_REG(4), 0x40 }, { AE_CTL_REG(5), 0x51 }, + { AE_CTL_REG(6), 0x33 }, { AE_CTL_REG(7), 0x7E }, + { AE_CTL9_REG, 0x00 }, { AE_CTL10_REG, 0x02 }, + { AE_YLVL_REG, 0x44 }, { AE_YTH_REG(0), 0x34 }, + { AE_YTH_REG(1), 0x30 }, { AE_WGT_REG, 0xD5 }, + /* Lens shading compensation */ + { LENS_CTRL_REG, 0x01 }, { LENS_XCEN_REG, 0x80 }, + { LENS_YCEN_REG, 0x70 }, { LENS_RC_REG, 0x53 }, + { LENS_GC_REG, 0x40 }, { LENS_BC_REG, 0x3E }, + { REG_TERM, 0 }, +}; + +static inline struct noon010_info *to_noon010(struct v4l2_subdev *sd) +{ + return container_of(sd, struct noon010_info, sd); +} + +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct noon010_info, hdl)->sd; +} + +static inline int set_i2c_page(struct noon010_info *info, + struct i2c_client *client, unsigned int reg) +{ + u32 page = reg >> 8 & 0xFF; + int ret = 0; + + if (info->i2c_reg_page != page && (reg & 0xFF) != 0x03) { + ret = i2c_smbus_write_byte_data(client, PAGEMODE_REG, page); + if (!ret) + info->i2c_reg_page = page; + } + return ret; +} + +static int cam_i2c_read(struct v4l2_subdev *sd, u32 reg_addr) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct noon010_info *info = to_noon010(sd); + int ret = set_i2c_page(info, client, reg_addr); + + if (ret) + return ret; + return i2c_smbus_read_byte_data(client, reg_addr & 0xFF); +} + +static int cam_i2c_write(struct v4l2_subdev *sd, u32 reg_addr, u32 val) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + struct noon010_info *info = to_noon010(sd); + int ret = set_i2c_page(info, client, reg_addr); + + if (ret) + return ret; + return i2c_smbus_write_byte_data(client, reg_addr & 0xFF, val); +} + +static inline int noon010_bulk_write_reg(struct v4l2_subdev *sd, + const struct i2c_regval *msg) +{ + while (msg->addr != REG_TERM) { + int ret = cam_i2c_write(sd, msg->addr, msg->val); + + if (ret) + return ret; + msg++; + } + return 0; +} + +/* Device reset and sleep mode control */ +static int noon010_power_ctrl(struct v4l2_subdev *sd, bool reset, bool sleep) +{ + struct noon010_info *info = to_noon010(sd); + u8 reg = sleep ? 0xF1 : 0xF0; + int ret = 0; + + if (reset) + ret = cam_i2c_write(sd, POWER_CTRL_REG, reg | 0x02); + if (!ret) { + ret = cam_i2c_write(sd, POWER_CTRL_REG, reg); + if (reset && !ret) + info->i2c_reg_page = -1; + } + return ret; +} + +/* Automatic white balance control */ +static int noon010_enable_autowhitebalance(struct v4l2_subdev *sd, int on) +{ + int ret; + + ret = cam_i2c_write(sd, AWB_CTL_REG(1), on ? 0x2E : 0x2F); + if (!ret) + ret = cam_i2c_write(sd, AWB_CTL_REG(0), on ? 0xFB : 0x7B); + return ret; +} + +static int noon010_set_flip(struct v4l2_subdev *sd, int hflip, int vflip) +{ + struct noon010_info *info = to_noon010(sd); + int reg, ret; + + reg = cam_i2c_read(sd, VDO_CTL_REG(1)); + if (reg < 0) + return reg; + + reg &= 0x7C; + if (hflip) + reg |= 0x01; + if (vflip) + reg |= 0x02; + + ret = cam_i2c_write(sd, VDO_CTL_REG(1), reg | 0x80); + if (!ret) { + info->hflip = hflip; + info->vflip = vflip; + } + return ret; +} + +/* Configure resolution and color format */ +static int noon010_set_params(struct v4l2_subdev *sd) +{ + struct noon010_info *info = to_noon010(sd); + int ret; + + if (!info->curr_win) + return -EINVAL; + + ret = cam_i2c_write(sd, VDO_CTL_REG(0), info->curr_win->vid_ctl1); + + if (!ret && info->curr_fmt) + ret = cam_i2c_write(sd, ISP_CTL_REG(0), + info->curr_fmt->ispctl1_reg); + return ret; +} + +/* Find nearest matching image pixel size. */ +static int noon010_try_frame_size(struct v4l2_mbus_framefmt *mf) +{ + unsigned int min_err = ~0; + int i = ARRAY_SIZE(noon010_sizes); + const struct noon010_frmsize *fsize = &noon010_sizes[0], + *match = NULL; + + while (i--) { + int err = abs(fsize->width - mf->width) + + abs(fsize->height - mf->height); + + if (err < min_err) { + min_err = err; + match = fsize; + } + fsize++; + } + if (match) { + mf->width = match->width; + mf->height = match->height; + return 0; + } + return -EINVAL; +} + +static int power_enable(struct noon010_info *info) +{ + int ret; + + if (info->power) { + v4l2_info(&info->sd, "%s: sensor is already on\n", __func__); + return 0; + } + + if (gpio_is_valid(info->gpio_nstby)) + gpio_set_value(info->gpio_nstby, 0); + + if (gpio_is_valid(info->gpio_nreset)) + gpio_set_value(info->gpio_nreset, 0); + + ret = regulator_bulk_enable(NOON010_NUM_SUPPLIES, info->supply); + if (ret) + return ret; + + if (gpio_is_valid(info->gpio_nreset)) { + msleep(50); + gpio_set_value(info->gpio_nreset, 1); + } + if (gpio_is_valid(info->gpio_nstby)) { + udelay(1000); + gpio_set_value(info->gpio_nstby, 1); + } + if (gpio_is_valid(info->gpio_nreset)) { + udelay(1000); + gpio_set_value(info->gpio_nreset, 0); + msleep(100); + gpio_set_value(info->gpio_nreset, 1); + msleep(20); + } + info->power = 1; + + v4l2_dbg(1, debug, &info->sd, "%s: sensor is on\n", __func__); + return 0; +} + +static int power_disable(struct noon010_info *info) +{ + int ret; + + if (!info->power) { + v4l2_info(&info->sd, "%s: sensor is already off\n", __func__); + return 0; + } + + ret = regulator_bulk_disable(NOON010_NUM_SUPPLIES, info->supply); + if (ret) + return ret; + + if (gpio_is_valid(info->gpio_nstby)) + gpio_set_value(info->gpio_nstby, 0); + + if (gpio_is_valid(info->gpio_nreset)) + gpio_set_value(info->gpio_nreset, 0); + + info->power = 0; + + v4l2_dbg(1, debug, &info->sd, "%s: sensor is off\n", __func__); + + return 0; +} + +static int noon010_s_ctrl(struct v4l2_ctrl *ctrl) +{ + struct v4l2_subdev *sd = to_sd(ctrl); + + v4l2_dbg(1, debug, sd, "%s: ctrl_id: %d, value: %d\n", + __func__, ctrl->id, ctrl->val); + + switch (ctrl->id) { + case V4L2_CID_AUTO_WHITE_BALANCE: + return noon010_enable_autowhitebalance(sd, ctrl->val); + case V4L2_CID_BLUE_BALANCE: + return cam_i2c_write(sd, MWB_BGAIN_REG, ctrl->val); + case V4L2_CID_RED_BALANCE: + return cam_i2c_write(sd, MWB_RGAIN_REG, ctrl->val); + default: + return -EINVAL; + } +} + +static int noon010_enum_fmt(struct v4l2_subdev *sd, unsigned int index, + enum v4l2_mbus_pixelcode *code) +{ + if (!code || index >= ARRAY_SIZE(noon010_formats)) + return -EINVAL; + + *code = noon010_formats[index].code; + return 0; +} + +static int noon010_g_fmt(struct v4l2_subdev *sd, struct v4l2_mbus_framefmt *mf) +{ + struct noon010_info *info = to_noon010(sd); + int ret; + + if (!mf) + return -EINVAL; + + if (!info->curr_win || !info->curr_fmt) { + ret = noon010_set_params(sd); + if (ret) + return ret; + } + + mf->width = info->curr_win->width; + mf->height = info->curr_win->height; + mf->code = info->curr_fmt->code; + mf->colorspace = info->curr_fmt->colorspace; + mf->field = V4L2_FIELD_NONE; + + return 0; +} + +/* Return nearest media bus frame format. */ +static const struct noon010_format *try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + int i = ARRAY_SIZE(noon010_formats); + + noon010_try_frame_size(mf); + + while (i--) + if (mf->code == noon010_formats[i].code) + break; + + mf->code = noon010_formats[i].code; + + return &noon010_formats[i]; +} + +static int noon010_try_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + if (!sd || !mf) + return -EINVAL; + + try_fmt(sd, mf); + return 0; +} + +static int noon010_s_fmt(struct v4l2_subdev *sd, + struct v4l2_mbus_framefmt *mf) +{ + struct noon010_info *info = to_noon010(sd); + + if (!sd || !mf) + return -EINVAL; + + info->curr_fmt = try_fmt(sd, mf); + + return noon010_set_params(sd); +} + +static int noon010_base_config(struct v4l2_subdev *sd) +{ + struct noon010_info *info = to_noon010(sd); + int ret; + + ret = noon010_bulk_write_reg(sd, noon010_base_regs); + if (!ret) { + info->curr_fmt = &noon010_formats[0]; + info->curr_win = &noon010_sizes[0]; + ret = noon010_set_params(sd); + } + if (!ret) + ret = noon010_set_flip(sd, 1, 0); + if (!ret) + ret = noon010_power_ctrl(sd, false, false); + + /* sync the handler and the registers state */ + v4l2_ctrl_handler_setup(&to_noon010(sd)->hdl); + return ret; +} + +static int noon010_s_power(struct v4l2_subdev *sd, int on) +{ + struct noon010_info *info = to_noon010(sd); + const struct noon010pc30_platform_data *pdata = info->pdata; + int ret = 0; + + if (WARN(pdata == NULL, "No platform data!\n")) + return -ENOMEM; + + if (on) { + ret = power_enable(info); + if (ret) + return ret; + ret = noon010_base_config(sd); + } else { + noon010_power_ctrl(sd, false, true); + ret = power_disable(info); + info->curr_win = NULL; + info->curr_fmt = NULL; + } + + return ret; +} + +static int noon010_g_chip_ident(struct v4l2_subdev *sd, + struct v4l2_dbg_chip_ident *chip) +{ + struct i2c_client *client = v4l2_get_subdevdata(sd); + + return v4l2_chip_ident_i2c_client(client, chip, + V4L2_IDENT_NOON010PC30, 0); +} + +static int noon010_log_status(struct v4l2_subdev *sd) +{ + struct noon010_info *info = to_noon010(sd); + + v4l2_ctrl_handler_log_status(&info->hdl, sd->name); + return 0; +} + +static const struct v4l2_ctrl_ops noon010_ctrl_ops = { + .s_ctrl = noon010_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops noon010_core_ops = { + .g_chip_ident = noon010_g_chip_ident, + .s_power = noon010_s_power, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .log_status = noon010_log_status, +}; + +static const struct v4l2_subdev_video_ops noon010_video_ops = { + .g_mbus_fmt = noon010_g_fmt, + .s_mbus_fmt = noon010_s_fmt, + .try_mbus_fmt = noon010_try_fmt, + .enum_mbus_fmt = noon010_enum_fmt, +}; + +static const struct v4l2_subdev_ops noon010_ops = { + .core = &noon010_core_ops, + .video = &noon010_video_ops, +}; + +/* Return 0 if NOON010PC30L sensor type was detected or -ENODEV otherwise. */ +static int noon010_detect(struct i2c_client *client, struct noon010_info *info) +{ + int ret; + + ret = power_enable(info); + if (ret) + return ret; + + ret = i2c_smbus_read_byte_data(client, DEVICE_ID_REG); + if (ret < 0) + dev_err(&client->dev, "I2C read failed: 0x%X\n", ret); + + power_disable(info); + + return ret == NOON010PC30_ID ? 0 : -ENODEV; +} + +static int noon010_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct noon010_info *info; + struct v4l2_subdev *sd; + const struct noon010pc30_platform_data *pdata + = client->dev.platform_data; + int ret; + int i; + + if (!pdata) { + dev_err(&client->dev, "No platform data!\n"); + return -EIO; + } + + info = kzalloc(sizeof(*info), GFP_KERNEL); + if (!info) + return -ENOMEM; + + sd = &info->sd; + strlcpy(sd->name, MODULE_NAME, sizeof(sd->name)); + v4l2_i2c_subdev_init(sd, client, &noon010_ops); + + v4l2_ctrl_handler_init(&info->hdl, 3); + + v4l2_ctrl_new_std(&info->hdl, &noon010_ctrl_ops, + V4L2_CID_AUTO_WHITE_BALANCE, 0, 1, 1, 1); + v4l2_ctrl_new_std(&info->hdl, &noon010_ctrl_ops, + V4L2_CID_RED_BALANCE, 0, 127, 1, 64); + v4l2_ctrl_new_std(&info->hdl, &noon010_ctrl_ops, + V4L2_CID_BLUE_BALANCE, 0, 127, 1, 64); + + sd->ctrl_handler = &info->hdl; + + ret = info->hdl.error; + if (ret) + goto np_err; + + info->pdata = client->dev.platform_data; + info->i2c_reg_page = -1; + info->gpio_nreset = -EINVAL; + info->gpio_nstby = -EINVAL; + + if (gpio_is_valid(pdata->gpio_nreset)) { + ret = gpio_request(pdata->gpio_nreset, "NOON010PC30 NRST"); + if (ret) { + dev_err(&client->dev, "GPIO request error: %d\n", ret); + goto np_err; + } + info->gpio_nreset = pdata->gpio_nreset; + gpio_direction_output(info->gpio_nreset, 0); + gpio_export(info->gpio_nreset, 0); + } + + if (gpio_is_valid(pdata->gpio_nstby)) { + ret = gpio_request(pdata->gpio_nstby, "NOON010PC30 NSTBY"); + if (ret) { + dev_err(&client->dev, "GPIO request error: %d\n", ret); + goto np_gpio_err; + } + info->gpio_nstby = pdata->gpio_nstby; + gpio_direction_output(info->gpio_nstby, 0); + gpio_export(info->gpio_nstby, 0); + } + + for (i = 0; i < NOON010_NUM_SUPPLIES; i++) + info->supply[i].supply = noon010_supply_name[i]; + + ret = regulator_bulk_get(&client->dev, NOON010_NUM_SUPPLIES, + info->supply); + if (ret) + goto np_reg_err; + + ret = noon010_detect(client, info); + if (!ret) + return 0; + + /* the sensor detection failed */ + regulator_bulk_free(NOON010_NUM_SUPPLIES, info->supply); +np_reg_err: + if (gpio_is_valid(info->gpio_nstby)) + gpio_free(info->gpio_nstby); +np_gpio_err: + if (gpio_is_valid(info->gpio_nreset)) + gpio_free(info->gpio_nreset); +np_err: + v4l2_ctrl_handler_free(&info->hdl); + v4l2_device_unregister_subdev(sd); + kfree(info); + return ret; +} + +static int noon010_remove(struct i2c_client *client) +{ + struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct noon010_info *info = to_noon010(sd); + + v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&info->hdl); + + regulator_bulk_free(NOON010_NUM_SUPPLIES, info->supply); + + if (gpio_is_valid(info->gpio_nreset)) + gpio_free(info->gpio_nreset); + + if (gpio_is_valid(info->gpio_nstby)) + gpio_free(info->gpio_nstby); + + kfree(info); + return 0; +} + +static const struct i2c_device_id noon010_id[] = { + { MODULE_NAME, 0 }, + { }, +}; +MODULE_DEVICE_TABLE(i2c, noon010_id); + + +static struct i2c_driver noon010_i2c_driver = { + .driver = { + .name = MODULE_NAME + }, + .probe = noon010_probe, + .remove = noon010_remove, + .id_table = noon010_id, +}; + +static int __init noon010_init(void) +{ + return i2c_add_driver(&noon010_i2c_driver); +} + +static void __exit noon010_exit(void) +{ + i2c_del_driver(&noon010_i2c_driver); +} + +module_init(noon010_init); +module_exit(noon010_exit); + +MODULE_DESCRIPTION("Siliconfile NOON010PC30 camera driver"); +MODULE_AUTHOR("Sylwester Nawrocki <s.nawrocki@samsung.com>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/s5p-fimc/fimc-capture.c b/drivers/media/video/s5p-fimc/fimc-capture.c index 2f500809f53d..5159cc8a0e1c 100644 --- a/drivers/media/video/s5p-fimc/fimc-capture.c +++ b/drivers/media/video/s5p-fimc/fimc-capture.c @@ -27,13 +27,13 @@ #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> #include <media/v4l2-mem2mem.h> -#include <media/videobuf-core.h> -#include <media/videobuf-dma-contig.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> #include "fimc-core.h" static struct v4l2_subdev *fimc_subdev_register(struct fimc_dev *fimc, - struct s3c_fimc_isp_info *isp_info) + struct s5p_fimc_isp_info *isp_info) { struct i2c_adapter *i2c_adap; struct fimc_vid_cap *vid_cap = &fimc->vid_cap; @@ -86,8 +86,8 @@ static void fimc_subdev_unregister(struct fimc_dev *fimc) static int fimc_subdev_attach(struct fimc_dev *fimc, int index) { struct fimc_vid_cap *vid_cap = &fimc->vid_cap; - struct s3c_platform_fimc *pdata = fimc->pdata; - struct s3c_fimc_isp_info *isp_info; + struct s5p_platform_fimc *pdata = fimc->pdata; + struct s5p_fimc_isp_info *isp_info; struct v4l2_subdev *sd; int i; @@ -113,26 +113,43 @@ static int fimc_subdev_attach(struct fimc_dev *fimc, int index) return -ENODEV; } -static int fimc_isp_subdev_init(struct fimc_dev *fimc, int index) +static int fimc_isp_subdev_init(struct fimc_dev *fimc, unsigned int index) { - struct s3c_fimc_isp_info *isp_info; + struct s5p_fimc_isp_info *isp_info; int ret; + if (index >= FIMC_MAX_CAMIF_CLIENTS) + return -EINVAL; + + isp_info = fimc->pdata->isp_info[index]; + if (!isp_info) + return -EINVAL; + + if (isp_info->clk_frequency) + clk_set_rate(fimc->clock[CLK_CAM], isp_info->clk_frequency); + + ret = clk_enable(fimc->clock[CLK_CAM]); + if (ret) + return ret; + ret = fimc_subdev_attach(fimc, index); if (ret) return ret; - isp_info = fimc->pdata->isp_info[fimc->vid_cap.input_index]; ret = fimc_hw_set_camera_polarity(fimc, isp_info); - if (!ret) { - ret = v4l2_subdev_call(fimc->vid_cap.sd, core, - s_power, 1); - if (!ret) - return ret; - } + if (ret) + return ret; + ret = v4l2_subdev_call(fimc->vid_cap.sd, core, s_power, 1); + if (!ret) + return ret; + + /* enabling power failed so unregister subdev */ fimc_subdev_unregister(fimc); - err("ISP initialization failed: %d", ret); + + v4l2_err(&fimc->vid_cap.v4l2_dev, "ISP initialization failed: %d\n", + ret); + return ret; } @@ -141,7 +158,7 @@ static int fimc_isp_subdev_init(struct fimc_dev *fimc, int index) * Locking: The caller holds fimc->slock spinlock. */ int fimc_vid_cap_buf_queue(struct fimc_dev *fimc, - struct fimc_vid_buffer *fimc_vb) + struct fimc_vid_buffer *fimc_vb) { struct fimc_vid_cap *cap = &fimc->vid_cap; struct fimc_ctx *ctx = cap->ctx; @@ -149,7 +166,7 @@ int fimc_vid_cap_buf_queue(struct fimc_dev *fimc, BUG_ON(!fimc || !fimc_vb); - ret = fimc_prepare_addr(ctx, fimc_vb, &ctx->d_frame, + ret = fimc_prepare_addr(ctx, &fimc_vb->vb, &ctx->d_frame, &fimc_vb->paddr); if (ret) return ret; @@ -174,7 +191,7 @@ static int fimc_stop_capture(struct fimc_dev *fimc) { unsigned long flags; struct fimc_vid_cap *cap; - int ret; + struct fimc_vid_buffer *buf; cap = &fimc->vid_cap; @@ -187,24 +204,213 @@ static int fimc_stop_capture(struct fimc_dev *fimc) spin_unlock_irqrestore(&fimc->slock, flags); wait_event_timeout(fimc->irq_queue, - test_bit(ST_CAPT_SHUT, &fimc->state), + !test_bit(ST_CAPT_SHUT, &fimc->state), FIMC_SHUTDOWN_TIMEOUT); - ret = v4l2_subdev_call(cap->sd, video, s_stream, 0); - if (ret) - v4l2_err(&fimc->vid_cap.v4l2_dev, "s_stream(0) failed\n"); + v4l2_subdev_call(cap->sd, video, s_stream, 0); spin_lock_irqsave(&fimc->slock, flags); fimc->state &= ~(1 << ST_CAPT_RUN | 1 << ST_CAPT_PEND | 1 << ST_CAPT_STREAM); fimc->vid_cap.active_buf_cnt = 0; + + /* Release buffers that were enqueued in the driver by videobuf2. */ + while (!list_empty(&cap->pending_buf_q)) { + buf = pending_queue_pop(cap); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + + while (!list_empty(&cap->active_buf_q)) { + buf = active_queue_pop(cap); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + } + spin_unlock_irqrestore(&fimc->slock, flags); dbg("state: 0x%lx", fimc->state); return 0; } +static int start_streaming(struct vb2_queue *q) +{ + struct fimc_ctx *ctx = q->drv_priv; + struct fimc_dev *fimc = ctx->fimc_dev; + struct s5p_fimc_isp_info *isp_info; + int ret; + + ret = v4l2_subdev_call(fimc->vid_cap.sd, video, s_stream, 1); + if (ret && ret != -ENOIOCTLCMD) + return ret; + + ret = fimc_prepare_config(ctx, ctx->state); + if (ret) + return ret; + + isp_info = fimc->pdata->isp_info[fimc->vid_cap.input_index]; + fimc_hw_set_camera_type(fimc, isp_info); + fimc_hw_set_camera_source(fimc, isp_info); + fimc_hw_set_camera_offset(fimc, &ctx->s_frame); + + if (ctx->state & FIMC_PARAMS) { + ret = fimc_set_scaler_info(ctx); + if (ret) { + err("Scaler setup error"); + return ret; + } + fimc_hw_set_input_path(ctx); + fimc_hw_set_prescaler(ctx); + fimc_hw_set_mainscaler(ctx); + fimc_hw_set_target_format(ctx); + fimc_hw_set_rotation(ctx); + fimc_hw_set_effect(ctx); + } + + fimc_hw_set_output_path(ctx); + fimc_hw_set_out_dma(ctx); + + INIT_LIST_HEAD(&fimc->vid_cap.pending_buf_q); + INIT_LIST_HEAD(&fimc->vid_cap.active_buf_q); + fimc->vid_cap.active_buf_cnt = 0; + fimc->vid_cap.frame_count = 0; + fimc->vid_cap.buf_index = fimc_hw_get_frame_index(fimc); + + set_bit(ST_CAPT_PEND, &fimc->state); + + return 0; +} + +static int stop_streaming(struct vb2_queue *q) +{ + struct fimc_ctx *ctx = q->drv_priv; + struct fimc_dev *fimc = ctx->fimc_dev; + unsigned long flags; + + spin_lock_irqsave(&fimc->slock, flags); + if (!fimc_capture_running(fimc) && !fimc_capture_pending(fimc)) { + spin_unlock_irqrestore(&fimc->slock, flags); + return -EINVAL; + } + spin_unlock_irqrestore(&fimc->slock, flags); + + return fimc_stop_capture(fimc); +} + +static unsigned int get_plane_size(struct fimc_frame *fr, unsigned int plane) +{ + if (!fr || plane >= fr->fmt->memplanes) + return 0; + + dbg("%s: w: %d. h: %d. depth[%d]: %d", + __func__, fr->width, fr->height, plane, fr->fmt->depth[plane]); + + return fr->f_width * fr->f_height * fr->fmt->depth[plane] / 8; + +} + +static int queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, + unsigned int *num_planes, unsigned long sizes[], + void *allocators[]) +{ + struct fimc_ctx *ctx = vq->drv_priv; + struct fimc_fmt *fmt = ctx->d_frame.fmt; + int i; + + if (!fmt) + return -EINVAL; + + *num_planes = fmt->memplanes; + + dbg("%s, buffer count=%d, plane count=%d", + __func__, *num_buffers, *num_planes); + + for (i = 0; i < fmt->memplanes; i++) { + sizes[i] = get_plane_size(&ctx->d_frame, i); + dbg("plane: %u, plane_size: %lu", i, sizes[i]); + allocators[i] = ctx->fimc_dev->alloc_ctx; + } + + return 0; +} + +static int buffer_init(struct vb2_buffer *vb) +{ + /* TODO: */ + return 0; +} + +static int buffer_prepare(struct vb2_buffer *vb) +{ + struct vb2_queue *vq = vb->vb2_queue; + struct fimc_ctx *ctx = vq->drv_priv; + struct v4l2_device *v4l2_dev = &ctx->fimc_dev->m2m.v4l2_dev; + int i; + + if (!ctx->d_frame.fmt || vq->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + return -EINVAL; + + for (i = 0; i < ctx->d_frame.fmt->memplanes; i++) { + unsigned long size = get_plane_size(&ctx->d_frame, i); + + if (vb2_plane_size(vb, i) < size) { + v4l2_err(v4l2_dev, "User buffer too small (%ld < %ld)\n", + vb2_plane_size(vb, i), size); + return -EINVAL; + } + + vb2_set_plane_payload(vb, i, size); + } + + return 0; +} + +static void buffer_queue(struct vb2_buffer *vb) +{ + struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); + struct fimc_dev *fimc = ctx->fimc_dev; + struct fimc_vid_buffer *buf + = container_of(vb, struct fimc_vid_buffer, vb); + struct fimc_vid_cap *vid_cap = &fimc->vid_cap; + unsigned long flags; + + spin_lock_irqsave(&fimc->slock, flags); + fimc_vid_cap_buf_queue(fimc, buf); + + dbg("active_buf_cnt: %d", fimc->vid_cap.active_buf_cnt); + + if (vid_cap->active_buf_cnt >= vid_cap->reqbufs_count || + vid_cap->active_buf_cnt >= FIMC_MAX_OUT_BUFS) { + if (!test_and_set_bit(ST_CAPT_STREAM, &fimc->state)) { + fimc_activate_capture(ctx); + dbg(""); + } + } + spin_unlock_irqrestore(&fimc->slock, flags); +} + +static void fimc_lock(struct vb2_queue *vq) +{ + struct fimc_ctx *ctx = vb2_get_drv_priv(vq); + mutex_lock(&ctx->fimc_dev->lock); +} + +static void fimc_unlock(struct vb2_queue *vq) +{ + struct fimc_ctx *ctx = vb2_get_drv_priv(vq); + mutex_unlock(&ctx->fimc_dev->lock); +} + +static struct vb2_ops fimc_capture_qops = { + .queue_setup = queue_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_init = buffer_init, + .wait_prepare = fimc_unlock, + .wait_finish = fimc_lock, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, +}; + static int fimc_capture_open(struct file *file) { struct fimc_dev *fimc = video_drvdata(file); @@ -216,44 +422,36 @@ static int fimc_capture_open(struct file *file) if (fimc_m2m_active(fimc)) return -EBUSY; - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - if (++fimc->vid_cap.refcnt == 1) { - ret = fimc_isp_subdev_init(fimc, -1); + ret = fimc_isp_subdev_init(fimc, 0); if (ret) { fimc->vid_cap.refcnt--; - ret = -EIO; + return -EIO; } } file->private_data = fimc->vid_cap.ctx; - mutex_unlock(&fimc->lock); - return ret; + return 0; } static int fimc_capture_close(struct file *file) { struct fimc_dev *fimc = video_drvdata(file); - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - dbg("pid: %d, state: 0x%lx", task_pid_nr(current), fimc->state); if (--fimc->vid_cap.refcnt == 0) { fimc_stop_capture(fimc); - - videobuf_stop(&fimc->vid_cap.vbq); - videobuf_mmap_free(&fimc->vid_cap.vbq); + vb2_queue_release(&fimc->vid_cap.vbq); v4l2_err(&fimc->vid_cap.v4l2_dev, "releasing ISP\n"); + v4l2_subdev_call(fimc->vid_cap.sd, core, s_power, 0); + clk_disable(fimc->clock[CLK_CAM]); fimc_subdev_unregister(fimc); } - mutex_unlock(&fimc->lock); return 0; } @@ -262,32 +460,16 @@ static unsigned int fimc_capture_poll(struct file *file, { struct fimc_ctx *ctx = file->private_data; struct fimc_dev *fimc = ctx->fimc_dev; - struct fimc_vid_cap *cap = &fimc->vid_cap; - int ret; - if (mutex_lock_interruptible(&fimc->lock)) - return POLLERR; - - ret = videobuf_poll_stream(file, &cap->vbq, wait); - mutex_unlock(&fimc->lock); - - return ret; + return vb2_poll(&fimc->vid_cap.vbq, file, wait); } static int fimc_capture_mmap(struct file *file, struct vm_area_struct *vma) { struct fimc_ctx *ctx = file->private_data; struct fimc_dev *fimc = ctx->fimc_dev; - struct fimc_vid_cap *cap = &fimc->vid_cap; - int ret; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - ret = videobuf_mmap_mapper(&cap->vbq, vma); - mutex_unlock(&fimc->lock); - - return ret; + return vb2_mmap(&fimc->vid_cap.vbq, vma); } /* video device file operations */ @@ -310,7 +492,8 @@ static int fimc_vidioc_querycap_capture(struct file *file, void *priv, strncpy(cap->card, fimc->pdev->name, sizeof(cap->card) - 1); cap->bus_info[0] = 0; cap->version = KERNEL_VERSION(1, 0, 0); - cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE; + cap->capabilities = V4L2_CAP_STREAMING | V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_VIDEO_CAPTURE_MPLANE; return 0; } @@ -351,57 +534,52 @@ static int sync_capture_fmt(struct fimc_ctx *ctx) return 0; } -static int fimc_cap_s_fmt(struct file *file, void *priv, - struct v4l2_format *f) +static int fimc_cap_s_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) { struct fimc_ctx *ctx = priv; struct fimc_dev *fimc = ctx->fimc_dev; struct fimc_frame *frame; - struct v4l2_pix_format *pix; + struct v4l2_pix_format_mplane *pix; int ret; + int i; - if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return -EINVAL; - ret = fimc_vidioc_try_fmt(file, priv, f); + ret = fimc_vidioc_try_fmt_mplane(file, priv, f); if (ret) return ret; - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - - if (fimc_capture_active(fimc)) { - ret = -EBUSY; - goto sf_unlock; - } + if (vb2_is_streaming(&fimc->vid_cap.vbq) || fimc_capture_active(fimc)) + return -EBUSY; frame = &ctx->d_frame; - pix = &f->fmt.pix; + pix = &f->fmt.pix_mp; frame->fmt = find_format(f, FMT_FLAGS_M2M | FMT_FLAGS_CAM); if (!frame->fmt) { err("fimc target format not found\n"); - ret = -EINVAL; - goto sf_unlock; + return -EINVAL; } + for (i = 0; i < frame->fmt->colplanes; i++) + frame->payload[i] = pix->plane_fmt[i].bytesperline * pix->height; + /* Output DMA frame pixel size and offsets. */ - frame->f_width = pix->bytesperline * 8 / frame->fmt->depth; + frame->f_width = pix->plane_fmt[0].bytesperline * 8 + / frame->fmt->depth[0]; frame->f_height = pix->height; frame->width = pix->width; frame->height = pix->height; frame->o_width = pix->width; frame->o_height = pix->height; - frame->size = (pix->width * pix->height * frame->fmt->depth) >> 3; frame->offs_h = 0; frame->offs_v = 0; - ret = sync_capture_fmt(ctx); - ctx->state |= (FIMC_PARAMS | FIMC_DST_FMT); -sf_unlock: - mutex_unlock(&fimc->lock); + ret = sync_capture_fmt(ctx); return ret; } @@ -409,8 +587,8 @@ static int fimc_cap_enum_input(struct file *file, void *priv, struct v4l2_input *i) { struct fimc_ctx *ctx = priv; - struct s3c_platform_fimc *pldata = ctx->fimc_dev->pdata; - struct s3c_fimc_isp_info *isp_info; + struct s5p_platform_fimc *pldata = ctx->fimc_dev->pdata; + struct s5p_fimc_isp_info *isp_info; if (i->index >= FIMC_MAX_CAMIF_CLIENTS) return -EINVAL; @@ -429,34 +607,27 @@ static int fimc_cap_s_input(struct file *file, void *priv, { struct fimc_ctx *ctx = priv; struct fimc_dev *fimc = ctx->fimc_dev; - struct s3c_platform_fimc *pdata = fimc->pdata; - int ret; + struct s5p_platform_fimc *pdata = fimc->pdata; if (fimc_capture_active(ctx->fimc_dev)) return -EBUSY; - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; + if (i >= FIMC_MAX_CAMIF_CLIENTS || !pdata->isp_info[i]) + return -EINVAL; - if (i >= FIMC_MAX_CAMIF_CLIENTS || !pdata->isp_info[i]) { - ret = -EINVAL; - goto si_unlock; - } if (fimc->vid_cap.sd) { - ret = v4l2_subdev_call(fimc->vid_cap.sd, core, s_power, 0); + int ret = v4l2_subdev_call(fimc->vid_cap.sd, core, s_power, 0); if (ret) err("s_power failed: %d", ret); + + clk_disable(fimc->clock[CLK_CAM]); } /* Release the attached sensor subdevice. */ fimc_subdev_unregister(fimc); - ret = fimc_isp_subdev_init(fimc, i); - -si_unlock: - mutex_unlock(&fimc->lock); - return ret; + return fimc_isp_subdev_init(fimc, i); } static int fimc_cap_g_input(struct file *file, void *priv, @@ -470,66 +641,20 @@ static int fimc_cap_g_input(struct file *file, void *priv, } static int fimc_cap_streamon(struct file *file, void *priv, - enum v4l2_buf_type type) + enum v4l2_buf_type type) { - struct s3c_fimc_isp_info *isp_info; struct fimc_ctx *ctx = priv; struct fimc_dev *fimc = ctx->fimc_dev; - int ret = -EBUSY; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; if (fimc_capture_active(fimc) || !fimc->vid_cap.sd) - goto s_unlock; + return -EBUSY; if (!(ctx->state & FIMC_DST_FMT)) { v4l2_err(&fimc->vid_cap.v4l2_dev, "Format is not set\n"); - ret = -EINVAL; - goto s_unlock; - } - - ret = v4l2_subdev_call(fimc->vid_cap.sd, video, s_stream, 1); - if (ret && ret != -ENOIOCTLCMD) - goto s_unlock; - - ret = fimc_prepare_config(ctx, ctx->state); - if (ret) - goto s_unlock; - - isp_info = fimc->pdata->isp_info[fimc->vid_cap.input_index]; - fimc_hw_set_camera_type(fimc, isp_info); - fimc_hw_set_camera_source(fimc, isp_info); - fimc_hw_set_camera_offset(fimc, &ctx->s_frame); - - if (ctx->state & FIMC_PARAMS) { - ret = fimc_set_scaler_info(ctx); - if (ret) { - err("Scaler setup error"); - goto s_unlock; - } - fimc_hw_set_input_path(ctx); - fimc_hw_set_scaler(ctx); - fimc_hw_set_target_format(ctx); - fimc_hw_set_rotation(ctx); - fimc_hw_set_effect(ctx); + return -EINVAL; } - fimc_hw_set_output_path(ctx); - fimc_hw_set_out_dma(ctx); - - INIT_LIST_HEAD(&fimc->vid_cap.pending_buf_q); - INIT_LIST_HEAD(&fimc->vid_cap.active_buf_q); - fimc->vid_cap.active_buf_cnt = 0; - fimc->vid_cap.frame_count = 0; - fimc->vid_cap.buf_index = fimc_hw_get_frame_index(fimc); - - set_bit(ST_CAPT_PEND, &fimc->state); - ret = videobuf_streamon(&fimc->vid_cap.vbq); - -s_unlock: - mutex_unlock(&fimc->lock); - return ret; + return vb2_streamon(&fimc->vid_cap.vbq, type); } static int fimc_cap_streamoff(struct file *file, void *priv, @@ -537,46 +662,22 @@ static int fimc_cap_streamoff(struct file *file, void *priv, { struct fimc_ctx *ctx = priv; struct fimc_dev *fimc = ctx->fimc_dev; - struct fimc_vid_cap *cap = &fimc->vid_cap; - unsigned long flags; - int ret; - - spin_lock_irqsave(&fimc->slock, flags); - if (!fimc_capture_running(fimc) && !fimc_capture_pending(fimc)) { - spin_unlock_irqrestore(&fimc->slock, flags); - dbg("state: 0x%lx", fimc->state); - return -EINVAL; - } - spin_unlock_irqrestore(&fimc->slock, flags); - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - - fimc_stop_capture(fimc); - ret = videobuf_streamoff(&cap->vbq); - mutex_unlock(&fimc->lock); - return ret; + return vb2_streamoff(&fimc->vid_cap.vbq, type); } static int fimc_cap_reqbufs(struct file *file, void *priv, - struct v4l2_requestbuffers *reqbufs) + struct v4l2_requestbuffers *reqbufs) { struct fimc_ctx *ctx = priv; - struct fimc_dev *fimc = ctx->fimc_dev; - struct fimc_vid_cap *cap = &fimc->vid_cap; + struct fimc_vid_cap *cap = &ctx->fimc_dev->vid_cap; int ret; - if (fimc_capture_active(ctx->fimc_dev)) - return -EBUSY; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - ret = videobuf_reqbufs(&cap->vbq, reqbufs); + ret = vb2_reqbufs(&cap->vbq, reqbufs); if (!ret) cap->reqbufs_count = reqbufs->count; - mutex_unlock(&fimc->lock); return ret; } @@ -586,43 +687,23 @@ static int fimc_cap_querybuf(struct file *file, void *priv, struct fimc_ctx *ctx = priv; struct fimc_vid_cap *cap = &ctx->fimc_dev->vid_cap; - if (fimc_capture_active(ctx->fimc_dev)) - return -EBUSY; - - return videobuf_querybuf(&cap->vbq, buf); + return vb2_querybuf(&cap->vbq, buf); } static int fimc_cap_qbuf(struct file *file, void *priv, struct v4l2_buffer *buf) { struct fimc_ctx *ctx = priv; - struct fimc_dev *fimc = ctx->fimc_dev; - struct fimc_vid_cap *cap = &fimc->vid_cap; - int ret; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - - ret = videobuf_qbuf(&cap->vbq, buf); - - mutex_unlock(&fimc->lock); - return ret; + struct fimc_vid_cap *cap = &ctx->fimc_dev->vid_cap; + return vb2_qbuf(&cap->vbq, buf); } static int fimc_cap_dqbuf(struct file *file, void *priv, struct v4l2_buffer *buf) { struct fimc_ctx *ctx = priv; - int ret; - - if (mutex_lock_interruptible(&ctx->fimc_dev->lock)) - return -ERESTARTSYS; - - ret = videobuf_dqbuf(&ctx->fimc_dev->vid_cap.vbq, buf, + return vb2_dqbuf(&ctx->fimc_dev->vid_cap.vbq, buf, file->f_flags & O_NONBLOCK); - - mutex_unlock(&ctx->fimc_dev->lock); - return ret; } static int fimc_cap_s_ctrl(struct file *file, void *priv, @@ -631,9 +712,6 @@ static int fimc_cap_s_ctrl(struct file *file, void *priv, struct fimc_ctx *ctx = priv; int ret = -EINVAL; - if (mutex_lock_interruptible(&ctx->fimc_dev->lock)) - return -ERESTARTSYS; - /* Allow any controls but 90/270 rotation while streaming */ if (!fimc_capture_active(ctx->fimc_dev) || ctrl->id != V4L2_CID_ROTATE || @@ -648,8 +726,6 @@ static int fimc_cap_s_ctrl(struct file *file, void *priv, if (ret == -EINVAL) ret = v4l2_subdev_call(ctx->fimc_dev->vid_cap.sd, core, s_ctrl, ctrl); - - mutex_unlock(&ctx->fimc_dev->lock); return ret; } @@ -658,22 +734,18 @@ static int fimc_cap_cropcap(struct file *file, void *fh, { struct fimc_frame *f; struct fimc_ctx *ctx = fh; - struct fimc_dev *fimc = ctx->fimc_dev; - if (cr->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + if (cr->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) return -EINVAL; - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - f = &ctx->s_frame; + cr->bounds.left = 0; cr->bounds.top = 0; cr->bounds.width = f->o_width; cr->bounds.height = f->o_height; cr->defrect = cr->bounds; - mutex_unlock(&fimc->lock); return 0; } @@ -681,19 +753,14 @@ static int fimc_cap_g_crop(struct file *file, void *fh, struct v4l2_crop *cr) { struct fimc_frame *f; struct fimc_ctx *ctx = file->private_data; - struct fimc_dev *fimc = ctx->fimc_dev; - - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; f = &ctx->s_frame; + cr->c.left = f->offs_h; cr->c.top = f->offs_v; cr->c.width = f->width; cr->c.height = f->height; - mutex_unlock(&fimc->lock); return 0; } @@ -712,41 +779,38 @@ static int fimc_cap_s_crop(struct file *file, void *fh, if (ret) return ret; - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - if (!(ctx->state & FIMC_DST_FMT)) { v4l2_err(&fimc->vid_cap.v4l2_dev, "Capture color format not set\n"); - goto sc_unlock; + return -EINVAL; /* TODO: make sure this is the right value */ } f = &ctx->s_frame; /* Check for the pixel scaling ratio when cropping input image. */ - ret = fimc_check_scaler_ratio(&cr->c, &ctx->d_frame); + ret = fimc_check_scaler_ratio(cr->c.width, cr->c.height, + ctx->d_frame.width, ctx->d_frame.height, + ctx->rotation); if (ret) { v4l2_err(&fimc->vid_cap.v4l2_dev, "Out of the scaler range"); - } else { - ret = 0; - f->offs_h = cr->c.left; - f->offs_v = cr->c.top; - f->width = cr->c.width; - f->height = cr->c.height; + return ret; } -sc_unlock: - mutex_unlock(&fimc->lock); - return ret; + f->offs_h = cr->c.left; + f->offs_v = cr->c.top; + f->width = cr->c.width; + f->height = cr->c.height; + + return 0; } static const struct v4l2_ioctl_ops fimc_capture_ioctl_ops = { .vidioc_querycap = fimc_vidioc_querycap_capture, - .vidioc_enum_fmt_vid_cap = fimc_vidioc_enum_fmt, - .vidioc_try_fmt_vid_cap = fimc_vidioc_try_fmt, - .vidioc_s_fmt_vid_cap = fimc_cap_s_fmt, - .vidioc_g_fmt_vid_cap = fimc_vidioc_g_fmt, + .vidioc_enum_fmt_vid_cap_mplane = fimc_vidioc_enum_fmt_mplane, + .vidioc_try_fmt_vid_cap_mplane = fimc_vidioc_try_fmt_mplane, + .vidioc_s_fmt_vid_cap_mplane = fimc_cap_s_fmt_mplane, + .vidioc_g_fmt_vid_cap_mplane = fimc_vidioc_g_fmt_mplane, .vidioc_reqbufs = fimc_cap_reqbufs, .vidioc_querybuf = fimc_cap_querybuf, @@ -770,6 +834,7 @@ static const struct v4l2_ioctl_ops fimc_capture_ioctl_ops = { .vidioc_g_input = fimc_cap_g_input, }; +/* fimc->lock must be already initialized */ int fimc_register_capture_device(struct fimc_dev *fimc) { struct v4l2_device *v4l2_dev = &fimc->vid_cap.v4l2_dev; @@ -777,6 +842,8 @@ int fimc_register_capture_device(struct fimc_dev *fimc) struct fimc_vid_cap *vid_cap; struct fimc_ctx *ctx; struct v4l2_format f; + struct fimc_frame *fr; + struct vb2_queue *q; int ret; ctx = kzalloc(sizeof *ctx, GFP_KERNEL); @@ -788,8 +855,12 @@ int fimc_register_capture_device(struct fimc_dev *fimc) ctx->out_path = FIMC_DMA; ctx->state = FIMC_CTX_CAP; - f.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; - ctx->d_frame.fmt = find_format(&f, FMT_FLAGS_M2M); + /* Default format of the output frames */ + f.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB32; + fr = &ctx->d_frame; + fr->fmt = find_format(&f, FMT_FLAGS_M2M); + fr->width = fr->f_width = fr->o_width = 640; + fr->height = fr->f_height = fr->o_height = 480; if (!v4l2_dev->name[0]) snprintf(v4l2_dev->name, sizeof(v4l2_dev->name), @@ -812,6 +883,7 @@ int fimc_register_capture_device(struct fimc_dev *fimc) vfd->ioctl_ops = &fimc_capture_ioctl_ops; vfd->minor = -1; vfd->release = video_device_release; + vfd->lock = &fimc->lock; video_set_drvdata(vfd, fimc); vid_cap = &fimc->vid_cap; @@ -819,7 +891,7 @@ int fimc_register_capture_device(struct fimc_dev *fimc) vid_cap->active_buf_cnt = 0; vid_cap->reqbufs_count = 0; vid_cap->refcnt = 0; - /* The default color format for image sensor. */ + /* Default color format for image sensor */ vid_cap->fmt.code = V4L2_MBUS_FMT_YUYV8_2X8; INIT_LIST_HEAD(&vid_cap->pending_buf_q); @@ -827,10 +899,16 @@ int fimc_register_capture_device(struct fimc_dev *fimc) spin_lock_init(&ctx->slock); vid_cap->ctx = ctx; - videobuf_queue_dma_contig_init(&vid_cap->vbq, &fimc_qops, - vid_cap->v4l2_dev.dev, &fimc->irqlock, - V4L2_BUF_TYPE_VIDEO_CAPTURE, V4L2_FIELD_NONE, - sizeof(struct fimc_vid_buffer), (void *)ctx, NULL); + q = &fimc->vid_cap.vbq; + memset(q, 0, sizeof(*q)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + q->io_modes = VB2_MMAP | VB2_USERPTR; + q->drv_priv = fimc->vid_cap.ctx; + q->ops = &fimc_capture_qops; + q->mem_ops = &vb2_dma_contig_memops; + q->buf_struct_size = sizeof(struct fimc_vid_buffer); + + vb2_queue_init(q); ret = video_register_device(vfd, VFL_TYPE_GRABBER, -1); if (ret) { diff --git a/drivers/media/video/s5p-fimc/fimc-core.c b/drivers/media/video/s5p-fimc/fimc-core.c index 817aa66627f6..cd8a3003f1aa 100644 --- a/drivers/media/video/s5p-fimc/fimc-core.c +++ b/drivers/media/video/s5p-fimc/fimc-core.c @@ -25,114 +25,141 @@ #include <linux/slab.h> #include <linux/clk.h> #include <media/v4l2-ioctl.h> -#include <media/videobuf-dma-contig.h> +#include <media/videobuf2-core.h> +#include <media/videobuf2-dma-contig.h> #include "fimc-core.h" -static char *fimc_clock_name[NUM_FIMC_CLOCKS] = { "sclk_fimc", "fimc" }; +static char *fimc_clocks[MAX_FIMC_CLOCKS] = { + "sclk_fimc", "fimc", "sclk_cam" +}; static struct fimc_fmt fimc_formats[] = { { - .name = "RGB565", - .fourcc = V4L2_PIX_FMT_RGB565X, - .depth = 16, - .color = S5P_FIMC_RGB565, - .buff_cnt = 1, - .planes_cnt = 1, - .mbus_code = V4L2_MBUS_FMT_RGB565_2X8_BE, - .flags = FMT_FLAGS_M2M, + .name = "RGB565", + .fourcc = V4L2_PIX_FMT_RGB565X, + .depth = { 16 }, + .color = S5P_FIMC_RGB565, + .memplanes = 1, + .colplanes = 1, + .mbus_code = V4L2_MBUS_FMT_RGB565_2X8_BE, + .flags = FMT_FLAGS_M2M, + }, { + .name = "BGR666", + .fourcc = V4L2_PIX_FMT_BGR666, + .depth = { 32 }, + .color = S5P_FIMC_RGB666, + .memplanes = 1, + .colplanes = 1, + .flags = FMT_FLAGS_M2M, + }, { + .name = "XRGB-8-8-8-8, 32 bpp", + .fourcc = V4L2_PIX_FMT_RGB32, + .depth = { 32 }, + .color = S5P_FIMC_RGB888, + .memplanes = 1, + .colplanes = 1, + .flags = FMT_FLAGS_M2M, }, { - .name = "BGR666", - .fourcc = V4L2_PIX_FMT_BGR666, - .depth = 32, - .color = S5P_FIMC_RGB666, - .buff_cnt = 1, - .planes_cnt = 1, - .flags = FMT_FLAGS_M2M, + .name = "YUV 4:2:2 packed, YCbYCr", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = { 16 }, + .color = S5P_FIMC_YCBYCR422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, }, { - .name = "XRGB-8-8-8-8, 32 bpp", - .fourcc = V4L2_PIX_FMT_RGB32, - .depth = 32, - .color = S5P_FIMC_RGB888, - .buff_cnt = 1, - .planes_cnt = 1, - .flags = FMT_FLAGS_M2M, + .name = "YUV 4:2:2 packed, CbYCrY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = { 16 }, + .color = S5P_FIMC_CBYCRY422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = V4L2_MBUS_FMT_UYVY8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, }, { - .name = "YUV 4:2:2 packed, YCbYCr", - .fourcc = V4L2_PIX_FMT_YUYV, - .depth = 16, - .color = S5P_FIMC_YCBYCR422, - .buff_cnt = 1, - .planes_cnt = 1, - .mbus_code = V4L2_MBUS_FMT_YUYV8_2X8, - .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + .name = "YUV 4:2:2 packed, CrYCbY", + .fourcc = V4L2_PIX_FMT_VYUY, + .depth = { 16 }, + .color = S5P_FIMC_CRYCBY422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = V4L2_MBUS_FMT_VYUY8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, }, { - .name = "YUV 4:2:2 packed, CbYCrY", - .fourcc = V4L2_PIX_FMT_UYVY, - .depth = 16, - .color = S5P_FIMC_CBYCRY422, - .buff_cnt = 1, - .planes_cnt = 1, - .mbus_code = V4L2_MBUS_FMT_UYVY8_2X8, - .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + .name = "YUV 4:2:2 packed, YCrYCb", + .fourcc = V4L2_PIX_FMT_YVYU, + .depth = { 16 }, + .color = S5P_FIMC_YCRYCB422, + .memplanes = 1, + .colplanes = 1, + .mbus_code = V4L2_MBUS_FMT_YVYU8_2X8, + .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, }, { - .name = "YUV 4:2:2 packed, CrYCbY", - .fourcc = V4L2_PIX_FMT_VYUY, - .depth = 16, - .color = S5P_FIMC_CRYCBY422, - .buff_cnt = 1, - .planes_cnt = 1, - .mbus_code = V4L2_MBUS_FMT_VYUY8_2X8, - .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + .name = "YUV 4:2:2 planar, Y/Cb/Cr", + .fourcc = V4L2_PIX_FMT_YUV422P, + .depth = { 12 }, + .color = S5P_FIMC_YCBYCR422, + .memplanes = 1, + .colplanes = 3, + .flags = FMT_FLAGS_M2M, }, { - .name = "YUV 4:2:2 packed, YCrYCb", - .fourcc = V4L2_PIX_FMT_YVYU, - .depth = 16, - .color = S5P_FIMC_YCRYCB422, - .buff_cnt = 1, - .planes_cnt = 1, - .mbus_code = V4L2_MBUS_FMT_YVYU8_2X8, - .flags = FMT_FLAGS_M2M | FMT_FLAGS_CAM, + .name = "YUV 4:2:2 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV16, + .depth = { 16 }, + .color = S5P_FIMC_YCBYCR422, + .memplanes = 1, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, }, { - .name = "YUV 4:2:2 planar, Y/Cb/Cr", - .fourcc = V4L2_PIX_FMT_YUV422P, - .depth = 12, - .color = S5P_FIMC_YCBCR422, - .buff_cnt = 1, - .planes_cnt = 3, - .flags = FMT_FLAGS_M2M, + .name = "YUV 4:2:2 planar, Y/CrCb", + .fourcc = V4L2_PIX_FMT_NV61, + .depth = { 16 }, + .color = S5P_FIMC_YCRYCB422, + .memplanes = 1, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, }, { - .name = "YUV 4:2:2 planar, Y/CbCr", - .fourcc = V4L2_PIX_FMT_NV16, - .depth = 16, - .color = S5P_FIMC_YCBCR422, - .buff_cnt = 1, - .planes_cnt = 2, - .flags = FMT_FLAGS_M2M, + .name = "YUV 4:2:0 planar, YCbCr", + .fourcc = V4L2_PIX_FMT_YUV420, + .depth = { 12 }, + .color = S5P_FIMC_YCBCR420, + .memplanes = 1, + .colplanes = 3, + .flags = FMT_FLAGS_M2M, }, { - .name = "YUV 4:2:2 planar, Y/CrCb", - .fourcc = V4L2_PIX_FMT_NV61, - .depth = 16, - .color = S5P_FIMC_RGB565, - .buff_cnt = 1, - .planes_cnt = 2, - .flags = FMT_FLAGS_M2M, + .name = "YUV 4:2:0 planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12, + .depth = { 12 }, + .color = S5P_FIMC_YCBCR420, + .memplanes = 1, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, }, { - .name = "YUV 4:2:0 planar, YCbCr", - .fourcc = V4L2_PIX_FMT_YUV420, - .depth = 12, - .color = S5P_FIMC_YCBCR420, - .buff_cnt = 1, - .planes_cnt = 3, - .flags = FMT_FLAGS_M2M, + .name = "YUV 4:2:0 non-contiguous 2-planar, Y/CbCr", + .fourcc = V4L2_PIX_FMT_NV12M, + .color = S5P_FIMC_YCBCR420, + .depth = { 8, 4 }, + .memplanes = 2, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, }, { - .name = "YUV 4:2:0 planar, Y/CbCr", - .fourcc = V4L2_PIX_FMT_NV12, - .depth = 12, - .color = S5P_FIMC_YCBCR420, - .buff_cnt = 1, - .planes_cnt = 2, - .flags = FMT_FLAGS_M2M, + .name = "YUV 4:2:0 non-contiguous 3-planar, Y/Cb/Cr", + .fourcc = V4L2_PIX_FMT_YUV420M, + .color = S5P_FIMC_YCBCR420, + .depth = { 8, 2, 2 }, + .memplanes = 3, + .colplanes = 3, + .flags = FMT_FLAGS_M2M, + }, { + .name = "YUV 4:2:0 non-contiguous 2-planar, Y/CbCr, tiled", + .fourcc = V4L2_PIX_FMT_NV12MT, + .color = S5P_FIMC_YCBCR420, + .depth = { 8, 4 }, + .memplanes = 2, + .colplanes = 2, + .flags = FMT_FLAGS_M2M, }, }; @@ -173,24 +200,21 @@ static struct v4l2_queryctrl *get_ctrl(int id) return NULL; } -int fimc_check_scaler_ratio(struct v4l2_rect *r, struct fimc_frame *f) +int fimc_check_scaler_ratio(int sw, int sh, int dw, int dh, int rot) { - if (r->width > f->width) { - if (f->width > (r->width * SCALER_MAX_HRATIO)) - return -EINVAL; - } else { - if ((f->width * SCALER_MAX_HRATIO) < r->width) - return -EINVAL; - } + int tx, ty; - if (r->height > f->height) { - if (f->height > (r->height * SCALER_MAX_VRATIO)) - return -EINVAL; + if (rot == 90 || rot == 270) { + ty = dw; + tx = dh; } else { - if ((f->height * SCALER_MAX_VRATIO) < r->height) - return -EINVAL; + tx = dw; + ty = dh; } + if ((sw >= SCALER_MAX_HRATIO * tx) || (sh >= SCALER_MAX_VRATIO * ty)) + return -EINVAL; + return 0; } @@ -221,6 +245,7 @@ int fimc_set_scaler_info(struct fimc_ctx *ctx) struct fimc_scaler *sc = &ctx->scaler; struct fimc_frame *s_frame = &ctx->s_frame; struct fimc_frame *d_frame = &ctx->d_frame; + struct samsung_fimc_variant *variant = ctx->fimc_dev->variant; int tx, ty, sx, sy; int ret; @@ -259,8 +284,14 @@ int fimc_set_scaler_info(struct fimc_ctx *ctx) sc->pre_dst_width = sx / sc->pre_hratio; sc->pre_dst_height = sy / sc->pre_vratio; - sc->main_hratio = (sx << 8) / (tx << sc->hfactor); - sc->main_vratio = (sy << 8) / (ty << sc->vfactor); + if (variant->has_mainscaler_ext) { + sc->main_hratio = (sx << 14) / (tx << sc->hfactor); + sc->main_vratio = (sy << 14) / (ty << sc->vfactor); + } else { + sc->main_hratio = (sx << 8) / (tx << sc->hfactor); + sc->main_vratio = (sy << 8) / (ty << sc->vfactor); + + } sc->scaleup_h = (tx >= sx) ? 1 : 0; sc->scaleup_v = (ty >= sy) ? 1 : 0; @@ -276,6 +307,23 @@ int fimc_set_scaler_info(struct fimc_ctx *ctx) return 0; } +static int stop_streaming(struct vb2_queue *q) +{ + struct fimc_ctx *ctx = q->drv_priv; + struct fimc_dev *fimc = ctx->fimc_dev; + + if (!fimc_m2m_pending(fimc)) + return 0; + + set_bit(ST_M2M_SHUT, &fimc->state); + + wait_event_timeout(fimc->irq_queue, + !test_bit(ST_M2M_SHUT, &fimc->state), + FIMC_SHUTDOWN_TIMEOUT); + + return 0; +} + static void fimc_capture_handler(struct fimc_dev *fimc) { struct fimc_vid_cap *cap = &fimc->vid_cap; @@ -283,7 +331,7 @@ static void fimc_capture_handler(struct fimc_dev *fimc) if (!list_empty(&cap->active_buf_q)) { v_buf = active_queue_pop(cap); - fimc_buf_finish(fimc, v_buf); + vb2_buffer_done(&v_buf->vb, VB2_BUF_STATE_DONE); } if (test_and_clear_bit(ST_CAPT_SHUT, &fimc->state)) { @@ -300,10 +348,6 @@ static void fimc_capture_handler(struct fimc_dev *fimc) dbg("hw ptr: %d, sw ptr: %d", fimc_hw_get_frame_index(fimc), cap->buf_index); - spin_lock(&fimc->irqlock); - v_buf->vb.state = VIDEOBUF_ACTIVE; - spin_unlock(&fimc->irqlock); - /* Move the buffer to the capture active queue */ active_queue_add(cap, v_buf); @@ -324,8 +368,6 @@ static void fimc_capture_handler(struct fimc_dev *fimc) static irqreturn_t fimc_isr(int irq, void *priv) { - struct fimc_vid_buffer *src_buf, *dst_buf; - struct fimc_ctx *ctx; struct fimc_dev *fimc = priv; BUG_ON(!fimc); @@ -333,18 +375,21 @@ static irqreturn_t fimc_isr(int irq, void *priv) spin_lock(&fimc->slock); - if (test_and_clear_bit(ST_M2M_PEND, &fimc->state)) { - ctx = v4l2_m2m_get_curr_priv(fimc->m2m.m2m_dev); + if (test_and_clear_bit(ST_M2M_SHUT, &fimc->state)) { + wake_up(&fimc->irq_queue); + goto isr_unlock; + } else if (test_and_clear_bit(ST_M2M_PEND, &fimc->state)) { + struct vb2_buffer *src_vb, *dst_vb; + struct fimc_ctx *ctx = v4l2_m2m_get_curr_priv(fimc->m2m.m2m_dev); + if (!ctx || !ctx->m2m_ctx) goto isr_unlock; - src_buf = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); - dst_buf = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); - if (src_buf && dst_buf) { - spin_lock(&fimc->irqlock); - src_buf->vb.state = dst_buf->vb.state = VIDEOBUF_DONE; - wake_up(&src_buf->vb.done); - wake_up(&dst_buf->vb.done); - spin_unlock(&fimc->irqlock); + + src_vb = v4l2_m2m_src_buf_remove(ctx->m2m_ctx); + dst_vb = v4l2_m2m_dst_buf_remove(ctx->m2m_ctx); + if (src_vb && dst_vb) { + v4l2_m2m_buf_done(src_vb, VB2_BUF_STATE_DONE); + v4l2_m2m_buf_done(dst_vb, VB2_BUF_STATE_DONE); v4l2_m2m_job_finish(fimc->m2m.m2m_dev, ctx->m2m_ctx); } goto isr_unlock; @@ -364,25 +409,25 @@ isr_unlock: return IRQ_HANDLED; } -/* The color format (planes_cnt, buff_cnt) must be already configured. */ -int fimc_prepare_addr(struct fimc_ctx *ctx, struct fimc_vid_buffer *buf, +/* The color format (colplanes, memplanes) must be already configured. */ +int fimc_prepare_addr(struct fimc_ctx *ctx, struct vb2_buffer *vb, struct fimc_frame *frame, struct fimc_addr *paddr) { int ret = 0; u32 pix_size; - if (buf == NULL || frame == NULL) + if (vb == NULL || frame == NULL) return -EINVAL; pix_size = frame->width * frame->height; - dbg("buff_cnt= %d, planes_cnt= %d, frame->size= %d, pix_size= %d", - frame->fmt->buff_cnt, frame->fmt->planes_cnt, - frame->size, pix_size); + dbg("memplanes= %d, colplanes= %d, pix_size= %d", + frame->fmt->memplanes, frame->fmt->colplanes, pix_size); - if (frame->fmt->buff_cnt == 1) { - paddr->y = videobuf_to_dma_contig(&buf->vb); - switch (frame->fmt->planes_cnt) { + paddr->y = vb2_dma_contig_plane_paddr(vb, 0); + + if (frame->fmt->memplanes == 1) { + switch (frame->fmt->colplanes) { case 1: paddr->cb = 0; paddr->cr = 0; @@ -405,6 +450,12 @@ int fimc_prepare_addr(struct fimc_ctx *ctx, struct fimc_vid_buffer *buf, default: return -EINVAL; } + } else { + if (frame->fmt->memplanes >= 2) + paddr->cb = vb2_dma_contig_plane_paddr(vb, 1); + + if (frame->fmt->memplanes == 3) + paddr->cr = vb2_dma_contig_plane_paddr(vb, 2); } dbg("PHYS_ADDR: y= 0x%X cb= 0x%X cr= 0x%X ret= %d", @@ -423,34 +474,34 @@ static void fimc_set_yuv_order(struct fimc_ctx *ctx) /* Set order for 1 plane input formats. */ switch (ctx->s_frame.fmt->color) { case S5P_FIMC_YCRYCB422: - ctx->in_order_1p = S5P_FIMC_IN_YCRYCB; + ctx->in_order_1p = S5P_MSCTRL_ORDER422_CBYCRY; break; case S5P_FIMC_CBYCRY422: - ctx->in_order_1p = S5P_FIMC_IN_CBYCRY; + ctx->in_order_1p = S5P_MSCTRL_ORDER422_YCRYCB; break; case S5P_FIMC_CRYCBY422: - ctx->in_order_1p = S5P_FIMC_IN_CRYCBY; + ctx->in_order_1p = S5P_MSCTRL_ORDER422_YCBYCR; break; case S5P_FIMC_YCBYCR422: default: - ctx->in_order_1p = S5P_FIMC_IN_YCBYCR; + ctx->in_order_1p = S5P_MSCTRL_ORDER422_CRYCBY; break; } dbg("ctx->in_order_1p= %d", ctx->in_order_1p); switch (ctx->d_frame.fmt->color) { case S5P_FIMC_YCRYCB422: - ctx->out_order_1p = S5P_FIMC_OUT_YCRYCB; + ctx->out_order_1p = S5P_CIOCTRL_ORDER422_CBYCRY; break; case S5P_FIMC_CBYCRY422: - ctx->out_order_1p = S5P_FIMC_OUT_CBYCRY; + ctx->out_order_1p = S5P_CIOCTRL_ORDER422_YCRYCB; break; case S5P_FIMC_CRYCBY422: - ctx->out_order_1p = S5P_FIMC_OUT_CRYCBY; + ctx->out_order_1p = S5P_CIOCTRL_ORDER422_YCBYCR; break; case S5P_FIMC_YCBYCR422: default: - ctx->out_order_1p = S5P_FIMC_OUT_YCBYCR; + ctx->out_order_1p = S5P_CIOCTRL_ORDER422_CRYCBY; break; } dbg("ctx->out_order_1p= %d", ctx->out_order_1p); @@ -459,10 +510,14 @@ static void fimc_set_yuv_order(struct fimc_ctx *ctx) static void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) { struct samsung_fimc_variant *variant = ctx->fimc_dev->variant; + u32 i, depth = 0; + + for (i = 0; i < f->fmt->colplanes; i++) + depth += f->fmt->depth[i]; f->dma_offset.y_h = f->offs_h; if (!variant->pix_hoff) - f->dma_offset.y_h *= (f->fmt->depth >> 3); + f->dma_offset.y_h *= (depth >> 3); f->dma_offset.y_v = f->offs_v; @@ -473,7 +528,7 @@ static void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) f->dma_offset.cr_v = f->offs_v; if (!variant->pix_hoff) { - if (f->fmt->planes_cnt == 3) { + if (f->fmt->colplanes == 3) { f->dma_offset.cb_h >>= 1; f->dma_offset.cr_h >>= 1; } @@ -499,7 +554,7 @@ static void fimc_prepare_dma_offset(struct fimc_ctx *ctx, struct fimc_frame *f) int fimc_prepare_config(struct fimc_ctx *ctx, u32 flags) { struct fimc_frame *s_frame, *d_frame; - struct fimc_vid_buffer *buf = NULL; + struct vb2_buffer *vb = NULL; int ret = 0; s_frame = &ctx->s_frame; @@ -522,15 +577,15 @@ int fimc_prepare_config(struct fimc_ctx *ctx, u32 flags) ctx->scaler.enabled = 1; if (flags & FIMC_SRC_ADDR) { - buf = v4l2_m2m_next_src_buf(ctx->m2m_ctx); - ret = fimc_prepare_addr(ctx, buf, s_frame, &s_frame->paddr); + vb = v4l2_m2m_next_src_buf(ctx->m2m_ctx); + ret = fimc_prepare_addr(ctx, vb, s_frame, &s_frame->paddr); if (ret) return ret; } if (flags & FIMC_DST_ADDR) { - buf = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); - ret = fimc_prepare_addr(ctx, buf, d_frame, &d_frame->paddr); + vb = v4l2_m2m_next_dst_buf(ctx->m2m_ctx); + ret = fimc_prepare_addr(ctx, vb, d_frame, &d_frame->paddr); } return ret; @@ -572,7 +627,9 @@ static void fimc_dma_run(void *priv) err("Scaler setup error"); goto dma_unlock; } - fimc_hw_set_scaler(ctx); + + fimc_hw_set_prescaler(ctx); + fimc_hw_set_mainscaler(ctx); fimc_hw_set_target_format(ctx); fimc_hw_set_rotation(ctx); fimc_hw_set_effect(ctx); @@ -587,7 +644,8 @@ static void fimc_dma_run(void *priv) fimc_activate_capture(ctx); - ctx->state &= (FIMC_CTX_M2M | FIMC_CTX_CAP); + ctx->state &= (FIMC_CTX_M2M | FIMC_CTX_CAP | + FIMC_SRC_FMT | FIMC_DST_FMT); fimc_hw_activate_input_dma(fimc, true); dma_unlock: @@ -596,109 +654,94 @@ dma_unlock: static void fimc_job_abort(void *priv) { - /* Nothing done in job_abort. */ -} + struct fimc_ctx *ctx = priv; + struct fimc_dev *fimc = ctx->fimc_dev; -static void fimc_buf_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) -{ - videobuf_dma_contig_free(vq, vb); - vb->state = VIDEOBUF_NEEDS_INIT; + if (!fimc_m2m_pending(fimc)) + return; + + set_bit(ST_M2M_SHUT, &fimc->state); + + wait_event_timeout(fimc->irq_queue, + !test_bit(ST_M2M_SHUT, &fimc->state), + FIMC_SHUTDOWN_TIMEOUT); } -static int fimc_buf_setup(struct videobuf_queue *vq, unsigned int *count, - unsigned int *size) +static int fimc_queue_setup(struct vb2_queue *vq, unsigned int *num_buffers, + unsigned int *num_planes, unsigned long sizes[], + void *allocators[]) { - struct fimc_ctx *ctx = vq->priv_data; - struct fimc_frame *frame; + struct fimc_ctx *ctx = vb2_get_drv_priv(vq); + struct fimc_frame *f; + int i; - frame = ctx_get_frame(ctx, vq->type); - if (IS_ERR(frame)) - return PTR_ERR(frame); + f = ctx_get_frame(ctx, vq->type); + if (IS_ERR(f)) + return PTR_ERR(f); + + /* + * Return number of non-contigous planes (plane buffers) + * depending on the configured color format. + */ + if (f->fmt) + *num_planes = f->fmt->memplanes; + + for (i = 0; i < f->fmt->memplanes; i++) { + sizes[i] = (f->width * f->height * f->fmt->depth[i]) >> 3; + allocators[i] = ctx->fimc_dev->alloc_ctx; + } + + if (*num_buffers == 0) + *num_buffers = 1; - *size = (frame->width * frame->height * frame->fmt->depth) >> 3; - if (0 == *count) - *count = 1; return 0; } -static int fimc_buf_prepare(struct videobuf_queue *vq, - struct videobuf_buffer *vb, enum v4l2_field field) +static int fimc_buf_prepare(struct vb2_buffer *vb) { - struct fimc_ctx *ctx = vq->priv_data; - struct v4l2_device *v4l2_dev = &ctx->fimc_dev->m2m.v4l2_dev; + struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); struct fimc_frame *frame; - int ret; + int i; - frame = ctx_get_frame(ctx, vq->type); + frame = ctx_get_frame(ctx, vb->vb2_queue->type); if (IS_ERR(frame)) return PTR_ERR(frame); - if (vb->baddr) { - if (vb->bsize < frame->size) { - v4l2_err(v4l2_dev, - "User-provided buffer too small (%d < %d)\n", - vb->bsize, frame->size); - WARN_ON(1); - return -EINVAL; - } - } else if (vb->state != VIDEOBUF_NEEDS_INIT - && vb->bsize < frame->size) { - return -EINVAL; - } - - vb->width = frame->width; - vb->height = frame->height; - vb->bytesperline = (frame->width * frame->fmt->depth) >> 3; - vb->size = frame->size; - vb->field = field; - - if (VIDEOBUF_NEEDS_INIT == vb->state) { - ret = videobuf_iolock(vq, vb, NULL); - if (ret) { - v4l2_err(v4l2_dev, "Iolock failed\n"); - fimc_buf_release(vq, vb); - return ret; - } - } - vb->state = VIDEOBUF_PREPARED; + for (i = 0; i < frame->fmt->memplanes; i++) + vb2_set_plane_payload(vb, i, frame->payload[i]); return 0; } -static void fimc_buf_queue(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static void fimc_buf_queue(struct vb2_buffer *vb) { - struct fimc_ctx *ctx = vq->priv_data; - struct fimc_dev *fimc = ctx->fimc_dev; - struct fimc_vid_cap *cap = &fimc->vid_cap; - unsigned long flags; + struct fimc_ctx *ctx = vb2_get_drv_priv(vb->vb2_queue); dbg("ctx: %p, ctx->state: 0x%x", ctx, ctx->state); - if ((ctx->state & FIMC_CTX_M2M) && ctx->m2m_ctx) { - v4l2_m2m_buf_queue(ctx->m2m_ctx, vq, vb); - } else if (ctx->state & FIMC_CTX_CAP) { - spin_lock_irqsave(&fimc->slock, flags); - fimc_vid_cap_buf_queue(fimc, (struct fimc_vid_buffer *)vb); + if (ctx->m2m_ctx) + v4l2_m2m_buf_queue(ctx->m2m_ctx, vb); +} - dbg("fimc->cap.active_buf_cnt: %d", - fimc->vid_cap.active_buf_cnt); +static void fimc_lock(struct vb2_queue *vq) +{ + struct fimc_ctx *ctx = vb2_get_drv_priv(vq); + mutex_lock(&ctx->fimc_dev->lock); +} - if (cap->active_buf_cnt >= cap->reqbufs_count || - cap->active_buf_cnt >= FIMC_MAX_OUT_BUFS) { - if (!test_and_set_bit(ST_CAPT_STREAM, &fimc->state)) - fimc_activate_capture(ctx); - } - spin_unlock_irqrestore(&fimc->slock, flags); - } +static void fimc_unlock(struct vb2_queue *vq) +{ + struct fimc_ctx *ctx = vb2_get_drv_priv(vq); + mutex_unlock(&ctx->fimc_dev->lock); } -struct videobuf_queue_ops fimc_qops = { - .buf_setup = fimc_buf_setup, - .buf_prepare = fimc_buf_prepare, - .buf_queue = fimc_buf_queue, - .buf_release = fimc_buf_release, +struct vb2_ops fimc_qops = { + .queue_setup = fimc_queue_setup, + .buf_prepare = fimc_buf_prepare, + .buf_queue = fimc_buf_queue, + .wait_prepare = fimc_unlock, + .wait_finish = fimc_lock, + .stop_streaming = stop_streaming, }; static int fimc_m2m_querycap(struct file *file, void *priv, @@ -712,12 +755,13 @@ static int fimc_m2m_querycap(struct file *file, void *priv, cap->bus_info[0] = 0; cap->version = KERNEL_VERSION(1, 0, 0); cap->capabilities = V4L2_CAP_STREAMING | - V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT; + V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_VIDEO_OUTPUT | + V4L2_CAP_VIDEO_CAPTURE_MPLANE | V4L2_CAP_VIDEO_OUTPUT_MPLANE; return 0; } -int fimc_vidioc_enum_fmt(struct file *file, void *priv, +int fimc_vidioc_enum_fmt_mplane(struct file *file, void *priv, struct v4l2_fmtdesc *f) { struct fimc_fmt *fmt; @@ -732,25 +776,21 @@ int fimc_vidioc_enum_fmt(struct file *file, void *priv, return 0; } -int fimc_vidioc_g_fmt(struct file *file, void *priv, struct v4l2_format *f) +int fimc_vidioc_g_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) { struct fimc_ctx *ctx = priv; - struct fimc_dev *fimc = ctx->fimc_dev; struct fimc_frame *frame; frame = ctx_get_frame(ctx, f->type); if (IS_ERR(frame)) return PTR_ERR(frame); - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - f->fmt.pix.width = frame->width; f->fmt.pix.height = frame->height; f->fmt.pix.field = V4L2_FIELD_NONE; f->fmt.pix.pixelformat = frame->fmt->fourcc; - mutex_unlock(&fimc->lock); return 0; } @@ -785,42 +825,40 @@ struct fimc_fmt *find_mbus_format(struct v4l2_mbus_framefmt *f, } -int fimc_vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) +int fimc_vidioc_try_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) { struct fimc_ctx *ctx = priv; struct fimc_dev *fimc = ctx->fimc_dev; struct samsung_fimc_variant *variant = fimc->variant; - struct v4l2_pix_format *pix = &f->fmt.pix; + struct v4l2_pix_format_mplane *pix = &f->fmt.pix_mp; struct fimc_fmt *fmt; u32 max_width, mod_x, mod_y, mask; - int ret = -EINVAL, is_output = 0; + int i, is_output = 0; - if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { if (ctx->state & FIMC_CTX_CAP) return -EINVAL; is_output = 1; - } else if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) { + } else if (f->type != V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { return -EINVAL; } - dbg("w: %d, h: %d, bpl: %d", - pix->width, pix->height, pix->bytesperline); - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; + dbg("w: %d, h: %d", pix->width, pix->height); mask = is_output ? FMT_FLAGS_M2M : FMT_FLAGS_M2M | FMT_FLAGS_CAM; fmt = find_format(f, mask); if (!fmt) { v4l2_err(&fimc->m2m.v4l2_dev, "Fourcc format (0x%X) invalid.\n", pix->pixelformat); - goto tf_out; + return -EINVAL; } if (pix->field == V4L2_FIELD_ANY) pix->field = V4L2_FIELD_NONE; else if (V4L2_FIELD_NONE != pix->field) - goto tf_out; + return -EINVAL; if (is_output) { max_width = variant->pix_limit->scaler_dis_w; @@ -834,7 +872,7 @@ int fimc_vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) mod_x = 6; /* 64 x 32 pixels tile */ mod_y = 5; } else { - if (fimc->id == 1 && fimc->variant->pix_hoff) + if (fimc->id == 1 && variant->pix_hoff) mod_y = fimc_fmt_is_rgb(fmt->color) ? 0 : 1; else mod_y = mod_x; @@ -845,74 +883,73 @@ int fimc_vidioc_try_fmt(struct file *file, void *priv, struct v4l2_format *f) v4l_bound_align_image(&pix->width, 16, max_width, mod_x, &pix->height, 8, variant->pix_limit->scaler_dis_w, mod_y, 0); - if (pix->bytesperline == 0 || - (pix->bytesperline * 8 / fmt->depth) > pix->width) - pix->bytesperline = (pix->width * fmt->depth) >> 3; + pix->num_planes = fmt->memplanes; - if (pix->sizeimage == 0) - pix->sizeimage = pix->height * pix->bytesperline; + for (i = 0; i < pix->num_planes; ++i) { + int bpl = pix->plane_fmt[i].bytesperline; - dbg("w: %d, h: %d, bpl: %d, depth: %d", - pix->width, pix->height, pix->bytesperline, fmt->depth); + dbg("[%d] bpl: %d, depth: %d, w: %d, h: %d", + i, bpl, fmt->depth[i], pix->width, pix->height); - ret = 0; + if (!bpl || (bpl * 8 / fmt->depth[i]) > pix->width) + bpl = (pix->width * fmt->depth[0]) >> 3; -tf_out: - mutex_unlock(&fimc->lock); - return ret; + if (!pix->plane_fmt[i].sizeimage) + pix->plane_fmt[i].sizeimage = pix->height * bpl; + + pix->plane_fmt[i].bytesperline = bpl; + + dbg("[%d]: bpl: %d, sizeimage: %d", + i, pix->plane_fmt[i].bytesperline, + pix->plane_fmt[i].sizeimage); + } + + return 0; } -static int fimc_m2m_s_fmt(struct file *file, void *priv, struct v4l2_format *f) +static int fimc_m2m_s_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f) { struct fimc_ctx *ctx = priv; struct fimc_dev *fimc = ctx->fimc_dev; - struct v4l2_device *v4l2_dev = &fimc->m2m.v4l2_dev; - struct videobuf_queue *vq; + struct vb2_queue *vq; struct fimc_frame *frame; - struct v4l2_pix_format *pix; + struct v4l2_pix_format_mplane *pix; unsigned long flags; - int ret = 0; + int i, ret = 0; + u32 tmp; - ret = fimc_vidioc_try_fmt(file, priv, f); + ret = fimc_vidioc_try_fmt_mplane(file, priv, f); if (ret) return ret; - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - vq = v4l2_m2m_get_vq(ctx->m2m_ctx, f->type); - mutex_lock(&vq->vb_lock); - if (videobuf_queue_is_busy(vq)) { - v4l2_err(v4l2_dev, "%s: queue (%d) busy\n", __func__, f->type); - ret = -EBUSY; - goto sf_out; + if (vb2_is_streaming(vq)) { + v4l2_err(&fimc->m2m.v4l2_dev, "queue (%d) busy\n", f->type); + return -EBUSY; } - spin_lock_irqsave(&ctx->slock, flags); - if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) { + if (f->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { frame = &ctx->s_frame; - ctx->state |= FIMC_SRC_FMT; - } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + } else if (f->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) { frame = &ctx->d_frame; - ctx->state |= FIMC_DST_FMT; } else { - spin_unlock_irqrestore(&ctx->slock, flags); - v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, + v4l2_err(&fimc->m2m.v4l2_dev, "Wrong buffer/video queue type (%d)\n", f->type); - ret = -EINVAL; - goto sf_out; + return -EINVAL; } - spin_unlock_irqrestore(&ctx->slock, flags); - pix = &f->fmt.pix; + pix = &f->fmt.pix_mp; frame->fmt = find_format(f, FMT_FLAGS_M2M); - if (!frame->fmt) { - ret = -EINVAL; - goto sf_out; - } + if (!frame->fmt) + return -EINVAL; + + for (i = 0; i < frame->fmt->colplanes; i++) + frame->payload[i] = pix->plane_fmt[i].bytesperline * pix->height; - frame->f_width = pix->bytesperline * 8 / frame->fmt->depth; + frame->f_width = pix->plane_fmt[0].bytesperline * 8 / + frame->fmt->depth[0]; frame->f_height = pix->height; frame->width = pix->width; frame->height = pix->height; @@ -920,19 +957,15 @@ static int fimc_m2m_s_fmt(struct file *file, void *priv, struct v4l2_format *f) frame->o_height = pix->height; frame->offs_h = 0; frame->offs_v = 0; - frame->size = (pix->width * pix->height * frame->fmt->depth) >> 3; - vq->field = pix->field; spin_lock_irqsave(&ctx->slock, flags); - ctx->state |= FIMC_PARAMS; + tmp = (frame == &ctx->d_frame) ? FIMC_DST_FMT : FIMC_SRC_FMT; + ctx->state |= FIMC_PARAMS | tmp; spin_unlock_irqrestore(&ctx->slock, flags); dbg("f_w: %d, f_h: %d", frame->f_width, frame->f_height); -sf_out: - mutex_unlock(&vq->vb_lock); - mutex_unlock(&fimc->lock); - return ret; + return 0; } static int fimc_m2m_reqbufs(struct file *file, void *priv, @@ -968,6 +1001,15 @@ static int fimc_m2m_streamon(struct file *file, void *priv, enum v4l2_buf_type type) { struct fimc_ctx *ctx = priv; + + /* The source and target color format need to be set */ + if (V4L2_TYPE_IS_OUTPUT(type)) { + if (~ctx->state & FIMC_SRC_FMT) + return -EINVAL; + } else if (~ctx->state & FIMC_DST_FMT) { + return -EINVAL; + } + return v4l2_m2m_streamon(file, ctx->m2m_ctx, type); } @@ -992,11 +1034,8 @@ int fimc_vidioc_queryctrl(struct file *file, void *priv, } if (ctx->state & FIMC_CTX_CAP) { - if (mutex_lock_interruptible(&ctx->fimc_dev->lock)) - return -ERESTARTSYS; - ret = v4l2_subdev_call(ctx->fimc_dev->vid_cap.sd, + return v4l2_subdev_call(ctx->fimc_dev->vid_cap.sd, core, queryctrl, qc); - mutex_unlock(&ctx->fimc_dev->lock); } return ret; } @@ -1006,10 +1045,6 @@ int fimc_vidioc_g_ctrl(struct file *file, void *priv, { struct fimc_ctx *ctx = priv; struct fimc_dev *fimc = ctx->fimc_dev; - int ret = 0; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; switch (ctrl->id) { case V4L2_CID_HFLIP: @@ -1023,18 +1058,17 @@ int fimc_vidioc_g_ctrl(struct file *file, void *priv, break; default: if (ctx->state & FIMC_CTX_CAP) { - ret = v4l2_subdev_call(fimc->vid_cap.sd, core, - g_ctrl, ctrl); + return v4l2_subdev_call(fimc->vid_cap.sd, core, + g_ctrl, ctrl); } else { v4l2_err(&fimc->m2m.v4l2_dev, "Invalid control\n"); - ret = -EINVAL; + return -EINVAL; } } dbg("ctrl->value= %d", ctrl->value); - mutex_unlock(&fimc->lock); - return ret; + return 0; } int check_ctrl_val(struct fimc_ctx *ctx, struct v4l2_control *ctrl) @@ -1059,13 +1093,7 @@ int fimc_s_ctrl(struct fimc_ctx *ctx, struct v4l2_control *ctrl) struct samsung_fimc_variant *variant = ctx->fimc_dev->variant; struct fimc_dev *fimc = ctx->fimc_dev; unsigned long flags; - - if (ctx->rotation != 0 && - (ctrl->id == V4L2_CID_HFLIP || ctrl->id == V4L2_CID_VFLIP)) { - v4l2_err(&fimc->m2m.v4l2_dev, - "Simultaneous flip and rotation is not supported\n"); - return -EINVAL; - } + int ret = 0; spin_lock_irqsave(&ctx->slock, flags); @@ -1085,6 +1113,20 @@ int fimc_s_ctrl(struct fimc_ctx *ctx, struct v4l2_control *ctrl) break; case V4L2_CID_ROTATE: + if (!(~ctx->state & (FIMC_DST_FMT | FIMC_SRC_FMT))) { + ret = fimc_check_scaler_ratio(ctx->s_frame.width, + ctx->s_frame.height, + ctx->d_frame.width, + ctx->d_frame.height, + ctrl->value); + if (ret) { + v4l2_err(&fimc->m2m.v4l2_dev, + "Out of scaler range"); + spin_unlock_irqrestore(&ctx->slock, flags); + return -EINVAL; + } + } + /* Check for the output rotator availability */ if ((ctrl->value == 90 || ctrl->value == 270) && (ctx->in_path == FIMC_DMA && !variant->has_out_rot)) { @@ -1107,7 +1149,7 @@ int fimc_s_ctrl(struct fimc_ctx *ctx, struct v4l2_control *ctrl) } static int fimc_m2m_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) + struct v4l2_control *ctrl) { struct fimc_ctx *ctx = priv; int ret = 0; @@ -1125,22 +1167,17 @@ static int fimc_m2m_cropcap(struct file *file, void *fh, { struct fimc_frame *frame; struct fimc_ctx *ctx = fh; - struct fimc_dev *fimc = ctx->fimc_dev; frame = ctx_get_frame(ctx, cr->type); if (IS_ERR(frame)) return PTR_ERR(frame); - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - cr->bounds.left = 0; cr->bounds.top = 0; cr->bounds.width = frame->f_width; cr->bounds.height = frame->f_height; cr->defrect = cr->bounds; - mutex_unlock(&fimc->lock); return 0; } @@ -1148,21 +1185,16 @@ static int fimc_m2m_g_crop(struct file *file, void *fh, struct v4l2_crop *cr) { struct fimc_frame *frame; struct fimc_ctx *ctx = file->private_data; - struct fimc_dev *fimc = ctx->fimc_dev; frame = ctx_get_frame(ctx, cr->type); if (IS_ERR(frame)) return PTR_ERR(frame); - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - cr->c.left = frame->offs_h; cr->c.top = frame->offs_v; cr->c.width = frame->width; cr->c.height = frame->height; - mutex_unlock(&fimc->lock); return 0; } @@ -1170,7 +1202,8 @@ int fimc_try_crop(struct fimc_ctx *ctx, struct v4l2_crop *cr) { struct fimc_dev *fimc = ctx->fimc_dev; struct fimc_frame *f; - u32 min_size, halign; + u32 min_size, halign, depth = 0; + int i; if (cr->c.top < 0 || cr->c.left < 0) { v4l2_err(&fimc->m2m.v4l2_dev, @@ -1178,9 +1211,9 @@ int fimc_try_crop(struct fimc_ctx *ctx, struct v4l2_crop *cr) return -EINVAL; } - if (cr->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + if (cr->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) f = (ctx->state & FIMC_CTX_CAP) ? &ctx->s_frame : &ctx->d_frame; - else if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT && + else if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE && ctx->state & FIMC_CTX_M2M) f = &ctx->s_frame; else @@ -1200,10 +1233,13 @@ int fimc_try_crop(struct fimc_ctx *ctx, struct v4l2_crop *cr) halign = 4; } + for (i = 0; i < f->fmt->colplanes; i++) + depth += f->fmt->depth[i]; + v4l_bound_align_image(&cr->c.width, min_size, f->o_width, ffs(min_size) - 1, &cr->c.height, min_size, f->o_height, - halign, 64/(ALIGN(f->fmt->depth, 8))); + halign, 64/(ALIGN(depth, 8))); /* adjust left/top if cropping rectangle is out of bounds */ if (cr->c.left + cr->c.width > f->o_width) @@ -1235,25 +1271,31 @@ static int fimc_m2m_s_crop(struct file *file, void *fh, struct v4l2_crop *cr) if (ret) return ret; - f = (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) ? + f = (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) ? &ctx->s_frame : &ctx->d_frame; - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; - spin_lock_irqsave(&ctx->slock, flags); - if (~ctx->state & (FIMC_SRC_FMT | FIMC_DST_FMT)) { - /* Check to see if scaling ratio is within supported range */ - if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) - ret = fimc_check_scaler_ratio(&cr->c, &ctx->d_frame); - else - ret = fimc_check_scaler_ratio(&cr->c, &ctx->s_frame); + /* Check to see if scaling ratio is within supported range */ + if (!(~ctx->state & (FIMC_DST_FMT | FIMC_SRC_FMT))) { + if (cr->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) { + ret = fimc_check_scaler_ratio(cr->c.width, cr->c.height, + ctx->d_frame.width, + ctx->d_frame.height, + ctx->rotation); + } else { + ret = fimc_check_scaler_ratio(ctx->s_frame.width, + ctx->s_frame.height, + cr->c.width, cr->c.height, + ctx->rotation); + } + if (ret) { v4l2_err(&fimc->m2m.v4l2_dev, "Out of scaler range"); - ret = -EINVAL; - goto scr_unlock; + spin_unlock_irqrestore(&ctx->slock, flags); + return -EINVAL; } } + ctx->state |= FIMC_PARAMS; f->offs_h = cr->c.left; @@ -1261,26 +1303,24 @@ static int fimc_m2m_s_crop(struct file *file, void *fh, struct v4l2_crop *cr) f->width = cr->c.width; f->height = cr->c.height; -scr_unlock: spin_unlock_irqrestore(&ctx->slock, flags); - mutex_unlock(&fimc->lock); return 0; } static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = { .vidioc_querycap = fimc_m2m_querycap, - .vidioc_enum_fmt_vid_cap = fimc_vidioc_enum_fmt, - .vidioc_enum_fmt_vid_out = fimc_vidioc_enum_fmt, + .vidioc_enum_fmt_vid_cap_mplane = fimc_vidioc_enum_fmt_mplane, + .vidioc_enum_fmt_vid_out_mplane = fimc_vidioc_enum_fmt_mplane, - .vidioc_g_fmt_vid_cap = fimc_vidioc_g_fmt, - .vidioc_g_fmt_vid_out = fimc_vidioc_g_fmt, + .vidioc_g_fmt_vid_cap_mplane = fimc_vidioc_g_fmt_mplane, + .vidioc_g_fmt_vid_out_mplane = fimc_vidioc_g_fmt_mplane, - .vidioc_try_fmt_vid_cap = fimc_vidioc_try_fmt, - .vidioc_try_fmt_vid_out = fimc_vidioc_try_fmt, + .vidioc_try_fmt_vid_cap_mplane = fimc_vidioc_try_fmt_mplane, + .vidioc_try_fmt_vid_out_mplane = fimc_vidioc_try_fmt_mplane, - .vidioc_s_fmt_vid_cap = fimc_m2m_s_fmt, - .vidioc_s_fmt_vid_out = fimc_m2m_s_fmt, + .vidioc_s_fmt_vid_cap_mplane = fimc_m2m_s_fmt_mplane, + .vidioc_s_fmt_vid_out_mplane = fimc_m2m_s_fmt_mplane, .vidioc_reqbufs = fimc_m2m_reqbufs, .vidioc_querybuf = fimc_m2m_querybuf, @@ -1301,26 +1341,39 @@ static const struct v4l2_ioctl_ops fimc_m2m_ioctl_ops = { }; -static void queue_init(void *priv, struct videobuf_queue *vq, - enum v4l2_buf_type type) +static int queue_init(void *priv, struct vb2_queue *src_vq, + struct vb2_queue *dst_vq) { struct fimc_ctx *ctx = priv; - struct fimc_dev *fimc = ctx->fimc_dev; + int ret; + + memset(src_vq, 0, sizeof(*src_vq)); + src_vq->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + src_vq->io_modes = VB2_MMAP | VB2_USERPTR; + src_vq->drv_priv = ctx; + src_vq->ops = &fimc_qops; + src_vq->mem_ops = &vb2_dma_contig_memops; + src_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); - videobuf_queue_dma_contig_init(vq, &fimc_qops, - &fimc->pdev->dev, - &fimc->irqlock, type, V4L2_FIELD_NONE, - sizeof(struct fimc_vid_buffer), priv, NULL); + ret = vb2_queue_init(src_vq); + if (ret) + return ret; + + memset(dst_vq, 0, sizeof(*dst_vq)); + dst_vq->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + dst_vq->io_modes = VB2_MMAP | VB2_USERPTR; + dst_vq->drv_priv = ctx; + dst_vq->ops = &fimc_qops; + dst_vq->mem_ops = &vb2_dma_contig_memops; + dst_vq->buf_struct_size = sizeof(struct v4l2_m2m_buffer); + + return vb2_queue_init(dst_vq); } static int fimc_m2m_open(struct file *file) { struct fimc_dev *fimc = video_drvdata(file); struct fimc_ctx *ctx = NULL; - int err = 0; - - if (mutex_lock_interruptible(&fimc->lock)) - return -ERESTARTSYS; dbg("pid: %d, state: 0x%lx, refcnt: %d", task_pid_nr(current), fimc->state, fimc->vid_cap.refcnt); @@ -1329,19 +1382,15 @@ static int fimc_m2m_open(struct file *file) * Return if the corresponding video capture node * is already opened. */ - if (fimc->vid_cap.refcnt > 0) { - err = -EBUSY; - goto err_unlock; - } + if (fimc->vid_cap.refcnt > 0) + return -EBUSY; fimc->m2m.refcnt++; set_bit(ST_OUTDMA_RUN, &fimc->state); ctx = kzalloc(sizeof *ctx, GFP_KERNEL); - if (!ctx) { - err = -ENOMEM; - goto err_unlock; - } + if (!ctx) + return -ENOMEM; file->private_data = ctx; ctx->fimc_dev = fimc; @@ -1355,15 +1404,14 @@ static int fimc_m2m_open(struct file *file) ctx->out_path = FIMC_DMA; spin_lock_init(&ctx->slock); - ctx->m2m_ctx = v4l2_m2m_ctx_init(ctx, fimc->m2m.m2m_dev, queue_init); + ctx->m2m_ctx = v4l2_m2m_ctx_init(fimc->m2m.m2m_dev, ctx, queue_init); if (IS_ERR(ctx->m2m_ctx)) { - err = PTR_ERR(ctx->m2m_ctx); + int err = PTR_ERR(ctx->m2m_ctx); kfree(ctx); + return err; } -err_unlock: - mutex_unlock(&fimc->lock); - return err; + return 0; } static int fimc_m2m_release(struct file *file) @@ -1371,8 +1419,6 @@ static int fimc_m2m_release(struct file *file) struct fimc_ctx *ctx = file->private_data; struct fimc_dev *fimc = ctx->fimc_dev; - mutex_lock(&fimc->lock); - dbg("pid: %d, state: 0x%lx, refcnt= %d", task_pid_nr(current), fimc->state, fimc->m2m.refcnt); @@ -1381,7 +1427,6 @@ static int fimc_m2m_release(struct file *file) if (--fimc->m2m.refcnt <= 0) clear_bit(ST_OUTDMA_RUN, &fimc->state); - mutex_unlock(&fimc->lock); return 0; } @@ -1415,7 +1460,6 @@ static struct v4l2_m2m_ops m2m_ops = { .job_abort = fimc_job_abort, }; - static int fimc_register_m2m_device(struct fimc_dev *fimc) { struct video_device *vfd; @@ -1448,6 +1492,7 @@ static int fimc_register_m2m_device(struct fimc_dev *fimc) vfd->ioctl_ops = &fimc_m2m_ioctl_ops; vfd->minor = -1; vfd->release = video_device_release; + vfd->lock = &fimc->lock; snprintf(vfd->name, sizeof(vfd->name), "%s:m2m", dev_name(&pdev->dev)); @@ -1496,7 +1541,7 @@ static void fimc_unregister_m2m_device(struct fimc_dev *fimc) static void fimc_clk_release(struct fimc_dev *fimc) { int i; - for (i = 0; i < NUM_FIMC_CLOCKS; i++) { + for (i = 0; i < fimc->num_clocks; i++) { if (fimc->clock[i]) { clk_disable(fimc->clock[i]); clk_put(fimc->clock[i]); @@ -1507,15 +1552,16 @@ static void fimc_clk_release(struct fimc_dev *fimc) static int fimc_clk_get(struct fimc_dev *fimc) { int i; - for (i = 0; i < NUM_FIMC_CLOCKS; i++) { - fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clock_name[i]); - if (IS_ERR(fimc->clock[i])) { - dev_err(&fimc->pdev->dev, - "failed to get fimc clock: %s\n", - fimc_clock_name[i]); - return -ENXIO; + for (i = 0; i < fimc->num_clocks; i++) { + fimc->clock[i] = clk_get(&fimc->pdev->dev, fimc_clocks[i]); + + if (!IS_ERR_OR_NULL(fimc->clock[i])) { + clk_enable(fimc->clock[i]); + continue; } - clk_enable(fimc->clock[i]); + dev_err(&fimc->pdev->dev, "failed to get fimc clock: %s\n", + fimc_clocks[i]); + return -ENXIO; } return 0; } @@ -1526,6 +1572,7 @@ static int fimc_probe(struct platform_device *pdev) struct resource *res; struct samsung_fimc_driverdata *drv_data; int ret = 0; + int cap_input_index = -1; dev_dbg(&pdev->dev, "%s():\n", __func__); @@ -1548,7 +1595,6 @@ static int fimc_probe(struct platform_device *pdev) fimc->pdata = pdev->dev.platform_data; fimc->state = ST_IDLE; - spin_lock_init(&fimc->irqlock); init_waitqueue_head(&fimc->irq_queue); spin_lock_init(&fimc->slock); @@ -1576,10 +1622,26 @@ static int fimc_probe(struct platform_device *pdev) goto err_req_region; } + fimc->num_clocks = MAX_FIMC_CLOCKS - 1; + /* + * Check if vide capture node needs to be registered for this device + * instance. + */ + if (fimc->pdata) { + int i; + for (i = 0; i < FIMC_MAX_CAMIF_CLIENTS; ++i) + if (fimc->pdata->isp_info[i]) + break; + if (i < FIMC_MAX_CAMIF_CLIENTS) { + cap_input_index = i; + fimc->num_clocks++; + } + } + ret = fimc_clk_get(fimc); if (ret) goto err_regs_unmap; - clk_set_rate(fimc->clock[0], drv_data->lclk_frequency); + clk_set_rate(fimc->clock[CLK_BUS], drv_data->lclk_frequency); res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); if (!res) { @@ -1597,24 +1659,24 @@ static int fimc_probe(struct platform_device *pdev) goto err_clk; } + /* Initialize contiguous memory allocator */ + fimc->alloc_ctx = vb2_dma_contig_init_ctx(&fimc->pdev->dev); + if (IS_ERR(fimc->alloc_ctx)) { + ret = PTR_ERR(fimc->alloc_ctx); + goto err_irq; + } + ret = fimc_register_m2m_device(fimc); if (ret) goto err_irq; /* At least one camera sensor is required to register capture node */ - if (fimc->pdata) { - int i; - for (i = 0; i < FIMC_MAX_CAMIF_CLIENTS; ++i) - if (fimc->pdata->isp_info[i]) - break; - - if (i < FIMC_MAX_CAMIF_CLIENTS) { - ret = fimc_register_capture_device(fimc); - if (ret) - goto err_m2m; - } + if (cap_input_index >= 0) { + ret = fimc_register_capture_device(fimc); + if (ret) + goto err_m2m; + clk_disable(fimc->clock[CLK_CAM]); } - /* * Exclude the additional output DMA address registers by masking * them out on HW revisions that provide extended capabilites. @@ -1656,6 +1718,9 @@ static int __devexit fimc_remove(struct platform_device *pdev) fimc_unregister_capture_device(fimc); fimc_clk_release(fimc); + + vb2_dma_contig_cleanup_ctx(fimc->alloc_ctx); + iounmap(fimc->regs); release_resource(fimc->regs_res); kfree(fimc->regs_res); @@ -1726,6 +1791,7 @@ static struct samsung_fimc_variant fimc1_variant_s5pv210 = { .pix_hoff = 1, .has_inp_rot = 1, .has_out_rot = 1, + .has_mainscaler_ext = 1, .min_inp_pixsize = 16, .min_out_pixsize = 16, .hor_offs_align = 1, @@ -1747,6 +1813,7 @@ static struct samsung_fimc_variant fimc0_variant_s5pv310 = { .has_inp_rot = 1, .has_out_rot = 1, .has_cistatus2 = 1, + .has_mainscaler_ext = 1, .min_inp_pixsize = 16, .min_out_pixsize = 16, .hor_offs_align = 1, @@ -1757,6 +1824,7 @@ static struct samsung_fimc_variant fimc0_variant_s5pv310 = { static struct samsung_fimc_variant fimc2_variant_s5pv310 = { .pix_hoff = 1, .has_cistatus2 = 1, + .has_mainscaler_ext = 1, .min_inp_pixsize = 16, .min_out_pixsize = 16, .hor_offs_align = 1, diff --git a/drivers/media/video/s5p-fimc/fimc-core.h b/drivers/media/video/s5p-fimc/fimc-core.h index 4f047d35f8ad..4829a2515076 100644 --- a/drivers/media/video/s5p-fimc/fimc-core.h +++ b/drivers/media/video/s5p-fimc/fimc-core.h @@ -16,11 +16,12 @@ #include <linux/sched.h> #include <linux/types.h> #include <linux/videodev2.h> -#include <media/videobuf-core.h> +#include <linux/io.h> +#include <media/videobuf2-core.h> #include <media/v4l2-device.h> #include <media/v4l2-mem2mem.h> #include <media/v4l2-mediabus.h> -#include <media/s3c_fimc.h> +#include <media/s5p_fimc.h> #include "regs-fimc.h" @@ -36,7 +37,7 @@ /* Time to wait for next frame VSYNC interrupt while stopping operation. */ #define FIMC_SHUTDOWN_TIMEOUT ((100*HZ)/1000) -#define NUM_FIMC_CLOCKS 2 +#define MAX_FIMC_CLOCKS 3 #define MODULE_NAME "s5p-fimc" #define FIMC_MAX_DEVS 4 #define FIMC_MAX_OUT_BUFS 4 @@ -44,12 +45,19 @@ #define SCALER_MAX_VRATIO 64 #define DMA_MIN_SIZE 8 -/* FIMC device state flags */ +/* indices to the clocks array */ +enum { + CLK_BUS, + CLK_GATE, + CLK_CAM, +}; + enum fimc_dev_flags { /* for m2m node */ ST_IDLE, ST_OUTDMA_RUN, ST_M2M_PEND, + ST_M2M_SHUT, /* for capture node */ ST_CAPT_PEND, ST_CAPT_RUN, @@ -70,13 +78,6 @@ enum fimc_dev_flags { #define fimc_capture_streaming(dev) \ test_bit(ST_CAPT_STREAM, &(dev)->state) -#define fimc_buf_finish(dev, vid_buf) do { \ - spin_lock(&(dev)->irqlock); \ - (vid_buf)->vb.state = VIDEOBUF_DONE; \ - spin_unlock(&(dev)->irqlock); \ - wake_up(&(vid_buf)->vb.done); \ -} while (0) - enum fimc_datapath { FIMC_CAMERA, FIMC_DMA, @@ -90,7 +91,6 @@ enum fimc_color_fmt { S5P_FIMC_RGB888, S5P_FIMC_RGB30_LOCAL, S5P_FIMC_YCBCR420 = 0x20, - S5P_FIMC_YCBCR422, S5P_FIMC_YCBYCR422, S5P_FIMC_YCRYCB422, S5P_FIMC_CBYCRY422, @@ -100,18 +100,6 @@ enum fimc_color_fmt { #define fimc_fmt_is_rgb(x) ((x) & 0x10) -/* Y/Cb/Cr components order at DMA output for 1 plane YCbCr 4:2:2 formats. */ -#define S5P_FIMC_OUT_CRYCBY S5P_CIOCTRL_ORDER422_CRYCBY -#define S5P_FIMC_OUT_CBYCRY S5P_CIOCTRL_ORDER422_YCRYCB -#define S5P_FIMC_OUT_YCRYCB S5P_CIOCTRL_ORDER422_CBYCRY -#define S5P_FIMC_OUT_YCBYCR S5P_CIOCTRL_ORDER422_YCBYCR - -/* Input Y/Cb/Cr components order for 1 plane YCbCr 4:2:2 color formats. */ -#define S5P_FIMC_IN_CRYCBY S5P_MSCTRL_ORDER422_CRYCBY -#define S5P_FIMC_IN_CBYCRY S5P_MSCTRL_ORDER422_YCRYCB -#define S5P_FIMC_IN_YCRYCB S5P_MSCTRL_ORDER422_CBYCRY -#define S5P_FIMC_IN_YCBYCR S5P_MSCTRL_ORDER422_YCBYCR - /* Cb/Cr chrominance components order for 2 plane Y/CbCr 4:2:2 formats. */ #define S5P_FIMC_LSB_CRCB S5P_CIOCTRL_ORDER422_2P_LSB_CRCB @@ -157,18 +145,18 @@ enum fimc_color_fmt { * @name: format description * @fourcc: the fourcc code for this format, 0 if not applicable * @color: the corresponding fimc_color_fmt - * @depth: driver's private 'number of bits per pixel' - * @buff_cnt: number of physically non-contiguous data planes - * @planes_cnt: number of physically contiguous data planes + * @depth: per plane driver's private 'number of bits per pixel' + * @memplanes: number of physically non-contiguous data planes + * @colplanes: number of physically contiguous data planes */ struct fimc_fmt { enum v4l2_mbus_pixelcode mbus_code; char *name; u32 fourcc; u32 color; - u16 buff_cnt; - u16 planes_cnt; - u16 depth; + u16 memplanes; + u16 colplanes; + u8 depth[VIDEO_MAX_PLANES]; u16 flags; #define FMT_FLAGS_CAM (1 << 0) #define FMT_FLAGS_M2M (1 << 1) @@ -260,7 +248,8 @@ struct fimc_addr { * @index: buffer index for the output DMA engine */ struct fimc_vid_buffer { - struct videobuf_buffer vb; + struct vb2_buffer vb; + struct list_head list; struct fimc_addr paddr; int index; }; @@ -277,7 +266,7 @@ struct fimc_vid_buffer { * @height: image pixel weight * @paddr: image frame buffer physical addresses * @buf_cnt: number of buffers depending on a color format - * @size: image size in bytes + * @payload: image size in bytes (w x h x bpp) * @color: color format * @dma_offset: DMA offset in bytes */ @@ -290,7 +279,7 @@ struct fimc_frame { u32 offs_v; u32 width; u32 height; - u32 size; + unsigned long payload[VIDEO_MAX_PLANES]; struct fimc_addr paddr; struct fimc_dma_offset dma_offset; struct fimc_fmt *fmt; @@ -331,13 +320,14 @@ struct fimc_m2m_device { */ struct fimc_vid_cap { struct fimc_ctx *ctx; + struct vb2_alloc_ctx *alloc_ctx; struct video_device *vfd; struct v4l2_device v4l2_dev; - struct v4l2_subdev *sd; + struct v4l2_subdev *sd;; struct v4l2_mbus_framefmt fmt; struct list_head pending_buf_q; struct list_head active_buf_q; - struct videobuf_queue vbq; + struct vb2_queue vbq; int active_buf_cnt; int buf_index; unsigned int frame_count; @@ -372,6 +362,8 @@ struct fimc_pix_limit { * @has_inp_rot: set if has input rotator * @has_out_rot: set if has output rotator * @has_cistatus2: 1 if CISTATUS2 register is present in this IP revision + * @has_mainscaler_ext: 1 if extended mainscaler ratios in CIEXTEN register + * are present in this IP revision * @pix_limit: pixel size constraints for the scaler * @min_inp_pixsize: minimum input pixel size * @min_out_pixsize: minimum output pixel size @@ -383,6 +375,7 @@ struct samsung_fimc_variant { unsigned int has_inp_rot:1; unsigned int has_out_rot:1; unsigned int has_cistatus2:1; + unsigned int has_mainscaler_ext:1; struct fimc_pix_limit *pix_limit; u16 min_inp_pixsize; u16 min_out_pixsize; @@ -412,12 +405,12 @@ struct fimc_ctx; * @lock: the mutex protecting this data structure * @pdev: pointer to the FIMC platform device * @pdata: pointer to the device platform data - * @id: FIMC device index (0..2) + * @id: FIMC device index (0..FIMC_MAX_DEVS) + * @num_clocks: the number of clocks managed by this device instance * @clock[]: the clocks required for FIMC operation * @regs: the mapped hardware registers * @regs_res: the resource claimed for IO registers * @irq: interrupt number of the FIMC subdevice - * @irqlock: spinlock protecting videobuffer queue * @irq_queue: * @m2m: memory-to-memory V4L2 device information * @vid_cap: camera capture device information @@ -427,18 +420,19 @@ struct fimc_dev { spinlock_t slock; struct mutex lock; struct platform_device *pdev; - struct s3c_platform_fimc *pdata; + struct s5p_platform_fimc *pdata; struct samsung_fimc_variant *variant; - int id; - struct clk *clock[NUM_FIMC_CLOCKS]; + u16 id; + u16 num_clocks; + struct clk *clock[MAX_FIMC_CLOCKS]; void __iomem *regs; struct resource *regs_res; int irq; - spinlock_t irqlock; wait_queue_head_t irq_queue; struct fimc_m2m_device m2m; struct fimc_vid_cap vid_cap; unsigned long state; + struct vb2_alloc_ctx *alloc_ctx; }; /** @@ -482,11 +476,9 @@ struct fimc_ctx { struct v4l2_m2m_ctx *m2m_ctx; }; -extern struct videobuf_queue_ops fimc_qops; - static inline int tiled_fmt(struct fimc_fmt *fmt) { - return 0; + return fmt->fourcc == V4L2_PIX_FMT_NV12MT; } static inline void fimc_hw_clear_irq(struct fimc_dev *dev) @@ -542,12 +534,12 @@ static inline struct fimc_frame *ctx_get_frame(struct fimc_ctx *ctx, { struct fimc_frame *frame; - if (V4L2_BUF_TYPE_VIDEO_OUTPUT == type) { + if (V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE == type) { if (ctx->state & FIMC_CTX_M2M) frame = &ctx->s_frame; else return ERR_PTR(-EINVAL); - } else if (V4L2_BUF_TYPE_VIDEO_CAPTURE == type) { + } else if (V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE == type) { frame = &ctx->d_frame; } else { v4l2_err(&ctx->fimc_dev->m2m.v4l2_dev, @@ -581,7 +573,8 @@ void fimc_hw_set_target_format(struct fimc_ctx *ctx); void fimc_hw_set_out_dma(struct fimc_ctx *ctx); void fimc_hw_en_lastirq(struct fimc_dev *fimc, int enable); void fimc_hw_en_irq(struct fimc_dev *fimc, int enable); -void fimc_hw_set_scaler(struct fimc_ctx *ctx); +void fimc_hw_set_prescaler(struct fimc_ctx *ctx); +void fimc_hw_set_mainscaler(struct fimc_ctx *ctx); void fimc_hw_en_capture(struct fimc_ctx *ctx); void fimc_hw_set_effect(struct fimc_ctx *ctx); void fimc_hw_set_in_dma(struct fimc_ctx *ctx); @@ -589,23 +582,23 @@ void fimc_hw_set_input_path(struct fimc_ctx *ctx); void fimc_hw_set_output_path(struct fimc_ctx *ctx); void fimc_hw_set_input_addr(struct fimc_dev *fimc, struct fimc_addr *paddr); void fimc_hw_set_output_addr(struct fimc_dev *fimc, struct fimc_addr *paddr, - int index); + int index); int fimc_hw_set_camera_source(struct fimc_dev *fimc, - struct s3c_fimc_isp_info *cam); + struct s5p_fimc_isp_info *cam); int fimc_hw_set_camera_offset(struct fimc_dev *fimc, struct fimc_frame *f); int fimc_hw_set_camera_polarity(struct fimc_dev *fimc, - struct s3c_fimc_isp_info *cam); + struct s5p_fimc_isp_info *cam); int fimc_hw_set_camera_type(struct fimc_dev *fimc, - struct s3c_fimc_isp_info *cam); + struct s5p_fimc_isp_info *cam); /* -----------------------------------------------------*/ /* fimc-core.c */ -int fimc_vidioc_enum_fmt(struct file *file, void *priv, - struct v4l2_fmtdesc *f); -int fimc_vidioc_g_fmt(struct file *file, void *priv, - struct v4l2_format *f); -int fimc_vidioc_try_fmt(struct file *file, void *priv, - struct v4l2_format *f); +int fimc_vidioc_enum_fmt_mplane(struct file *file, void *priv, + struct v4l2_fmtdesc *f); +int fimc_vidioc_g_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f); +int fimc_vidioc_try_fmt_mplane(struct file *file, void *priv, + struct v4l2_format *f); int fimc_vidioc_queryctrl(struct file *file, void *priv, struct v4l2_queryctrl *qc); int fimc_vidioc_g_ctrl(struct file *file, void *priv, @@ -619,10 +612,10 @@ struct fimc_fmt *find_format(struct v4l2_format *f, unsigned int mask); struct fimc_fmt *find_mbus_format(struct v4l2_mbus_framefmt *f, unsigned int mask); -int fimc_check_scaler_ratio(struct v4l2_rect *r, struct fimc_frame *f); +int fimc_check_scaler_ratio(int sw, int sh, int dw, int dh, int rot); int fimc_set_scaler_info(struct fimc_ctx *ctx); int fimc_prepare_config(struct fimc_ctx *ctx, u32 flags); -int fimc_prepare_addr(struct fimc_ctx *ctx, struct fimc_vid_buffer *buf, +int fimc_prepare_addr(struct fimc_ctx *ctx, struct vb2_buffer *vb, struct fimc_frame *frame, struct fimc_addr *paddr); /* -----------------------------------------------------*/ @@ -649,28 +642,27 @@ static inline void fimc_deactivate_capture(struct fimc_dev *fimc) } /* - * Add video buffer to the active buffers queue. - * The caller holds irqlock spinlock. + * Add buf to the capture active buffers queue. + * Locking: Need to be called with fimc_dev::slock held. */ static inline void active_queue_add(struct fimc_vid_cap *vid_cap, - struct fimc_vid_buffer *buf) + struct fimc_vid_buffer *buf) { - buf->vb.state = VIDEOBUF_ACTIVE; - list_add_tail(&buf->vb.queue, &vid_cap->active_buf_q); + list_add_tail(&buf->list, &vid_cap->active_buf_q); vid_cap->active_buf_cnt++; } /* * Pop a video buffer from the capture active buffers queue - * Locking: Need to be called with dev->slock held. + * Locking: Need to be called with fimc_dev::slock held. */ static inline struct fimc_vid_buffer * active_queue_pop(struct fimc_vid_cap *vid_cap) { struct fimc_vid_buffer *buf; buf = list_entry(vid_cap->active_buf_q.next, - struct fimc_vid_buffer, vb.queue); - list_del(&buf->vb.queue); + struct fimc_vid_buffer, list); + list_del(&buf->list); vid_cap->active_buf_cnt--; return buf; } @@ -679,8 +671,7 @@ active_queue_pop(struct fimc_vid_cap *vid_cap) static inline void fimc_pending_queue_add(struct fimc_vid_cap *vid_cap, struct fimc_vid_buffer *buf) { - buf->vb.state = VIDEOBUF_QUEUED; - list_add_tail(&buf->vb.queue, &vid_cap->pending_buf_q); + list_add_tail(&buf->list, &vid_cap->pending_buf_q); } /* Add video buffer to the capture pending buffers queue */ @@ -689,10 +680,9 @@ pending_queue_pop(struct fimc_vid_cap *vid_cap) { struct fimc_vid_buffer *buf; buf = list_entry(vid_cap->pending_buf_q.next, - struct fimc_vid_buffer, vb.queue); - list_del(&buf->vb.queue); + struct fimc_vid_buffer, list); + list_del(&buf->list); return buf; } - #endif /* FIMC_CORE_H_ */ diff --git a/drivers/media/video/s5p-fimc/fimc-reg.c b/drivers/media/video/s5p-fimc/fimc-reg.c index 511631a2e5c3..10684aef5b2d 100644 --- a/drivers/media/video/s5p-fimc/fimc-reg.c +++ b/drivers/media/video/s5p-fimc/fimc-reg.c @@ -13,7 +13,7 @@ #include <linux/io.h> #include <linux/delay.h> #include <mach/map.h> -#include <media/s3c_fimc.h> +#include <media/s5p_fimc.h> #include "fimc-core.h" @@ -37,11 +37,11 @@ void fimc_hw_reset(struct fimc_dev *dev) writel(cfg, dev->regs + S5P_CIGCTRL); } -static u32 fimc_hw_get_in_flip(u32 ctx_flip) +static u32 fimc_hw_get_in_flip(struct fimc_ctx *ctx) { u32 flip = S5P_MSCTRL_FLIP_NORMAL; - switch (ctx_flip) { + switch (ctx->flip) { case FLIP_X_AXIS: flip = S5P_MSCTRL_FLIP_X_MIRROR; break; @@ -51,16 +51,20 @@ static u32 fimc_hw_get_in_flip(u32 ctx_flip) case FLIP_XY_AXIS: flip = S5P_MSCTRL_FLIP_180; break; + default: + break; } + if (ctx->rotation <= 90) + return flip; - return flip; + return (flip ^ S5P_MSCTRL_FLIP_180) & S5P_MSCTRL_FLIP_180; } -static u32 fimc_hw_get_target_flip(u32 ctx_flip) +static u32 fimc_hw_get_target_flip(struct fimc_ctx *ctx) { u32 flip = S5P_CITRGFMT_FLIP_NORMAL; - switch (ctx_flip) { + switch (ctx->flip) { case FLIP_X_AXIS: flip = S5P_CITRGFMT_FLIP_X_MIRROR; break; @@ -70,11 +74,13 @@ static u32 fimc_hw_get_target_flip(u32 ctx_flip) case FLIP_XY_AXIS: flip = S5P_CITRGFMT_FLIP_180; break; - case FLIP_NONE: + default: break; - } - return flip; + if (ctx->rotation <= 90) + return flip; + + return (flip ^ S5P_CITRGFMT_FLIP_180) & S5P_CITRGFMT_FLIP_180; } void fimc_hw_set_rotation(struct fimc_ctx *ctx) @@ -84,10 +90,7 @@ void fimc_hw_set_rotation(struct fimc_ctx *ctx) cfg = readl(dev->regs + S5P_CITRGFMT); cfg &= ~(S5P_CITRGFMT_INROT90 | S5P_CITRGFMT_OUTROT90 | - S5P_CITRGFMT_FLIP_180); - - flip = readl(dev->regs + S5P_MSCTRL); - flip &= ~S5P_MSCTRL_FLIP_MASK; + S5P_CITRGFMT_FLIP_180); /* * The input and output rotator cannot work simultaneously. @@ -95,26 +98,22 @@ void fimc_hw_set_rotation(struct fimc_ctx *ctx) * in direct fifo output mode. */ if (ctx->rotation == 90 || ctx->rotation == 270) { - if (ctx->out_path == FIMC_LCDFIFO) { - cfg |= S5P_CITRGFMT_INROT90; - if (ctx->rotation == 270) - flip |= S5P_MSCTRL_FLIP_180; - } else { - cfg |= S5P_CITRGFMT_OUTROT90; - if (ctx->rotation == 270) - cfg |= S5P_CITRGFMT_FLIP_180; - } - } else if (ctx->rotation == 180) { if (ctx->out_path == FIMC_LCDFIFO) - flip |= S5P_MSCTRL_FLIP_180; + cfg |= S5P_CITRGFMT_INROT90; else - cfg |= S5P_CITRGFMT_FLIP_180; + cfg |= S5P_CITRGFMT_OUTROT90; } - if (ctx->rotation == 180 || ctx->rotation == 270) - writel(flip, dev->regs + S5P_MSCTRL); - cfg |= fimc_hw_get_target_flip(ctx->flip); - writel(cfg, dev->regs + S5P_CITRGFMT); + if (ctx->out_path == FIMC_DMA) { + cfg |= fimc_hw_get_target_flip(ctx); + writel(cfg, dev->regs + S5P_CITRGFMT); + } else { + /* LCD FIFO path */ + flip = readl(dev->regs + S5P_MSCTRL); + flip &= ~S5P_MSCTRL_FLIP_MASK; + flip |= fimc_hw_get_in_flip(ctx); + writel(flip, dev->regs + S5P_MSCTRL); + } } void fimc_hw_set_target_format(struct fimc_ctx *ctx) @@ -131,19 +130,14 @@ void fimc_hw_set_target_format(struct fimc_ctx *ctx) S5P_CITRGFMT_VSIZE_MASK); switch (frame->fmt->color) { - case S5P_FIMC_RGB565: - case S5P_FIMC_RGB666: - case S5P_FIMC_RGB888: + case S5P_FIMC_RGB565...S5P_FIMC_RGB888: cfg |= S5P_CITRGFMT_RGB; break; case S5P_FIMC_YCBCR420: cfg |= S5P_CITRGFMT_YCBCR420; break; - case S5P_FIMC_YCBYCR422: - case S5P_FIMC_YCRYCB422: - case S5P_FIMC_CBYCRY422: - case S5P_FIMC_CRYCBY422: - if (frame->fmt->planes_cnt == 1) + case S5P_FIMC_YCBYCR422...S5P_FIMC_CRYCBY422: + if (frame->fmt->colplanes == 1) cfg |= S5P_CITRGFMT_YCBCR422_1P; else cfg |= S5P_CITRGFMT_YCBCR422; @@ -219,11 +213,11 @@ void fimc_hw_set_out_dma(struct fimc_ctx *ctx) cfg &= ~(S5P_CIOCTRL_ORDER2P_MASK | S5P_CIOCTRL_ORDER422_MASK | S5P_CIOCTRL_YCBCR_PLANE_MASK); - if (frame->fmt->planes_cnt == 1) + if (frame->fmt->colplanes == 1) cfg |= ctx->out_order_1p; - else if (frame->fmt->planes_cnt == 2) + else if (frame->fmt->colplanes == 2) cfg |= ctx->out_order_2p | S5P_CIOCTRL_YCBCR_2PLANE; - else if (frame->fmt->planes_cnt == 3) + else if (frame->fmt->colplanes == 3) cfg |= S5P_CIOCTRL_YCBCR_3PLANE; writel(cfg, dev->regs + S5P_CIOCTRL); @@ -249,7 +243,7 @@ void fimc_hw_en_lastirq(struct fimc_dev *dev, int enable) writel(cfg, dev->regs + S5P_CIOCTRL); } -static void fimc_hw_set_prescaler(struct fimc_ctx *ctx) +void fimc_hw_set_prescaler(struct fimc_ctx *ctx) { struct fimc_dev *dev = ctx->fimc_dev; struct fimc_scaler *sc = &ctx->scaler; @@ -267,7 +261,7 @@ static void fimc_hw_set_prescaler(struct fimc_ctx *ctx) writel(cfg, dev->regs + S5P_CISCPREDST); } -void fimc_hw_set_scaler(struct fimc_ctx *ctx) +static void fimc_hw_set_scaler(struct fimc_ctx *ctx) { struct fimc_dev *dev = ctx->fimc_dev; struct fimc_scaler *sc = &ctx->scaler; @@ -275,8 +269,6 @@ void fimc_hw_set_scaler(struct fimc_ctx *ctx) struct fimc_frame *dst_frame = &ctx->d_frame; u32 cfg = 0; - fimc_hw_set_prescaler(ctx); - if (!(ctx->flags & FIMC_COLOR_RANGE_NARROW)) cfg |= (S5P_CISCCTRL_CSCR2Y_WIDE | S5P_CISCCTRL_CSCY2R_WIDE); @@ -316,13 +308,42 @@ void fimc_hw_set_scaler(struct fimc_ctx *ctx) cfg |= S5P_CISCCTRL_INTERLACE; } + writel(cfg, dev->regs + S5P_CISCCTRL); +} + +void fimc_hw_set_mainscaler(struct fimc_ctx *ctx) +{ + struct fimc_dev *dev = ctx->fimc_dev; + struct samsung_fimc_variant *variant = dev->variant; + struct fimc_scaler *sc = &ctx->scaler; + u32 cfg; + dbg("main_hratio= 0x%X main_vratio= 0x%X", sc->main_hratio, sc->main_vratio); - cfg |= S5P_CISCCTRL_SC_HORRATIO(sc->main_hratio); - cfg |= S5P_CISCCTRL_SC_VERRATIO(sc->main_vratio); + fimc_hw_set_scaler(ctx); - writel(cfg, dev->regs + S5P_CISCCTRL); + cfg = readl(dev->regs + S5P_CISCCTRL); + + if (variant->has_mainscaler_ext) { + cfg &= ~(S5P_CISCCTRL_MHRATIO_MASK | S5P_CISCCTRL_MVRATIO_MASK); + cfg |= S5P_CISCCTRL_MHRATIO_EXT(sc->main_hratio); + cfg |= S5P_CISCCTRL_MVRATIO_EXT(sc->main_vratio); + writel(cfg, dev->regs + S5P_CISCCTRL); + + cfg = readl(dev->regs + S5P_CIEXTEN); + + cfg &= ~(S5P_CIEXTEN_MVRATIO_EXT_MASK | + S5P_CIEXTEN_MHRATIO_EXT_MASK); + cfg |= S5P_CIEXTEN_MHRATIO_EXT(sc->main_hratio); + cfg |= S5P_CIEXTEN_MVRATIO_EXT(sc->main_vratio); + writel(cfg, dev->regs + S5P_CIEXTEN); + } else { + cfg &= ~(S5P_CISCCTRL_MHRATIO_MASK | S5P_CISCCTRL_MVRATIO_MASK); + cfg |= S5P_CISCCTRL_MHRATIO(sc->main_hratio); + cfg |= S5P_CISCCTRL_MVRATIO(sc->main_vratio); + writel(cfg, dev->regs + S5P_CISCCTRL); + } } void fimc_hw_en_capture(struct fimc_ctx *ctx) @@ -410,41 +431,37 @@ void fimc_hw_set_in_dma(struct fimc_ctx *ctx) /* Set the input DMA to process single frame only. */ cfg = readl(dev->regs + S5P_MSCTRL); - cfg &= ~(S5P_MSCTRL_FLIP_MASK - | S5P_MSCTRL_INFORMAT_MASK + cfg &= ~(S5P_MSCTRL_INFORMAT_MASK | S5P_MSCTRL_IN_BURST_COUNT_MASK | S5P_MSCTRL_INPUT_MASK | S5P_MSCTRL_C_INT_IN_MASK | S5P_MSCTRL_2P_IN_ORDER_MASK); - cfg |= (S5P_MSCTRL_FRAME_COUNT(1) | S5P_MSCTRL_INPUT_MEMORY); + cfg |= (S5P_MSCTRL_IN_BURST_COUNT(4) + | S5P_MSCTRL_INPUT_MEMORY + | S5P_MSCTRL_FIFO_CTRL_FULL); switch (frame->fmt->color) { - case S5P_FIMC_RGB565: - case S5P_FIMC_RGB666: - case S5P_FIMC_RGB888: + case S5P_FIMC_RGB565...S5P_FIMC_RGB888: cfg |= S5P_MSCTRL_INFORMAT_RGB; break; case S5P_FIMC_YCBCR420: cfg |= S5P_MSCTRL_INFORMAT_YCBCR420; - if (frame->fmt->planes_cnt == 2) + if (frame->fmt->colplanes == 2) cfg |= ctx->in_order_2p | S5P_MSCTRL_C_INT_IN_2PLANE; else cfg |= S5P_MSCTRL_C_INT_IN_3PLANE; break; - case S5P_FIMC_YCBYCR422: - case S5P_FIMC_YCRYCB422: - case S5P_FIMC_CBYCRY422: - case S5P_FIMC_CRYCBY422: - if (frame->fmt->planes_cnt == 1) { + case S5P_FIMC_YCBYCR422...S5P_FIMC_CRYCBY422: + if (frame->fmt->colplanes == 1) { cfg |= ctx->in_order_1p | S5P_MSCTRL_INFORMAT_YCBCR422_1P; } else { cfg |= S5P_MSCTRL_INFORMAT_YCBCR422; - if (frame->fmt->planes_cnt == 2) + if (frame->fmt->colplanes == 2) cfg |= ctx->in_order_2p | S5P_MSCTRL_C_INT_IN_2PLANE; else @@ -455,13 +472,6 @@ void fimc_hw_set_in_dma(struct fimc_ctx *ctx) break; } - /* - * Input DMA flip mode (and rotation). - * Do not allow simultaneous rotation and flipping. - */ - if (!ctx->rotation && ctx->out_path == FIMC_LCDFIFO) - cfg |= fimc_hw_get_in_flip(ctx->flip); - writel(cfg, dev->regs + S5P_MSCTRL); /* Input/output DMA linear/tiled mode. */ @@ -532,7 +542,7 @@ void fimc_hw_set_output_addr(struct fimc_dev *dev, } int fimc_hw_set_camera_polarity(struct fimc_dev *fimc, - struct s3c_fimc_isp_info *cam) + struct s5p_fimc_isp_info *cam) { u32 cfg = readl(fimc->regs + S5P_CIGCTRL); @@ -557,41 +567,46 @@ int fimc_hw_set_camera_polarity(struct fimc_dev *fimc, } int fimc_hw_set_camera_source(struct fimc_dev *fimc, - struct s3c_fimc_isp_info *cam) + struct s5p_fimc_isp_info *cam) { struct fimc_frame *f = &fimc->vid_cap.ctx->s_frame; u32 cfg = 0; + u32 bus_width; + int i; + + static const struct { + u32 pixelcode; + u32 cisrcfmt; + u16 bus_width; + } pix_desc[] = { + { V4L2_MBUS_FMT_YUYV8_2X8, S5P_CISRCFMT_ORDER422_YCBYCR, 8 }, + { V4L2_MBUS_FMT_YVYU8_2X8, S5P_CISRCFMT_ORDER422_YCRYCB, 8 }, + { V4L2_MBUS_FMT_VYUY8_2X8, S5P_CISRCFMT_ORDER422_CRYCBY, 8 }, + { V4L2_MBUS_FMT_UYVY8_2X8, S5P_CISRCFMT_ORDER422_CBYCRY, 8 }, + /* TODO: Add pixel codes for 16-bit bus width */ + }; if (cam->bus_type == FIMC_ITU_601 || cam->bus_type == FIMC_ITU_656) { + for (i = 0; i < ARRAY_SIZE(pix_desc); i++) { + if (fimc->vid_cap.fmt.code == pix_desc[i].pixelcode) { + cfg = pix_desc[i].cisrcfmt; + bus_width = pix_desc[i].bus_width; + break; + } + } - switch (fimc->vid_cap.fmt.code) { - case V4L2_MBUS_FMT_YUYV8_2X8: - cfg = S5P_CISRCFMT_ORDER422_YCBYCR; - break; - case V4L2_MBUS_FMT_YVYU8_2X8: - cfg = S5P_CISRCFMT_ORDER422_YCRYCB; - break; - case V4L2_MBUS_FMT_VYUY8_2X8: - cfg = S5P_CISRCFMT_ORDER422_CRYCBY; - break; - case V4L2_MBUS_FMT_UYVY8_2X8: - cfg = S5P_CISRCFMT_ORDER422_CBYCRY; - break; - default: - err("camera image format not supported: %d", - fimc->vid_cap.fmt.code); + if (i == ARRAY_SIZE(pix_desc)) { + v4l2_err(&fimc->vid_cap.v4l2_dev, + "Camera color format not supported: %d\n", + fimc->vid_cap.fmt.code); return -EINVAL; } if (cam->bus_type == FIMC_ITU_601) { - if (cam->bus_width == 8) { + if (bus_width == 8) cfg |= S5P_CISRCFMT_ITU601_8BIT; - } else if (cam->bus_width == 16) { + else if (bus_width == 16) cfg |= S5P_CISRCFMT_ITU601_16BIT; - } else { - err("invalid bus width: %d", cam->bus_width); - return -EINVAL; - } } /* else defaults to ITU-R BT.656 8-bit */ } @@ -624,7 +639,7 @@ int fimc_hw_set_camera_offset(struct fimc_dev *fimc, struct fimc_frame *f) } int fimc_hw_set_camera_type(struct fimc_dev *fimc, - struct s3c_fimc_isp_info *cam) + struct s5p_fimc_isp_info *cam) { u32 cfg, tmp; struct fimc_vid_cap *vid_cap = &fimc->vid_cap; diff --git a/drivers/media/video/s5p-fimc/regs-fimc.h b/drivers/media/video/s5p-fimc/regs-fimc.h index 57e33f84fcfa..0fea3e635d76 100644 --- a/drivers/media/video/s5p-fimc/regs-fimc.h +++ b/drivers/media/video/s5p-fimc/regs-fimc.h @@ -98,8 +98,8 @@ #define S5P_CIOCTRL 0x4c #define S5P_CIOCTRL_ORDER422_MASK (3 << 0) #define S5P_CIOCTRL_ORDER422_CRYCBY (0 << 0) -#define S5P_CIOCTRL_ORDER422_YCRYCB (1 << 0) -#define S5P_CIOCTRL_ORDER422_CBYCRY (2 << 0) +#define S5P_CIOCTRL_ORDER422_CBYCRY (1 << 0) +#define S5P_CIOCTRL_ORDER422_YCRYCB (2 << 0) #define S5P_CIOCTRL_ORDER422_YCBYCR (3 << 0) #define S5P_CIOCTRL_LASTIRQ_ENABLE (1 << 2) #define S5P_CIOCTRL_YCBCR_3PLANE (0 << 3) @@ -139,8 +139,12 @@ #define S5P_CISCCTRL_OUTRGB_FMT_MASK (3 << 11) #define S5P_CISCCTRL_RGB_EXT (1 << 10) #define S5P_CISCCTRL_ONE2ONE (1 << 9) -#define S5P_CISCCTRL_SC_HORRATIO(x) ((x) << 16) -#define S5P_CISCCTRL_SC_VERRATIO(x) ((x) << 0) +#define S5P_CISCCTRL_MHRATIO(x) ((x) << 16) +#define S5P_CISCCTRL_MVRATIO(x) ((x) << 0) +#define S5P_CISCCTRL_MHRATIO_MASK (0x1ff << 16) +#define S5P_CISCCTRL_MVRATIO_MASK (0x1ff << 0) +#define S5P_CISCCTRL_MHRATIO_EXT(x) (((x) >> 6) << 16) +#define S5P_CISCCTRL_MVRATIO_EXT(x) (((x) >> 6) << 0) /* Target area */ #define S5P_CITAREA 0x5c @@ -210,7 +214,7 @@ /* Input DMA control */ #define S5P_MSCTRL 0xfc -#define S5P_MSCTRL_IN_BURST_COUNT_MASK (3 << 24) +#define S5P_MSCTRL_IN_BURST_COUNT_MASK (0xF << 24) #define S5P_MSCTRL_2P_IN_ORDER_MASK (3 << 16) #define S5P_MSCTRL_2P_IN_ORDER_SHIFT 16 #define S5P_MSCTRL_C_INT_IN_3PLANE (0 << 15) @@ -222,11 +226,12 @@ #define S5P_MSCTRL_FLIP_X_MIRROR (1 << 13) #define S5P_MSCTRL_FLIP_Y_MIRROR (2 << 13) #define S5P_MSCTRL_FLIP_180 (3 << 13) +#define S5P_MSCTRL_FIFO_CTRL_FULL (1 << 12) #define S5P_MSCTRL_ORDER422_SHIFT 4 -#define S5P_MSCTRL_ORDER422_CRYCBY (0 << 4) -#define S5P_MSCTRL_ORDER422_YCRYCB (1 << 4) -#define S5P_MSCTRL_ORDER422_CBYCRY (2 << 4) -#define S5P_MSCTRL_ORDER422_YCBYCR (3 << 4) +#define S5P_MSCTRL_ORDER422_YCBYCR (0 << 4) +#define S5P_MSCTRL_ORDER422_CBYCRY (1 << 4) +#define S5P_MSCTRL_ORDER422_YCRYCB (2 << 4) +#define S5P_MSCTRL_ORDER422_CRYCBY (3 << 4) #define S5P_MSCTRL_ORDER422_MASK (3 << 4) #define S5P_MSCTRL_INPUT_EXTCAM (0 << 3) #define S5P_MSCTRL_INPUT_MEMORY (1 << 3) @@ -237,7 +242,7 @@ #define S5P_MSCTRL_INFORMAT_RGB (3 << 1) #define S5P_MSCTRL_INFORMAT_MASK (3 << 1) #define S5P_MSCTRL_ENVID (1 << 0) -#define S5P_MSCTRL_FRAME_COUNT(x) ((x) << 24) +#define S5P_MSCTRL_IN_BURST_COUNT(x) ((x) << 24) /* Output DMA Y/Cb/Cr offset */ #define S5P_CIOYOFF 0x168 @@ -263,6 +268,10 @@ /* Real output DMA image size (extension register) */ #define S5P_CIEXTEN 0x188 +#define S5P_CIEXTEN_MHRATIO_EXT(x) (((x) & 0x3f) << 10) +#define S5P_CIEXTEN_MVRATIO_EXT(x) ((x) & 0x3f) +#define S5P_CIEXTEN_MHRATIO_EXT_MASK (0x3f << 10) +#define S5P_CIEXTEN_MVRATIO_EXT_MASK 0x3f #define S5P_CIDMAPARAM 0x18c #define S5P_CIDMAPARAM_R_LINEAR (0 << 29) diff --git a/drivers/media/video/saa7110.c b/drivers/media/video/saa7110.c index 7913f93979b8..99664205ef4e 100644 --- a/drivers/media/video/saa7110.c +++ b/drivers/media/video/saa7110.c @@ -36,6 +36,7 @@ #include <linux/videodev2.h> #include <media/v4l2-device.h> #include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> MODULE_DESCRIPTION("Philips SAA7110 video decoder driver"); MODULE_AUTHOR("Pauline Middelink"); @@ -53,15 +54,12 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)"); struct saa7110 { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; u8 reg[SAA7110_NR_REG]; v4l2_std_id norm; int input; int enable; - int bright; - int contrast; - int hue; - int sat; wait_queue_head_t wq; }; @@ -71,6 +69,11 @@ static inline struct saa7110 *to_saa7110(struct v4l2_subdev *sd) return container_of(sd, struct saa7110, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct saa7110, hdl)->sd; +} + /* ----------------------------------------------------------------------- */ /* I2C support functions */ /* ----------------------------------------------------------------------- */ @@ -326,73 +329,22 @@ static int saa7110_s_stream(struct v4l2_subdev *sd, int enable) return 0; } -static int saa7110_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_BRIGHTNESS: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 128); - case V4L2_CID_CONTRAST: - case V4L2_CID_SATURATION: - return v4l2_ctrl_query_fill(qc, 0, 127, 1, 64); - case V4L2_CID_HUE: - return v4l2_ctrl_query_fill(qc, -128, 127, 1, 0); - default: - return -EINVAL; - } - return 0; -} - -static int saa7110_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct saa7110 *decoder = to_saa7110(sd); - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - ctrl->value = decoder->bright; - break; - case V4L2_CID_CONTRAST: - ctrl->value = decoder->contrast; - break; - case V4L2_CID_SATURATION: - ctrl->value = decoder->sat; - break; - case V4L2_CID_HUE: - ctrl->value = decoder->hue; - break; - default: - return -EINVAL; - } - return 0; -} - -static int saa7110_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int saa7110_s_ctrl(struct v4l2_ctrl *ctrl) { - struct saa7110 *decoder = to_saa7110(sd); + struct v4l2_subdev *sd = to_sd(ctrl); switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - if (decoder->bright != ctrl->value) { - decoder->bright = ctrl->value; - saa7110_write(sd, 0x19, decoder->bright); - } + saa7110_write(sd, 0x19, ctrl->val); break; case V4L2_CID_CONTRAST: - if (decoder->contrast != ctrl->value) { - decoder->contrast = ctrl->value; - saa7110_write(sd, 0x13, decoder->contrast); - } + saa7110_write(sd, 0x13, ctrl->val); break; case V4L2_CID_SATURATION: - if (decoder->sat != ctrl->value) { - decoder->sat = ctrl->value; - saa7110_write(sd, 0x12, decoder->sat); - } + saa7110_write(sd, 0x12, ctrl->val); break; case V4L2_CID_HUE: - if (decoder->hue != ctrl->value) { - decoder->hue = ctrl->value; - saa7110_write(sd, 0x07, decoder->hue); - } + saa7110_write(sd, 0x07, ctrl->val); break; default: return -EINVAL; @@ -409,11 +361,19 @@ static int saa7110_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ide /* ----------------------------------------------------------------------- */ +static const struct v4l2_ctrl_ops saa7110_ctrl_ops = { + .s_ctrl = saa7110_s_ctrl, +}; + static const struct v4l2_subdev_core_ops saa7110_core_ops = { .g_chip_ident = saa7110_g_chip_ident, - .g_ctrl = saa7110_g_ctrl, - .s_ctrl = saa7110_s_ctrl, - .queryctrl = saa7110_queryctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, .s_std = saa7110_s_std, }; @@ -454,10 +414,25 @@ static int saa7110_probe(struct i2c_client *client, decoder->norm = V4L2_STD_PAL; decoder->input = 0; decoder->enable = 1; - decoder->bright = 32768; - decoder->contrast = 32768; - decoder->hue = 32768; - decoder->sat = 32768; + v4l2_ctrl_handler_init(&decoder->hdl, 2); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_CONTRAST, 0, 127, 1, 64); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_SATURATION, 0, 127, 1, 64); + v4l2_ctrl_new_std(&decoder->hdl, &saa7110_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + init_waitqueue_head(&decoder->wq); rv = saa7110_write_block(sd, initseq, sizeof(initseq)); @@ -490,9 +465,11 @@ static int saa7110_probe(struct i2c_client *client, static int saa7110_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct saa7110 *decoder = to_saa7110(sd); v4l2_device_unregister_subdev(sd); - kfree(to_saa7110(sd)); + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); return 0; } diff --git a/drivers/media/video/saa7134/saa7134-cards.c b/drivers/media/video/saa7134/saa7134-cards.c index deb8fcf4aa49..74467c18e69a 100644 --- a/drivers/media/video/saa7134/saa7134-cards.c +++ b/drivers/media/video/saa7134/saa7134-cards.c @@ -3620,6 +3620,38 @@ struct saa7134_board saa7134_boards[] = { .amux = 0, }, }, + [SAA7134_BOARD_ENCORE_ENLTV_FM3] = { + .name = "Encore ENLTV-FM 3", + .audio_clock = 0x02187de7, + .tuner_type = TUNER_TENA_TNF_5337, + .radio_type = TUNER_TEA5767, + .tuner_addr = 0x61, + .radio_addr = 0x60, + .inputs = { { + .name = name_tv, + .vmux = 1, + .amux = LINE2, + .tv = 1, + }, { + .name = name_comp1, + .vmux = 3, + .amux = LINE1, + }, { + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + } }, + .radio = { + .name = name_radio, + .vmux = 1, + .amux = LINE1, + }, + .mute = { + .name = name_mute, + .amux = LINE1, + .gpio = 0x43000, + }, + }, [SAA7134_BOARD_CINERGY_HT_PCI] = { .name = "Terratec Cinergy HT PCI", .audio_clock = 0x00187de7, @@ -6387,6 +6419,12 @@ struct pci_device_id saa7134_pci_tbl[] = { .driver_data = SAA7134_BOARD_ENCORE_ENLTV_FM53, }, { .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7134, + .subvendor = 0x1a7f, + .subdevice = 0x2108, + .driver_data = SAA7134_BOARD_ENCORE_ENLTV_FM3, + }, { + .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7133, .subvendor = 0x153b, .subdevice = 0x1175, @@ -7102,6 +7140,7 @@ int saa7134_board_init1(struct saa7134_dev *dev) case SAA7134_BOARD_ENCORE_ENLTV: case SAA7134_BOARD_ENCORE_ENLTV_FM: case SAA7134_BOARD_ENCORE_ENLTV_FM53: + case SAA7134_BOARD_ENCORE_ENLTV_FM3: case SAA7134_BOARD_10MOONSTVMASTER3: case SAA7134_BOARD_BEHOLD_401: case SAA7134_BOARD_BEHOLD_403: diff --git a/drivers/media/video/saa7134/saa7134-core.c b/drivers/media/video/saa7134/saa7134-core.c index 6abeecff6da7..41f836fc93ec 100644 --- a/drivers/media/video/saa7134/saa7134-core.c +++ b/drivers/media/video/saa7134/saa7134-core.c @@ -752,19 +752,28 @@ static int saa7134_hwfini(struct saa7134_dev *dev) return 0; } -static void __devinit must_configure_manually(void) +static void __devinit must_configure_manually(int has_eeprom) { unsigned int i,p; - printk(KERN_WARNING - "saa7134: <rant>\n" - "saa7134: Congratulations! Your TV card vendor saved a few\n" - "saa7134: cents for a eeprom, thus your pci board has no\n" - "saa7134: subsystem ID and I can't identify it automatically\n" - "saa7134: </rant>\n" - "saa7134: I feel better now. Ok, here are the good news:\n" - "saa7134: You can use the card=<nr> insmod option to specify\n" - "saa7134: which board do you have. The list:\n"); + if (!has_eeprom) + printk(KERN_WARNING + "saa7134: <rant>\n" + "saa7134: Congratulations! Your TV card vendor saved a few\n" + "saa7134: cents for a eeprom, thus your pci board has no\n" + "saa7134: subsystem ID and I can't identify it automatically\n" + "saa7134: </rant>\n" + "saa7134: I feel better now. Ok, here are the good news:\n" + "saa7134: You can use the card=<nr> insmod option to specify\n" + "saa7134: which board do you have. The list:\n"); + else + printk(KERN_WARNING + "saa7134: Board is currently unknown. You might try to use the card=<nr>\n" + "saa7134: insmod option to specify which board do you have, but this is\n" + "saa7134: somewhat risky, as might damage your card. It is better to ask\n" + "saa7134: for support at linux-media@vger.kernel.org.\n" + "saa7134: The supported cards are:\n"); + for (i = 0; i < saa7134_bcount; i++) { printk(KERN_WARNING "saa7134: card=%d -> %-40.40s", i,saa7134_boards[i].name); @@ -936,8 +945,10 @@ static int __devinit saa7134_initdev(struct pci_dev *pci_dev, if (card[dev->nr] >= 0 && card[dev->nr] < saa7134_bcount) dev->board = card[dev->nr]; - if (SAA7134_BOARD_NOAUTO == dev->board) { - must_configure_manually(); + if (SAA7134_BOARD_UNKNOWN == dev->board) + must_configure_manually(0); + else if (SAA7134_BOARD_NOAUTO == dev->board) { + must_configure_manually(1); dev->board = SAA7134_BOARD_UNKNOWN; } dev->autodetected = card[dev->nr] != dev->board; diff --git a/drivers/media/video/saa7134/saa7134-input.c b/drivers/media/video/saa7134/saa7134-input.c index dc646e65edb7..c9eff0336aa6 100644 --- a/drivers/media/video/saa7134/saa7134-input.c +++ b/drivers/media/video/saa7134/saa7134-input.c @@ -681,6 +681,7 @@ int saa7134_input_init1(struct saa7134_dev *dev) polling = 50; // ms break; case SAA7134_BOARD_ENCORE_ENLTV_FM53: + case SAA7134_BOARD_ENCORE_ENLTV_FM3: ir_codes = RC_MAP_ENCORE_ENLTV_FM53; mask_keydown = 0x0040000; /* Enable GPIO18 line on both edges */ mask_keyup = 0x0040000; diff --git a/drivers/media/video/saa7134/saa7134.h b/drivers/media/video/saa7134/saa7134.h index 5b0a347b0b8f..f96cd5d761f9 100644 --- a/drivers/media/video/saa7134/saa7134.h +++ b/drivers/media/video/saa7134/saa7134.h @@ -327,6 +327,7 @@ struct saa7134_card_ir { #define SAA7134_BOARD_TECHNOTREND_BUDGET_T3000 181 #define SAA7134_BOARD_KWORLD_PCI_SBTVD_FULLSEG 182 #define SAA7134_BOARD_VIDEOMATE_M1F 183 +#define SAA7134_BOARD_ENCORE_ENLTV_FM3 184 #define SAA7134_MAXBOARDS 32 #define SAA7134_INPUT_MAX 8 diff --git a/drivers/media/video/tlv320aic23b.c b/drivers/media/video/tlv320aic23b.c index dfc4dd7c5097..286ec7e7062a 100644 --- a/drivers/media/video/tlv320aic23b.c +++ b/drivers/media/video/tlv320aic23b.c @@ -31,6 +31,7 @@ #include <linux/i2c.h> #include <linux/videodev2.h> #include <media/v4l2-device.h> +#include <media/v4l2-ctrls.h> MODULE_DESCRIPTION("tlv320aic23b driver"); MODULE_AUTHOR("Scott Alfter, Ulf Eklund, Hans Verkuil"); @@ -41,7 +42,7 @@ MODULE_LICENSE("GPL"); struct tlv320aic23b_state { struct v4l2_subdev sd; - u8 muted; + struct v4l2_ctrl_handler hdl; }; static inline struct tlv320aic23b_state *to_state(struct v4l2_subdev *sd) @@ -49,6 +50,11 @@ static inline struct tlv320aic23b_state *to_state(struct v4l2_subdev *sd) return container_of(sd, struct tlv320aic23b_state, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tlv320aic23b_state, hdl)->sd; +} + static int tlv320aic23b_write(struct v4l2_subdev *sd, int reg, u16 val) { struct i2c_client *client = v4l2_get_subdevdata(sd); @@ -85,44 +91,44 @@ static int tlv320aic23b_s_clock_freq(struct v4l2_subdev *sd, u32 freq) return 0; } -static int tlv320aic23b_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct tlv320aic23b_state *state = to_state(sd); - - if (ctrl->id != V4L2_CID_AUDIO_MUTE) - return -EINVAL; - ctrl->value = state->muted; - return 0; -} - -static int tlv320aic23b_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int tlv320aic23b_s_ctrl(struct v4l2_ctrl *ctrl) { - struct tlv320aic23b_state *state = to_state(sd); - - if (ctrl->id != V4L2_CID_AUDIO_MUTE) - return -EINVAL; - state->muted = ctrl->value; - tlv320aic23b_write(sd, 0, 0x180); /* mute both channels */ - /* set gain on both channels to +3.0 dB */ - if (!state->muted) - tlv320aic23b_write(sd, 0, 0x119); - return 0; + struct v4l2_subdev *sd = to_sd(ctrl); + + switch (ctrl->id) { + case V4L2_CID_AUDIO_MUTE: + tlv320aic23b_write(sd, 0, 0x180); /* mute both channels */ + /* set gain on both channels to +3.0 dB */ + if (!ctrl->val) + tlv320aic23b_write(sd, 0, 0x119); + return 0; + } + return -EINVAL; } static int tlv320aic23b_log_status(struct v4l2_subdev *sd) { struct tlv320aic23b_state *state = to_state(sd); - v4l2_info(sd, "Input: %s\n", state->muted ? "muted" : "active"); + v4l2_ctrl_handler_log_status(&state->hdl, sd->name); return 0; } /* ----------------------------------------------------------------------- */ +static const struct v4l2_ctrl_ops tlv320aic23b_ctrl_ops = { + .s_ctrl = tlv320aic23b_s_ctrl, +}; + static const struct v4l2_subdev_core_ops tlv320aic23b_core_ops = { .log_status = tlv320aic23b_log_status, - .g_ctrl = tlv320aic23b_g_ctrl, - .s_ctrl = tlv320aic23b_s_ctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, }; static const struct v4l2_subdev_audio_ops tlv320aic23b_audio_ops = { @@ -161,7 +167,6 @@ static int tlv320aic23b_probe(struct i2c_client *client, return -ENOMEM; sd = &state->sd; v4l2_i2c_subdev_init(sd, client, &tlv320aic23b_ops); - state->muted = 0; /* Initialize tlv320aic23b */ @@ -177,15 +182,30 @@ static int tlv320aic23b_probe(struct i2c_client *client, tlv320aic23b_write(sd, 8, 0x000); /* activate digital interface */ tlv320aic23b_write(sd, 9, 0x001); + + v4l2_ctrl_handler_init(&state->hdl, 1); + v4l2_ctrl_new_std(&state->hdl, &tlv320aic23b_ctrl_ops, + V4L2_CID_AUDIO_MUTE, 0, 1, 1, 0); + sd->ctrl_handler = &state->hdl; + if (state->hdl.error) { + int err = state->hdl.error; + + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); + return err; + } + v4l2_ctrl_handler_setup(&state->hdl); return 0; } static int tlv320aic23b_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct tlv320aic23b_state *state = to_state(sd); v4l2_device_unregister_subdev(sd); - kfree(to_state(sd)); + v4l2_ctrl_handler_free(&state->hdl); + kfree(state); return 0; } diff --git a/drivers/media/video/tvp514x.c b/drivers/media/video/tvp514x.c index 45bcf0358a1d..9b3e828b0775 100644 --- a/drivers/media/video/tvp514x.c +++ b/drivers/media/video/tvp514x.c @@ -37,6 +37,7 @@ #include <media/v4l2-common.h> #include <media/v4l2-mediabus.h> #include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> #include <media/tvp514x.h> #include "tvp514x_regs.h" @@ -97,6 +98,7 @@ static int tvp514x_s_stream(struct v4l2_subdev *sd, int enable); */ struct tvp514x_decoder { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; struct tvp514x_reg tvp514x_regs[ARRAY_SIZE(tvp514x_reg_list_default)]; const struct tvp514x_platform_data *pdata; @@ -238,6 +240,11 @@ static inline struct tvp514x_decoder *to_decoder(struct v4l2_subdev *sd) return container_of(sd, struct tvp514x_decoder, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp514x_decoder, hdl)->sd; +} + /** * tvp514x_read_reg() - Read a value from a register in an TVP5146/47. @@ -719,213 +726,54 @@ static int tvp514x_s_routing(struct v4l2_subdev *sd, } /** - * tvp514x_queryctrl() - V4L2 decoder interface handler for queryctrl - * @sd: pointer to standard V4L2 sub-device structure - * @qctrl: standard V4L2 v4l2_queryctrl structure - * - * If the requested control is supported, returns the control information. - * Otherwise, returns -EINVAL if the control is not supported. - */ -static int -tvp514x_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qctrl) -{ - int err = -EINVAL; - - if (qctrl == NULL) - return err; - - switch (qctrl->id) { - case V4L2_CID_BRIGHTNESS: - /* Brightness supported is (0-255), */ - err = v4l2_ctrl_query_fill(qctrl, 0, 255, 1, 128); - break; - case V4L2_CID_CONTRAST: - case V4L2_CID_SATURATION: - /** - * Saturation and Contrast supported is - - * Contrast: 0 - 255 (Default - 128) - * Saturation: 0 - 255 (Default - 128) - */ - err = v4l2_ctrl_query_fill(qctrl, 0, 255, 1, 128); - break; - case V4L2_CID_HUE: - /* Hue Supported is - - * Hue - -180 - +180 (Default - 0, Step - +180) - */ - err = v4l2_ctrl_query_fill(qctrl, -180, 180, 180, 0); - break; - case V4L2_CID_AUTOGAIN: - /** - * Auto Gain supported is - - * 0 - 1 (Default - 1) - */ - err = v4l2_ctrl_query_fill(qctrl, 0, 1, 1, 1); - break; - default: - v4l2_err(sd, "invalid control id %d\n", qctrl->id); - return err; - } - - v4l2_dbg(1, debug, sd, "Query Control:%s: Min - %d, Max - %d, Def - %d\n", - qctrl->name, qctrl->minimum, qctrl->maximum, - qctrl->default_value); - - return err; -} - -/** - * tvp514x_g_ctrl() - V4L2 decoder interface handler for g_ctrl - * @sd: pointer to standard V4L2 sub-device structure - * @ctrl: pointer to v4l2_control structure - * - * If the requested control is supported, returns the control's current - * value from the decoder. Otherwise, returns -EINVAL if the control is not - * supported. - */ -static int -tvp514x_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct tvp514x_decoder *decoder = to_decoder(sd); - - if (ctrl == NULL) - return -EINVAL; - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - ctrl->value = decoder->tvp514x_regs[REG_BRIGHTNESS].val; - break; - case V4L2_CID_CONTRAST: - ctrl->value = decoder->tvp514x_regs[REG_CONTRAST].val; - break; - case V4L2_CID_SATURATION: - ctrl->value = decoder->tvp514x_regs[REG_SATURATION].val; - break; - case V4L2_CID_HUE: - ctrl->value = decoder->tvp514x_regs[REG_HUE].val; - if (ctrl->value == 0x7F) - ctrl->value = 180; - else if (ctrl->value == 0x80) - ctrl->value = -180; - else - ctrl->value = 0; - - break; - case V4L2_CID_AUTOGAIN: - ctrl->value = decoder->tvp514x_regs[REG_AFE_GAIN_CTRL].val; - if ((ctrl->value & 0x3) == 3) - ctrl->value = 1; - else - ctrl->value = 0; - - break; - default: - v4l2_err(sd, "invalid control id %d\n", ctrl->id); - return -EINVAL; - } - - v4l2_dbg(1, debug, sd, "Get Control: ID - %d - %d\n", - ctrl->id, ctrl->value); - return 0; -} - -/** * tvp514x_s_ctrl() - V4L2 decoder interface handler for s_ctrl - * @sd: pointer to standard V4L2 sub-device structure - * @ctrl: pointer to v4l2_control structure + * @ctrl: pointer to v4l2_ctrl structure * * If the requested control is supported, sets the control's current * value in HW. Otherwise, returns -EINVAL if the control is not supported. */ -static int -tvp514x_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int tvp514x_s_ctrl(struct v4l2_ctrl *ctrl) { + struct v4l2_subdev *sd = to_sd(ctrl); struct tvp514x_decoder *decoder = to_decoder(sd); int err = -EINVAL, value; - if (ctrl == NULL) - return err; - - value = ctrl->value; + value = ctrl->val; switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - if (ctrl->value < 0 || ctrl->value > 255) { - v4l2_err(sd, "invalid brightness setting %d\n", - ctrl->value); - return -ERANGE; - } - err = tvp514x_write_reg(sd, REG_BRIGHTNESS, - value); - if (err) - return err; - - decoder->tvp514x_regs[REG_BRIGHTNESS].val = value; + err = tvp514x_write_reg(sd, REG_BRIGHTNESS, value); + if (!err) + decoder->tvp514x_regs[REG_BRIGHTNESS].val = value; break; case V4L2_CID_CONTRAST: - if (ctrl->value < 0 || ctrl->value > 255) { - v4l2_err(sd, "invalid contrast setting %d\n", - ctrl->value); - return -ERANGE; - } err = tvp514x_write_reg(sd, REG_CONTRAST, value); - if (err) - return err; - - decoder->tvp514x_regs[REG_CONTRAST].val = value; + if (!err) + decoder->tvp514x_regs[REG_CONTRAST].val = value; break; case V4L2_CID_SATURATION: - if (ctrl->value < 0 || ctrl->value > 255) { - v4l2_err(sd, "invalid saturation setting %d\n", - ctrl->value); - return -ERANGE; - } err = tvp514x_write_reg(sd, REG_SATURATION, value); - if (err) - return err; - - decoder->tvp514x_regs[REG_SATURATION].val = value; + if (!err) + decoder->tvp514x_regs[REG_SATURATION].val = value; break; case V4L2_CID_HUE: if (value == 180) value = 0x7F; else if (value == -180) value = 0x80; - else if (value == 0) - value = 0; - else { - v4l2_err(sd, "invalid hue setting %d\n", ctrl->value); - return -ERANGE; - } err = tvp514x_write_reg(sd, REG_HUE, value); - if (err) - return err; - - decoder->tvp514x_regs[REG_HUE].val = value; + if (!err) + decoder->tvp514x_regs[REG_HUE].val = value; break; case V4L2_CID_AUTOGAIN: - if (value == 1) - value = 0x0F; - else if (value == 0) - value = 0x0C; - else { - v4l2_err(sd, "invalid auto gain setting %d\n", - ctrl->value); - return -ERANGE; - } - err = tvp514x_write_reg(sd, REG_AFE_GAIN_CTRL, value); - if (err) - return err; - - decoder->tvp514x_regs[REG_AFE_GAIN_CTRL].val = value; + err = tvp514x_write_reg(sd, REG_AFE_GAIN_CTRL, value ? 0x0f : 0x0c); + if (!err) + decoder->tvp514x_regs[REG_AFE_GAIN_CTRL].val = value; break; - default: - v4l2_err(sd, "invalid control id %d\n", ctrl->id); - return err; } v4l2_dbg(1, debug, sd, "Set Control: ID - %d - %d\n", - ctrl->id, ctrl->value); - + ctrl->id, ctrl->val); return err; } @@ -1104,10 +952,18 @@ static int tvp514x_s_stream(struct v4l2_subdev *sd, int enable) return err; } -static const struct v4l2_subdev_core_ops tvp514x_core_ops = { - .queryctrl = tvp514x_queryctrl, - .g_ctrl = tvp514x_g_ctrl, +static const struct v4l2_ctrl_ops tvp514x_ctrl_ops = { .s_ctrl = tvp514x_s_ctrl, +}; + +static const struct v4l2_subdev_core_ops tvp514x_core_ops = { + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, .s_std = tvp514x_s_std, }; @@ -1190,6 +1046,27 @@ tvp514x_probe(struct i2c_client *client, const struct i2c_device_id *id) sd = &decoder->sd; v4l2_i2c_subdev_init(sd, client, &tvp514x_ops); + v4l2_ctrl_handler_init(&decoder->hdl, 5); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_HUE, -180, 180, 180, 0); + v4l2_ctrl_new_std(&decoder->hdl, &tvp514x_ctrl_ops, + V4L2_CID_AUTOGAIN, 0, 1, 1, 1); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); + v4l2_info(sd, "%s decoder driver registered !!\n", sd->name); return 0; @@ -1209,6 +1086,7 @@ static int tvp514x_remove(struct i2c_client *client) struct tvp514x_decoder *decoder = to_decoder(sd); v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); kfree(decoder); return 0; } diff --git a/drivers/media/video/tvp5150.c b/drivers/media/video/tvp5150.c index 58927664d3ea..959d690363bb 100644 --- a/drivers/media/video/tvp5150.c +++ b/drivers/media/video/tvp5150.c @@ -12,6 +12,7 @@ #include <media/v4l2-device.h> #include <media/tvp5150.h> #include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> #include "tvp5150_reg.h" @@ -24,58 +25,14 @@ static int debug; module_param(debug, int, 0); MODULE_PARM_DESC(debug, "Debug level (0-2)"); -/* supported controls */ -static struct v4l2_queryctrl tvp5150_qctrl[] = { - { - .id = V4L2_CID_BRIGHTNESS, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Brightness", - .minimum = 0, - .maximum = 255, - .step = 1, - .default_value = 128, - .flags = 0, - }, { - .id = V4L2_CID_CONTRAST, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Contrast", - .minimum = 0, - .maximum = 255, - .step = 0x1, - .default_value = 128, - .flags = 0, - }, { - .id = V4L2_CID_SATURATION, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Saturation", - .minimum = 0, - .maximum = 255, - .step = 0x1, - .default_value = 128, - .flags = 0, - }, { - .id = V4L2_CID_HUE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Hue", - .minimum = -128, - .maximum = 127, - .step = 0x1, - .default_value = 0, - .flags = 0, - } -}; - struct tvp5150 { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; v4l2_std_id norm; /* Current set standard */ u32 input; u32 output; int enable; - int bright; - int contrast; - int hue; - int sat; }; static inline struct tvp5150 *to_tvp5150(struct v4l2_subdev *sd) @@ -83,6 +40,11 @@ static inline struct tvp5150 *to_tvp5150(struct v4l2_subdev *sd) return container_of(sd, struct tvp5150, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp5150, hdl)->sd; +} + static int tvp5150_read(struct v4l2_subdev *sd, unsigned char addr) { struct i2c_client *c = v4l2_get_subdevdata(sd); @@ -810,64 +772,28 @@ static int tvp5150_reset(struct v4l2_subdev *sd, u32 val) tvp5150_write_inittab(sd, tvp5150_init_enable); /* Initialize image preferences */ - tvp5150_write(sd, TVP5150_BRIGHT_CTL, decoder->bright); - tvp5150_write(sd, TVP5150_CONTRAST_CTL, decoder->contrast); - tvp5150_write(sd, TVP5150_SATURATION_CTL, decoder->contrast); - tvp5150_write(sd, TVP5150_HUE_CTL, decoder->hue); + v4l2_ctrl_handler_setup(&decoder->hdl); tvp5150_set_std(sd, decoder->norm); return 0; }; -static int tvp5150_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - v4l2_dbg(1, debug, sd, "g_ctrl called\n"); - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - ctrl->value = tvp5150_read(sd, TVP5150_BRIGHT_CTL); - return 0; - case V4L2_CID_CONTRAST: - ctrl->value = tvp5150_read(sd, TVP5150_CONTRAST_CTL); - return 0; - case V4L2_CID_SATURATION: - ctrl->value = tvp5150_read(sd, TVP5150_SATURATION_CTL); - return 0; - case V4L2_CID_HUE: - ctrl->value = tvp5150_read(sd, TVP5150_HUE_CTL); - return 0; - } - return -EINVAL; -} - -static int tvp5150_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int tvp5150_s_ctrl(struct v4l2_ctrl *ctrl) { - u8 i, n; - n = ARRAY_SIZE(tvp5150_qctrl); - - for (i = 0; i < n; i++) { - if (ctrl->id != tvp5150_qctrl[i].id) - continue; - if (ctrl->value < tvp5150_qctrl[i].minimum || - ctrl->value > tvp5150_qctrl[i].maximum) - return -ERANGE; - v4l2_dbg(1, debug, sd, "s_ctrl: id=%d, value=%d\n", - ctrl->id, ctrl->value); - break; - } + struct v4l2_subdev *sd = to_sd(ctrl); switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - tvp5150_write(sd, TVP5150_BRIGHT_CTL, ctrl->value); + tvp5150_write(sd, TVP5150_BRIGHT_CTL, ctrl->val); return 0; case V4L2_CID_CONTRAST: - tvp5150_write(sd, TVP5150_CONTRAST_CTL, ctrl->value); + tvp5150_write(sd, TVP5150_CONTRAST_CTL, ctrl->val); return 0; case V4L2_CID_SATURATION: - tvp5150_write(sd, TVP5150_SATURATION_CTL, ctrl->value); + tvp5150_write(sd, TVP5150_SATURATION_CTL, ctrl->val); return 0; case V4L2_CID_HUE: - tvp5150_write(sd, TVP5150_HUE_CTL, ctrl->value); + tvp5150_write(sd, TVP5150_HUE_CTL, ctrl->val); return 0; } return -EINVAL; @@ -995,29 +921,21 @@ static int tvp5150_g_tuner(struct v4l2_subdev *sd, struct v4l2_tuner *vt) return 0; } -static int tvp5150_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - int i; - - v4l2_dbg(1, debug, sd, "queryctrl called\n"); - - for (i = 0; i < ARRAY_SIZE(tvp5150_qctrl); i++) - if (qc->id && qc->id == tvp5150_qctrl[i].id) { - memcpy(qc, &(tvp5150_qctrl[i]), - sizeof(*qc)); - return 0; - } - - return -EINVAL; -} - /* ----------------------------------------------------------------------- */ +static const struct v4l2_ctrl_ops tvp5150_ctrl_ops = { + .s_ctrl = tvp5150_s_ctrl, +}; + static const struct v4l2_subdev_core_ops tvp5150_core_ops = { .log_status = tvp5150_log_status, - .g_ctrl = tvp5150_g_ctrl, - .s_ctrl = tvp5150_s_ctrl, - .queryctrl = tvp5150_queryctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, .s_std = tvp5150_s_std, .reset = tvp5150_reset, .g_chip_ident = tvp5150_g_chip_ident, @@ -1077,10 +995,25 @@ static int tvp5150_probe(struct i2c_client *c, core->norm = V4L2_STD_ALL; /* Default is autodetect */ core->input = TVP5150_COMPOSITE1; core->enable = 1; - core->bright = 128; - core->contrast = 128; - core->hue = 0; - core->sat = 128; + + v4l2_ctrl_handler_init(&core->hdl, 4); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 128); + v4l2_ctrl_new_std(&core->hdl, &tvp5150_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + sd->ctrl_handler = &core->hdl; + if (core->hdl.error) { + int err = core->hdl.error; + + v4l2_ctrl_handler_free(&core->hdl); + kfree(core); + return err; + } + v4l2_ctrl_handler_setup(&core->hdl); if (debug > 1) tvp5150_log_status(sd); @@ -1090,12 +1023,14 @@ static int tvp5150_probe(struct i2c_client *c, static int tvp5150_remove(struct i2c_client *c) { struct v4l2_subdev *sd = i2c_get_clientdata(c); + struct tvp5150 *decoder = to_tvp5150(sd); v4l2_dbg(1, debug, sd, "tvp5150.c: removing tvp5150 adapter on address 0x%x\n", c->addr << 1); v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&decoder->hdl); kfree(to_tvp5150(sd)); return 0; } diff --git a/drivers/media/video/tvp7002.c b/drivers/media/video/tvp7002.c index c799e4eb6fcd..b799851bf3d0 100644 --- a/drivers/media/video/tvp7002.c +++ b/drivers/media/video/tvp7002.c @@ -32,6 +32,7 @@ #include <media/v4l2-device.h> #include <media/v4l2-chip-ident.h> #include <media/v4l2-common.h> +#include <media/v4l2-ctrls.h> #include "tvp7002_reg.h" MODULE_DESCRIPTION("TI TVP7002 Video and Graphics Digitizer driver"); @@ -421,13 +422,13 @@ static const struct tvp7002_preset_definition tvp7002_presets[] = { /* Device definition */ struct tvp7002 { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; const struct tvp7002_config *pdata; int ver; int streaming; const struct tvp7002_preset_definition *current_preset; - u8 gain; }; /* @@ -441,6 +442,11 @@ static inline struct tvp7002 *to_tvp7002(struct v4l2_subdev *sd) return container_of(sd, struct tvp7002, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct tvp7002, hdl)->sd; +} + /* * tvp7002_read - Read a value from a register in an TVP7002 * @sd: ptr to v4l2_subdev struct @@ -606,78 +612,25 @@ static int tvp7002_s_dv_preset(struct v4l2_subdev *sd, } /* - * tvp7002_g_ctrl() - Get a control - * @sd: ptr to v4l2_subdev struct - * @ctrl: ptr to v4l2_control struct - * - * Get a control for a TVP7002 decoder device. - * Returns zero when successful or -EINVAL if register access fails. - */ -static int tvp7002_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct tvp7002 *device = to_tvp7002(sd); - - switch (ctrl->id) { - case V4L2_CID_GAIN: - ctrl->value = device->gain; - return 0; - default: - return -EINVAL; - } -} - -/* * tvp7002_s_ctrl() - Set a control - * @sd: ptr to v4l2_subdev struct - * @ctrl: ptr to v4l2_control struct + * @ctrl: ptr to v4l2_ctrl struct * * Set a control in TVP7002 decoder device. * Returns zero when successful or -EINVAL if register access fails. */ -static int tvp7002_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int tvp7002_s_ctrl(struct v4l2_ctrl *ctrl) { - struct tvp7002 *device = to_tvp7002(sd); + struct v4l2_subdev *sd = to_sd(ctrl); int error = 0; switch (ctrl->id) { case V4L2_CID_GAIN: - tvp7002_write_err(sd, TVP7002_R_FINE_GAIN, - ctrl->value & 0xff, &error); - tvp7002_write_err(sd, TVP7002_G_FINE_GAIN, - ctrl->value & 0xff, &error); - tvp7002_write_err(sd, TVP7002_B_FINE_GAIN, - ctrl->value & 0xff, &error); - - if (error < 0) - return error; - - /* Set only after knowing there is no error */ - device->gain = ctrl->value & 0xff; - return 0; - default: - return -EINVAL; - } -} - -/* - * tvp7002_queryctrl() - Query a control - * @sd: ptr to v4l2_subdev struct - * @qc: ptr to v4l2_queryctrl struct - * - * Query a control of a TVP7002 decoder device. - * Returns zero when successful or -EINVAL if register read fails. - */ -static int tvp7002_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_GAIN: - /* - * Gain is supported [0-255, default=0, step=1] - */ - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 0); - default: - return -EINVAL; + tvp7002_write_err(sd, TVP7002_R_FINE_GAIN, ctrl->val, &error); + tvp7002_write_err(sd, TVP7002_G_FINE_GAIN, ctrl->val, &error); + tvp7002_write_err(sd, TVP7002_B_FINE_GAIN, ctrl->val, &error); + return error; } + return -EINVAL; } /* @@ -924,7 +877,7 @@ static int tvp7002_log_status(struct v4l2_subdev *sd) device->streaming ? "yes" : "no"); /* Print the current value of the gain control */ - v4l2_info(sd, "Gain: %u\n", device->gain); + v4l2_ctrl_handler_log_status(&device->hdl, sd->name); return 0; } @@ -946,13 +899,21 @@ static int tvp7002_enum_dv_presets(struct v4l2_subdev *sd, return v4l_fill_dv_preset_info(tvp7002_presets[preset->index].preset, preset); } +static const struct v4l2_ctrl_ops tvp7002_ctrl_ops = { + .s_ctrl = tvp7002_s_ctrl, +}; + /* V4L2 core operation handlers */ static const struct v4l2_subdev_core_ops tvp7002_core_ops = { .g_chip_ident = tvp7002_g_chip_ident, .log_status = tvp7002_log_status, - .g_ctrl = tvp7002_g_ctrl, - .s_ctrl = tvp7002_s_ctrl, - .queryctrl = tvp7002_queryctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, #ifdef CONFIG_VIDEO_ADV_DEBUG .g_register = tvp7002_g_register, .s_register = tvp7002_s_register, @@ -977,12 +938,6 @@ static const struct v4l2_subdev_ops tvp7002_ops = { .video = &tvp7002_video_ops, }; -static struct tvp7002 tvp7002_dev = { - .streaming = 0, - .current_preset = tvp7002_presets, - .gain = 0, -}; - /* * tvp7002_probe - Probe a TVP7002 device * @c: ptr to i2c_client struct @@ -1013,14 +968,14 @@ static int tvp7002_probe(struct i2c_client *c, const struct i2c_device_id *id) return -ENODEV; } - device = kmalloc(sizeof(struct tvp7002), GFP_KERNEL); + device = kzalloc(sizeof(struct tvp7002), GFP_KERNEL); if (!device) return -ENOMEM; - *device = tvp7002_dev; sd = &device->sd; device->pdata = c->dev.platform_data; + device->current_preset = tvp7002_presets; /* Tell v4l2 the device is ready */ v4l2_i2c_subdev_init(sd, c, &tvp7002_ops); @@ -1060,6 +1015,19 @@ static int tvp7002_probe(struct i2c_client *c, const struct i2c_device_id *id) preset.preset = device->current_preset->preset; error = tvp7002_s_dv_preset(sd, &preset); + v4l2_ctrl_handler_init(&device->hdl, 1); + v4l2_ctrl_new_std(&device->hdl, &tvp7002_ctrl_ops, + V4L2_CID_GAIN, 0, 255, 1, 0); + sd->ctrl_handler = &device->hdl; + if (device->hdl.error) { + int err = device->hdl.error; + + v4l2_ctrl_handler_free(&device->hdl); + kfree(device); + return err; + } + v4l2_ctrl_handler_setup(&device->hdl); + found_error: if (error < 0) kfree(device); @@ -1083,6 +1051,7 @@ static int tvp7002_remove(struct i2c_client *c) "on address 0x%x\n", c->addr); v4l2_device_unregister_subdev(sd); + v4l2_ctrl_handler_free(&device->hdl); kfree(device); return 0; } diff --git a/drivers/media/video/v4l2-compat-ioctl32.c b/drivers/media/video/v4l2-compat-ioctl32.c index dc82eb83c1d4..c19208a07b48 100644 --- a/drivers/media/video/v4l2-compat-ioctl32.c +++ b/drivers/media/video/v4l2-compat-ioctl32.c @@ -97,6 +97,14 @@ static inline int get_v4l2_pix_format(struct v4l2_pix_format *kp, struct v4l2_pi return 0; } +static inline int get_v4l2_pix_format_mplane(struct v4l2_pix_format_mplane *kp, + struct v4l2_pix_format_mplane __user *up) +{ + if (copy_from_user(kp, up, sizeof(struct v4l2_pix_format_mplane))) + return -EFAULT; + return 0; +} + static inline int put_v4l2_pix_format(struct v4l2_pix_format *kp, struct v4l2_pix_format __user *up) { if (copy_to_user(up, kp, sizeof(struct v4l2_pix_format))) @@ -104,6 +112,14 @@ static inline int put_v4l2_pix_format(struct v4l2_pix_format *kp, struct v4l2_pi return 0; } +static inline int put_v4l2_pix_format_mplane(struct v4l2_pix_format_mplane *kp, + struct v4l2_pix_format_mplane __user *up) +{ + if (copy_to_user(up, kp, sizeof(struct v4l2_pix_format_mplane))) + return -EFAULT; + return 0; +} + static inline int get_v4l2_vbi_format(struct v4l2_vbi_format *kp, struct v4l2_vbi_format __user *up) { if (copy_from_user(kp, up, sizeof(struct v4l2_vbi_format))) @@ -136,6 +152,7 @@ struct v4l2_format32 { enum v4l2_buf_type type; union { struct v4l2_pix_format pix; + struct v4l2_pix_format_mplane pix_mp; struct v4l2_window32 win; struct v4l2_vbi_format vbi; struct v4l2_sliced_vbi_format sliced; @@ -152,6 +169,10 @@ static int get_v4l2_format32(struct v4l2_format *kp, struct v4l2_format32 __user case V4L2_BUF_TYPE_VIDEO_CAPTURE: case V4L2_BUF_TYPE_VIDEO_OUTPUT: return get_v4l2_pix_format(&kp->fmt.pix, &up->fmt.pix); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return get_v4l2_pix_format_mplane(&kp->fmt.pix_mp, + &up->fmt.pix_mp); case V4L2_BUF_TYPE_VIDEO_OVERLAY: case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: return get_v4l2_window32(&kp->fmt.win, &up->fmt.win); @@ -181,6 +202,10 @@ static int put_v4l2_format32(struct v4l2_format *kp, struct v4l2_format32 __user case V4L2_BUF_TYPE_VIDEO_CAPTURE: case V4L2_BUF_TYPE_VIDEO_OUTPUT: return put_v4l2_pix_format(&kp->fmt.pix, &up->fmt.pix); + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + return put_v4l2_pix_format_mplane(&kp->fmt.pix_mp, + &up->fmt.pix_mp); case V4L2_BUF_TYPE_VIDEO_OVERLAY: case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: return put_v4l2_window32(&kp->fmt.win, &up->fmt.win); @@ -232,6 +257,17 @@ static int put_v4l2_standard32(struct v4l2_standard *kp, struct v4l2_standard32 return 0; } +struct v4l2_plane32 { + __u32 bytesused; + __u32 length; + union { + __u32 mem_offset; + compat_long_t userptr; + } m; + __u32 data_offset; + __u32 reserved[11]; +}; + struct v4l2_buffer32 { __u32 index; enum v4l2_buf_type type; @@ -247,14 +283,64 @@ struct v4l2_buffer32 { union { __u32 offset; compat_long_t userptr; + compat_caddr_t planes; } m; __u32 length; __u32 input; __u32 reserved; }; +static int get_v4l2_plane32(struct v4l2_plane *up, struct v4l2_plane32 *up32, + enum v4l2_memory memory) +{ + void __user *up_pln; + compat_long_t p; + + if (copy_in_user(up, up32, 2 * sizeof(__u32)) || + copy_in_user(&up->data_offset, &up32->data_offset, + sizeof(__u32))) + return -EFAULT; + + if (memory == V4L2_MEMORY_USERPTR) { + if (get_user(p, &up32->m.userptr)) + return -EFAULT; + up_pln = compat_ptr(p); + if (put_user((unsigned long)up_pln, &up->m.userptr)) + return -EFAULT; + } else { + if (copy_in_user(&up->m.mem_offset, &up32->m.mem_offset, + sizeof(__u32))) + return -EFAULT; + } + + return 0; +} + +static int put_v4l2_plane32(struct v4l2_plane *up, struct v4l2_plane32 *up32, + enum v4l2_memory memory) +{ + if (copy_in_user(up32, up, 2 * sizeof(__u32)) || + copy_in_user(&up32->data_offset, &up->data_offset, + sizeof(__u32))) + return -EFAULT; + + /* For MMAP, driver might've set up the offset, so copy it back. + * USERPTR stays the same (was userspace-provided), so no copying. */ + if (memory == V4L2_MEMORY_MMAP) + if (copy_in_user(&up32->m.mem_offset, &up->m.mem_offset, + sizeof(__u32))) + return -EFAULT; + + return 0; +} + static int get_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user *up) { + struct v4l2_plane32 __user *uplane32; + struct v4l2_plane __user *uplane; + compat_caddr_t p; + int num_planes; + int ret; if (!access_ok(VERIFY_READ, up, sizeof(struct v4l2_buffer32)) || get_user(kp->index, &up->index) || @@ -263,33 +349,84 @@ static int get_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user get_user(kp->memory, &up->memory) || get_user(kp->input, &up->input)) return -EFAULT; - switch (kp->memory) { - case V4L2_MEMORY_MMAP: - if (get_user(kp->length, &up->length) || - get_user(kp->m.offset, &up->m.offset)) + + if (V4L2_TYPE_IS_OUTPUT(kp->type)) + if (get_user(kp->bytesused, &up->bytesused) || + get_user(kp->field, &up->field) || + get_user(kp->timestamp.tv_sec, &up->timestamp.tv_sec) || + get_user(kp->timestamp.tv_usec, + &up->timestamp.tv_usec)) return -EFAULT; - break; - case V4L2_MEMORY_USERPTR: - { - compat_long_t tmp; - if (get_user(kp->length, &up->length) || - get_user(tmp, &up->m.userptr)) + if (V4L2_TYPE_IS_MULTIPLANAR(kp->type)) { + if (get_user(kp->length, &up->length)) return -EFAULT; - kp->m.userptr = (unsigned long)compat_ptr(tmp); + num_planes = kp->length; + if (num_planes == 0) { + kp->m.planes = NULL; + /* num_planes == 0 is legal, e.g. when userspace doesn't + * need planes array on DQBUF*/ + return 0; } - break; - case V4L2_MEMORY_OVERLAY: - if (get_user(kp->m.offset, &up->m.offset)) + + if (get_user(p, &up->m.planes)) return -EFAULT; - break; + + uplane32 = compat_ptr(p); + if (!access_ok(VERIFY_READ, uplane32, + num_planes * sizeof(struct v4l2_plane32))) + return -EFAULT; + + /* We don't really care if userspace decides to kill itself + * by passing a very big num_planes value */ + uplane = compat_alloc_user_space(num_planes * + sizeof(struct v4l2_plane)); + kp->m.planes = uplane; + + while (--num_planes >= 0) { + ret = get_v4l2_plane32(uplane, uplane32, kp->memory); + if (ret) + return ret; + ++uplane; + ++uplane32; + } + } else { + switch (kp->memory) { + case V4L2_MEMORY_MMAP: + if (get_user(kp->length, &up->length) || + get_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + case V4L2_MEMORY_USERPTR: + { + compat_long_t tmp; + + if (get_user(kp->length, &up->length) || + get_user(tmp, &up->m.userptr)) + return -EFAULT; + + kp->m.userptr = (unsigned long)compat_ptr(tmp); + } + break; + case V4L2_MEMORY_OVERLAY: + if (get_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + } } + return 0; } static int put_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user *up) { + struct v4l2_plane32 __user *uplane32; + struct v4l2_plane __user *uplane; + compat_caddr_t p; + int num_planes; + int ret; + if (!access_ok(VERIFY_WRITE, up, sizeof(struct v4l2_buffer32)) || put_user(kp->index, &up->index) || put_user(kp->type, &up->type) || @@ -297,22 +434,7 @@ static int put_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user put_user(kp->memory, &up->memory) || put_user(kp->input, &up->input)) return -EFAULT; - switch (kp->memory) { - case V4L2_MEMORY_MMAP: - if (put_user(kp->length, &up->length) || - put_user(kp->m.offset, &up->m.offset)) - return -EFAULT; - break; - case V4L2_MEMORY_USERPTR: - if (put_user(kp->length, &up->length) || - put_user(kp->m.userptr, &up->m.userptr)) - return -EFAULT; - break; - case V4L2_MEMORY_OVERLAY: - if (put_user(kp->m.offset, &up->m.offset)) - return -EFAULT; - break; - } + if (put_user(kp->bytesused, &up->bytesused) || put_user(kp->field, &up->field) || put_user(kp->timestamp.tv_sec, &up->timestamp.tv_sec) || @@ -321,6 +443,43 @@ static int put_v4l2_buffer32(struct v4l2_buffer *kp, struct v4l2_buffer32 __user put_user(kp->sequence, &up->sequence) || put_user(kp->reserved, &up->reserved)) return -EFAULT; + + if (V4L2_TYPE_IS_MULTIPLANAR(kp->type)) { + num_planes = kp->length; + if (num_planes == 0) + return 0; + + uplane = kp->m.planes; + if (get_user(p, &up->m.planes)) + return -EFAULT; + uplane32 = compat_ptr(p); + + while (--num_planes >= 0) { + ret = put_v4l2_plane32(uplane, uplane32, kp->memory); + if (ret) + return ret; + ++uplane; + ++uplane32; + } + } else { + switch (kp->memory) { + case V4L2_MEMORY_MMAP: + if (put_user(kp->length, &up->length) || + put_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + case V4L2_MEMORY_USERPTR: + if (put_user(kp->length, &up->length) || + put_user(kp->m.userptr, &up->m.userptr)) + return -EFAULT; + break; + case V4L2_MEMORY_OVERLAY: + if (put_user(kp->m.offset, &up->m.offset)) + return -EFAULT; + break; + } + } + return 0; } @@ -442,12 +601,13 @@ static int get_v4l2_ext_controls32(struct v4l2_ext_controls *kp, struct v4l2_ext if (get_user(p, &up->controls)) return -EFAULT; ucontrols = compat_ptr(p); - if (!access_ok(VERIFY_READ, ucontrols, n * sizeof(struct v4l2_ext_control))) + if (!access_ok(VERIFY_READ, ucontrols, + n * sizeof(struct v4l2_ext_control32))) return -EFAULT; kcontrols = compat_alloc_user_space(n * sizeof(struct v4l2_ext_control)); kp->controls = kcontrols; while (--n >= 0) { - if (copy_in_user(kcontrols, ucontrols, sizeof(*kcontrols))) + if (copy_in_user(kcontrols, ucontrols, sizeof(*ucontrols))) return -EFAULT; if (ctrl_is_pointer(kcontrols->id)) { void __user *s; @@ -483,7 +643,8 @@ static int put_v4l2_ext_controls32(struct v4l2_ext_controls *kp, struct v4l2_ext if (get_user(p, &up->controls)) return -EFAULT; ucontrols = compat_ptr(p); - if (!access_ok(VERIFY_WRITE, ucontrols, n * sizeof(struct v4l2_ext_control))) + if (!access_ok(VERIFY_WRITE, ucontrols, + n * sizeof(struct v4l2_ext_control32))) return -EFAULT; while (--n >= 0) { diff --git a/drivers/media/video/v4l2-ioctl.c b/drivers/media/video/v4l2-ioctl.c index f51327ef6757..8360ed2d933a 100644 --- a/drivers/media/video/v4l2-ioctl.c +++ b/drivers/media/video/v4l2-ioctl.c @@ -165,6 +165,8 @@ const char *v4l2_type_names[] = { [V4L2_BUF_TYPE_SLICED_VBI_CAPTURE] = "sliced-vbi-cap", [V4L2_BUF_TYPE_SLICED_VBI_OUTPUT] = "sliced-vbi-out", [V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY] = "vid-out-overlay", + [V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE] = "vid-cap-mplane", + [V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE] = "vid-out-mplane", }; EXPORT_SYMBOL(v4l2_type_names); @@ -426,20 +428,33 @@ static void dbgbuf(unsigned int cmd, struct video_device *vfd, struct v4l2_buffer *p) { struct v4l2_timecode *tc = &p->timecode; + struct v4l2_plane *plane; + int i; dbgarg(cmd, "%02ld:%02d:%02d.%08ld index=%d, type=%s, " - "bytesused=%d, flags=0x%08d, " - "field=%0d, sequence=%d, memory=%s, offset/userptr=0x%08lx, length=%d\n", + "flags=0x%08d, field=%0d, sequence=%d, memory=%s\n", p->timestamp.tv_sec / 3600, (int)(p->timestamp.tv_sec / 60) % 60, (int)(p->timestamp.tv_sec % 60), (long)p->timestamp.tv_usec, p->index, prt_names(p->type, v4l2_type_names), - p->bytesused, p->flags, - p->field, p->sequence, - prt_names(p->memory, v4l2_memory_names), - p->m.userptr, p->length); + p->flags, p->field, p->sequence, + prt_names(p->memory, v4l2_memory_names)); + + if (V4L2_TYPE_IS_MULTIPLANAR(p->type) && p->m.planes) { + for (i = 0; i < p->length; ++i) { + plane = &p->m.planes[i]; + dbgarg2("plane %d: bytesused=%d, data_offset=0x%08x " + "offset/userptr=0x%08lx, length=%d\n", + i, plane->bytesused, plane->data_offset, + plane->m.userptr, plane->length); + } + } else { + dbgarg2("bytesused=%d, offset/userptr=0x%08lx, length=%d\n", + p->bytesused, p->m.userptr, p->length); + } + dbgarg2("timecode=%02d:%02d:%02d type=%d, " "flags=0x%08d, frames=%d, userbits=0x%08x\n", tc->hours, tc->minutes, tc->seconds, @@ -467,6 +482,27 @@ static inline void v4l_print_pix_fmt(struct video_device *vfd, fmt->bytesperline, fmt->sizeimage, fmt->colorspace); }; +static inline void v4l_print_pix_fmt_mplane(struct video_device *vfd, + struct v4l2_pix_format_mplane *fmt) +{ + int i; + + dbgarg2("width=%d, height=%d, format=%c%c%c%c, field=%s, " + "colorspace=%d, num_planes=%d\n", + fmt->width, fmt->height, + (fmt->pixelformat & 0xff), + (fmt->pixelformat >> 8) & 0xff, + (fmt->pixelformat >> 16) & 0xff, + (fmt->pixelformat >> 24) & 0xff, + prt_names(fmt->field, v4l2_field_names), + fmt->colorspace, fmt->num_planes); + + for (i = 0; i < fmt->num_planes; ++i) + dbgarg2("plane %d: bytesperline=%d sizeimage=%d\n", i, + fmt->plane_fmt[i].bytesperline, + fmt->plane_fmt[i].sizeimage); +} + static inline void v4l_print_ext_ctrls(unsigned int cmd, struct video_device *vfd, struct v4l2_ext_controls *c, int show_vals) { @@ -520,7 +556,12 @@ static int check_fmt(const struct v4l2_ioctl_ops *ops, enum v4l2_buf_type type) switch (type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: - if (ops->vidioc_g_fmt_vid_cap) + if (ops->vidioc_g_fmt_vid_cap || + ops->vidioc_g_fmt_vid_cap_mplane) + return 0; + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (ops->vidioc_g_fmt_vid_cap_mplane) return 0; break; case V4L2_BUF_TYPE_VIDEO_OVERLAY: @@ -528,7 +569,12 @@ static int check_fmt(const struct v4l2_ioctl_ops *ops, enum v4l2_buf_type type) return 0; break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: - if (ops->vidioc_g_fmt_vid_out) + if (ops->vidioc_g_fmt_vid_out || + ops->vidioc_g_fmt_vid_out_mplane) + return 0; + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (ops->vidioc_g_fmt_vid_out_mplane) return 0; break; case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: @@ -559,12 +605,70 @@ static int check_fmt(const struct v4l2_ioctl_ops *ops, enum v4l2_buf_type type) return -EINVAL; } +/** + * fmt_sp_to_mp() - Convert a single-plane format to its multi-planar 1-plane + * equivalent + */ +static int fmt_sp_to_mp(const struct v4l2_format *f_sp, + struct v4l2_format *f_mp) +{ + struct v4l2_pix_format_mplane *pix_mp = &f_mp->fmt.pix_mp; + const struct v4l2_pix_format *pix = &f_sp->fmt.pix; + + if (f_sp->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) + f_mp->type = V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE; + else if (f_sp->type == V4L2_BUF_TYPE_VIDEO_OUTPUT) + f_mp->type = V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE; + else + return -EINVAL; + + pix_mp->width = pix->width; + pix_mp->height = pix->height; + pix_mp->pixelformat = pix->pixelformat; + pix_mp->field = pix->field; + pix_mp->colorspace = pix->colorspace; + pix_mp->num_planes = 1; + pix_mp->plane_fmt[0].sizeimage = pix->sizeimage; + pix_mp->plane_fmt[0].bytesperline = pix->bytesperline; + + return 0; +} + +/** + * fmt_mp_to_sp() - Convert a multi-planar 1-plane format to its single-planar + * equivalent + */ +static int fmt_mp_to_sp(const struct v4l2_format *f_mp, + struct v4l2_format *f_sp) +{ + const struct v4l2_pix_format_mplane *pix_mp = &f_mp->fmt.pix_mp; + struct v4l2_pix_format *pix = &f_sp->fmt.pix; + + if (f_mp->type == V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE) + f_sp->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + else if (f_mp->type == V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE) + f_sp->type = V4L2_BUF_TYPE_VIDEO_OUTPUT; + else + return -EINVAL; + + pix->width = pix_mp->width; + pix->height = pix_mp->height; + pix->pixelformat = pix_mp->pixelformat; + pix->field = pix_mp->field; + pix->colorspace = pix_mp->colorspace; + pix->sizeimage = pix_mp->plane_fmt[0].sizeimage; + pix->bytesperline = pix_mp->plane_fmt[0].bytesperline; + + return 0; +} + static long __video_do_ioctl(struct file *file, unsigned int cmd, void *arg) { struct video_device *vfd = video_devdata(file); const struct v4l2_ioctl_ops *ops = vfd->ioctl_ops; void *fh = file->private_data; + struct v4l2_format f_copy; long ret = -EINVAL; if (ops == NULL) { @@ -633,6 +737,11 @@ static long __video_do_ioctl(struct file *file, if (ops->vidioc_enum_fmt_vid_cap) ret = ops->vidioc_enum_fmt_vid_cap(file, fh, f); break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (ops->vidioc_enum_fmt_vid_cap_mplane) + ret = ops->vidioc_enum_fmt_vid_cap_mplane(file, + fh, f); + break; case V4L2_BUF_TYPE_VIDEO_OVERLAY: if (ops->vidioc_enum_fmt_vid_overlay) ret = ops->vidioc_enum_fmt_vid_overlay(file, @@ -642,6 +751,11 @@ static long __video_do_ioctl(struct file *file, if (ops->vidioc_enum_fmt_vid_out) ret = ops->vidioc_enum_fmt_vid_out(file, fh, f); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (ops->vidioc_enum_fmt_vid_out_mplane) + ret = ops->vidioc_enum_fmt_vid_out_mplane(file, + fh, f); + break; case V4L2_BUF_TYPE_PRIVATE: if (ops->vidioc_enum_fmt_type_private) ret = ops->vidioc_enum_fmt_type_private(file, @@ -670,22 +784,90 @@ static long __video_do_ioctl(struct file *file, switch (f->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: - if (ops->vidioc_g_fmt_vid_cap) + if (ops->vidioc_g_fmt_vid_cap) { ret = ops->vidioc_g_fmt_vid_cap(file, fh, f); + } else if (ops->vidioc_g_fmt_vid_cap_mplane) { + if (fmt_sp_to_mp(f, &f_copy)) + break; + ret = ops->vidioc_g_fmt_vid_cap_mplane(file, fh, + &f_copy); + if (ret) + break; + + /* Driver is currently in multi-planar format, + * we can't return it in single-planar API*/ + if (f_copy.fmt.pix_mp.num_planes > 1) { + ret = -EBUSY; + break; + } + + ret = fmt_mp_to_sp(&f_copy, f); + } if (!ret) v4l_print_pix_fmt(vfd, &f->fmt.pix); break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + if (ops->vidioc_g_fmt_vid_cap_mplane) { + ret = ops->vidioc_g_fmt_vid_cap_mplane(file, + fh, f); + } else if (ops->vidioc_g_fmt_vid_cap) { + if (fmt_mp_to_sp(f, &f_copy)) + break; + ret = ops->vidioc_g_fmt_vid_cap(file, + fh, &f_copy); + if (ret) + break; + + ret = fmt_sp_to_mp(&f_copy, f); + } + if (!ret) + v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp); + break; case V4L2_BUF_TYPE_VIDEO_OVERLAY: if (ops->vidioc_g_fmt_vid_overlay) ret = ops->vidioc_g_fmt_vid_overlay(file, fh, f); break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: - if (ops->vidioc_g_fmt_vid_out) + if (ops->vidioc_g_fmt_vid_out) { ret = ops->vidioc_g_fmt_vid_out(file, fh, f); + } else if (ops->vidioc_g_fmt_vid_out_mplane) { + if (fmt_sp_to_mp(f, &f_copy)) + break; + ret = ops->vidioc_g_fmt_vid_out_mplane(file, fh, + &f_copy); + if (ret) + break; + + /* Driver is currently in multi-planar format, + * we can't return it in single-planar API*/ + if (f_copy.fmt.pix_mp.num_planes > 1) { + ret = -EBUSY; + break; + } + + ret = fmt_mp_to_sp(&f_copy, f); + } if (!ret) v4l_print_pix_fmt(vfd, &f->fmt.pix); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + if (ops->vidioc_g_fmt_vid_out_mplane) { + ret = ops->vidioc_g_fmt_vid_out_mplane(file, + fh, f); + } else if (ops->vidioc_g_fmt_vid_out) { + if (fmt_mp_to_sp(f, &f_copy)) + break; + ret = ops->vidioc_g_fmt_vid_out(file, + fh, &f_copy); + if (ret) + break; + + ret = fmt_sp_to_mp(&f_copy, f); + } + if (!ret) + v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp); + break; case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: if (ops->vidioc_g_fmt_vid_out_overlay) ret = ops->vidioc_g_fmt_vid_out_overlay(file, @@ -729,8 +911,44 @@ static long __video_do_ioctl(struct file *file, case V4L2_BUF_TYPE_VIDEO_CAPTURE: CLEAR_AFTER_FIELD(f, fmt.pix); v4l_print_pix_fmt(vfd, &f->fmt.pix); - if (ops->vidioc_s_fmt_vid_cap) + if (ops->vidioc_s_fmt_vid_cap) { ret = ops->vidioc_s_fmt_vid_cap(file, fh, f); + } else if (ops->vidioc_s_fmt_vid_cap_mplane) { + if (fmt_sp_to_mp(f, &f_copy)) + break; + ret = ops->vidioc_s_fmt_vid_cap_mplane(file, fh, + &f_copy); + if (ret) + break; + + if (f_copy.fmt.pix_mp.num_planes > 1) { + /* Drivers shouldn't adjust from 1-plane + * to more than 1-plane formats */ + ret = -EBUSY; + WARN_ON(1); + break; + } + + ret = fmt_mp_to_sp(&f_copy, f); + } + break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + CLEAR_AFTER_FIELD(f, fmt.pix_mp); + v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp); + if (ops->vidioc_s_fmt_vid_cap_mplane) { + ret = ops->vidioc_s_fmt_vid_cap_mplane(file, + fh, f); + } else if (ops->vidioc_s_fmt_vid_cap && + f->fmt.pix_mp.num_planes == 1) { + if (fmt_mp_to_sp(f, &f_copy)) + break; + ret = ops->vidioc_s_fmt_vid_cap(file, + fh, &f_copy); + if (ret) + break; + + ret = fmt_sp_to_mp(&f_copy, f); + } break; case V4L2_BUF_TYPE_VIDEO_OVERLAY: CLEAR_AFTER_FIELD(f, fmt.win); @@ -741,8 +959,44 @@ static long __video_do_ioctl(struct file *file, case V4L2_BUF_TYPE_VIDEO_OUTPUT: CLEAR_AFTER_FIELD(f, fmt.pix); v4l_print_pix_fmt(vfd, &f->fmt.pix); - if (ops->vidioc_s_fmt_vid_out) + if (ops->vidioc_s_fmt_vid_out) { ret = ops->vidioc_s_fmt_vid_out(file, fh, f); + } else if (ops->vidioc_s_fmt_vid_out_mplane) { + if (fmt_sp_to_mp(f, &f_copy)) + break; + ret = ops->vidioc_s_fmt_vid_out_mplane(file, fh, + &f_copy); + if (ret) + break; + + if (f_copy.fmt.pix_mp.num_planes > 1) { + /* Drivers shouldn't adjust from 1-plane + * to more than 1-plane formats */ + ret = -EBUSY; + WARN_ON(1); + break; + } + + ret = fmt_mp_to_sp(&f_copy, f); + } + break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + CLEAR_AFTER_FIELD(f, fmt.pix_mp); + v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp); + if (ops->vidioc_s_fmt_vid_out_mplane) { + ret = ops->vidioc_s_fmt_vid_out_mplane(file, + fh, f); + } else if (ops->vidioc_s_fmt_vid_out && + f->fmt.pix_mp.num_planes == 1) { + if (fmt_mp_to_sp(f, &f_copy)) + break; + ret = ops->vidioc_s_fmt_vid_out(file, + fh, &f_copy); + if (ret) + break; + + ret = fmt_mp_to_sp(&f_copy, f); + } break; case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: CLEAR_AFTER_FIELD(f, fmt.win); @@ -791,11 +1045,47 @@ static long __video_do_ioctl(struct file *file, switch (f->type) { case V4L2_BUF_TYPE_VIDEO_CAPTURE: CLEAR_AFTER_FIELD(f, fmt.pix); - if (ops->vidioc_try_fmt_vid_cap) + if (ops->vidioc_try_fmt_vid_cap) { ret = ops->vidioc_try_fmt_vid_cap(file, fh, f); + } else if (ops->vidioc_try_fmt_vid_cap_mplane) { + if (fmt_sp_to_mp(f, &f_copy)) + break; + ret = ops->vidioc_try_fmt_vid_cap_mplane(file, + fh, &f_copy); + if (ret) + break; + + if (f_copy.fmt.pix_mp.num_planes > 1) { + /* Drivers shouldn't adjust from 1-plane + * to more than 1-plane formats */ + ret = -EBUSY; + WARN_ON(1); + break; + } + ret = fmt_mp_to_sp(&f_copy, f); + } if (!ret) v4l_print_pix_fmt(vfd, &f->fmt.pix); break; + case V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE: + CLEAR_AFTER_FIELD(f, fmt.pix_mp); + if (ops->vidioc_try_fmt_vid_cap_mplane) { + ret = ops->vidioc_try_fmt_vid_cap_mplane(file, + fh, f); + } else if (ops->vidioc_try_fmt_vid_cap && + f->fmt.pix_mp.num_planes == 1) { + if (fmt_mp_to_sp(f, &f_copy)) + break; + ret = ops->vidioc_try_fmt_vid_cap(file, + fh, &f_copy); + if (ret) + break; + + ret = fmt_sp_to_mp(&f_copy, f); + } + if (!ret) + v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp); + break; case V4L2_BUF_TYPE_VIDEO_OVERLAY: CLEAR_AFTER_FIELD(f, fmt.win); if (ops->vidioc_try_fmt_vid_overlay) @@ -804,11 +1094,47 @@ static long __video_do_ioctl(struct file *file, break; case V4L2_BUF_TYPE_VIDEO_OUTPUT: CLEAR_AFTER_FIELD(f, fmt.pix); - if (ops->vidioc_try_fmt_vid_out) + if (ops->vidioc_try_fmt_vid_out) { ret = ops->vidioc_try_fmt_vid_out(file, fh, f); + } else if (ops->vidioc_try_fmt_vid_out_mplane) { + if (fmt_sp_to_mp(f, &f_copy)) + break; + ret = ops->vidioc_try_fmt_vid_out_mplane(file, + fh, &f_copy); + if (ret) + break; + + if (f_copy.fmt.pix_mp.num_planes > 1) { + /* Drivers shouldn't adjust from 1-plane + * to more than 1-plane formats */ + ret = -EBUSY; + WARN_ON(1); + break; + } + ret = fmt_mp_to_sp(&f_copy, f); + } if (!ret) v4l_print_pix_fmt(vfd, &f->fmt.pix); break; + case V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE: + CLEAR_AFTER_FIELD(f, fmt.pix_mp); + if (ops->vidioc_try_fmt_vid_out_mplane) { + ret = ops->vidioc_try_fmt_vid_out_mplane(file, + fh, f); + } else if (ops->vidioc_try_fmt_vid_out && + f->fmt.pix_mp.num_planes == 1) { + if (fmt_mp_to_sp(f, &f_copy)) + break; + ret = ops->vidioc_try_fmt_vid_out(file, + fh, &f_copy); + if (ret) + break; + + ret = fmt_sp_to_mp(&f_copy, f); + } + if (!ret) + v4l_print_pix_fmt_mplane(vfd, &f->fmt.pix_mp); + break; case V4L2_BUF_TYPE_VIDEO_OUTPUT_OVERLAY: CLEAR_AFTER_FIELD(f, fmt.win); if (ops->vidioc_try_fmt_vid_out_overlay) @@ -1973,7 +2299,7 @@ static unsigned long cmd_input_size(unsigned int cmd) switch (cmd) { CMDINSIZE(ENUM_FMT, fmtdesc, type); CMDINSIZE(G_FMT, format, type); - CMDINSIZE(QUERYBUF, buffer, type); + CMDINSIZE(QUERYBUF, buffer, length); CMDINSIZE(G_PARM, streamparm, type); CMDINSIZE(ENUMSTD, standard, index); CMDINSIZE(ENUMINPUT, input, index); @@ -1998,6 +2324,49 @@ static unsigned long cmd_input_size(unsigned int cmd) } } +static int check_array_args(unsigned int cmd, void *parg, size_t *array_size, + void * __user *user_ptr, void ***kernel_ptr) +{ + int ret = 0; + + switch (cmd) { + case VIDIOC_QUERYBUF: + case VIDIOC_QBUF: + case VIDIOC_DQBUF: { + struct v4l2_buffer *buf = parg; + + if (V4L2_TYPE_IS_MULTIPLANAR(buf->type) && buf->length > 0) { + if (buf->length > VIDEO_MAX_PLANES) { + ret = -EINVAL; + break; + } + *user_ptr = (void __user *)buf->m.planes; + *kernel_ptr = (void **)&buf->m.planes; + *array_size = sizeof(struct v4l2_plane) * buf->length; + ret = 1; + } + break; + } + + case VIDIOC_S_EXT_CTRLS: + case VIDIOC_G_EXT_CTRLS: + case VIDIOC_TRY_EXT_CTRLS: { + struct v4l2_ext_controls *ctrls = parg; + + if (ctrls->count != 0) { + *user_ptr = (void __user *)ctrls->controls; + *kernel_ptr = (void **)&ctrls->controls; + *array_size = sizeof(struct v4l2_ext_control) + * ctrls->count; + ret = 1; + } + break; + } + } + + return ret; +} + long video_ioctl2(struct file *file, unsigned int cmd, unsigned long arg) { @@ -2005,16 +2374,14 @@ long video_ioctl2(struct file *file, void *mbuf = NULL; void *parg = (void *)arg; long err = -EINVAL; - int is_ext_ctrl; - size_t ctrls_size = 0; + bool has_array_args; + size_t array_size = 0; void __user *user_ptr = NULL; + void **kernel_ptr = NULL; #ifdef __OLD_VIDIOC_ cmd = video_fix_command(cmd); #endif - is_ext_ctrl = (cmd == VIDIOC_S_EXT_CTRLS || cmd == VIDIOC_G_EXT_CTRLS || - cmd == VIDIOC_TRY_EXT_CTRLS); - /* Copy arguments into temp kernel buffer */ if (_IOC_DIR(cmd) != _IOC_NONE) { if (_IOC_SIZE(cmd) <= sizeof(sbuf)) { @@ -2043,43 +2410,43 @@ long video_ioctl2(struct file *file, } } - if (is_ext_ctrl) { - struct v4l2_ext_controls *p = parg; + err = check_array_args(cmd, parg, &array_size, &user_ptr, &kernel_ptr); + if (err < 0) + goto out; + has_array_args = err; - /* In case of an error, tell the caller that it wasn't - a specific control that caused it. */ - p->error_idx = p->count; - user_ptr = (void __user *)p->controls; - if (p->count) { - ctrls_size = sizeof(struct v4l2_ext_control) * p->count; - /* Note: v4l2_ext_controls fits in sbuf[] so mbuf is still NULL. */ - mbuf = kmalloc(ctrls_size, GFP_KERNEL); - err = -ENOMEM; - if (NULL == mbuf) - goto out_ext_ctrl; - err = -EFAULT; - if (copy_from_user(mbuf, user_ptr, ctrls_size)) - goto out_ext_ctrl; - p->controls = mbuf; - } + if (has_array_args) { + /* + * When adding new types of array args, make sure that the + * parent argument to ioctl (which contains the pointer to the + * array) fits into sbuf (so that mbuf will still remain + * unused up to here). + */ + mbuf = kmalloc(array_size, GFP_KERNEL); + err = -ENOMEM; + if (NULL == mbuf) + goto out_array_args; + err = -EFAULT; + if (copy_from_user(mbuf, user_ptr, array_size)) + goto out_array_args; + *kernel_ptr = mbuf; } /* Handles IOCTL */ err = __video_do_ioctl(file, cmd, parg); if (err == -ENOIOCTLCMD) err = -EINVAL; - if (is_ext_ctrl) { - struct v4l2_ext_controls *p = parg; - p->controls = (void *)user_ptr; - if (p->count && err == 0 && copy_to_user(user_ptr, mbuf, ctrls_size)) + if (has_array_args) { + *kernel_ptr = user_ptr; + if (copy_to_user(user_ptr, mbuf, array_size)) err = -EFAULT; - goto out_ext_ctrl; + goto out_array_args; } if (err < 0) goto out; -out_ext_ctrl: +out_array_args: /* Copy results into user buffer */ switch (_IOC_DIR(cmd)) { case _IOC_READ: diff --git a/drivers/media/video/v4l2-mem2mem.c b/drivers/media/video/v4l2-mem2mem.c index ac832a28e18e..a78e5c9be1a2 100644 --- a/drivers/media/video/v4l2-mem2mem.c +++ b/drivers/media/video/v4l2-mem2mem.c @@ -17,7 +17,7 @@ #include <linux/sched.h> #include <linux/slab.h> -#include <media/videobuf-core.h> +#include <media/videobuf2-core.h> #include <media/v4l2-mem2mem.h> MODULE_DESCRIPTION("Mem to mem device framework for videobuf"); @@ -65,21 +65,16 @@ struct v4l2_m2m_dev { static struct v4l2_m2m_queue_ctx *get_queue_ctx(struct v4l2_m2m_ctx *m2m_ctx, enum v4l2_buf_type type) { - switch (type) { - case V4L2_BUF_TYPE_VIDEO_CAPTURE: - return &m2m_ctx->cap_q_ctx; - case V4L2_BUF_TYPE_VIDEO_OUTPUT: + if (V4L2_TYPE_IS_OUTPUT(type)) return &m2m_ctx->out_q_ctx; - default: - printk(KERN_ERR "Invalid buffer type\n"); - return NULL; - } + else + return &m2m_ctx->cap_q_ctx; } /** - * v4l2_m2m_get_vq() - return videobuf_queue for the given type + * v4l2_m2m_get_vq() - return vb2_queue for the given type */ -struct videobuf_queue *v4l2_m2m_get_vq(struct v4l2_m2m_ctx *m2m_ctx, +struct vb2_queue *v4l2_m2m_get_vq(struct v4l2_m2m_ctx *m2m_ctx, enum v4l2_buf_type type) { struct v4l2_m2m_queue_ctx *q_ctx; @@ -95,27 +90,20 @@ EXPORT_SYMBOL(v4l2_m2m_get_vq); /** * v4l2_m2m_next_buf() - return next buffer from the list of ready buffers */ -void *v4l2_m2m_next_buf(struct v4l2_m2m_ctx *m2m_ctx, enum v4l2_buf_type type) +void *v4l2_m2m_next_buf(struct v4l2_m2m_queue_ctx *q_ctx) { - struct v4l2_m2m_queue_ctx *q_ctx; - struct videobuf_buffer *vb = NULL; + struct v4l2_m2m_buffer *b = NULL; unsigned long flags; - q_ctx = get_queue_ctx(m2m_ctx, type); - if (!q_ctx) - return NULL; - - spin_lock_irqsave(q_ctx->q.irqlock, flags); + spin_lock_irqsave(&q_ctx->rdy_spinlock, flags); if (list_empty(&q_ctx->rdy_queue)) goto end; - vb = list_entry(q_ctx->rdy_queue.next, struct videobuf_buffer, queue); - vb->state = VIDEOBUF_ACTIVE; - + b = list_entry(q_ctx->rdy_queue.next, struct v4l2_m2m_buffer, list); end: - spin_unlock_irqrestore(q_ctx->q.irqlock, flags); - return vb; + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); + return &b->vb; } EXPORT_SYMBOL_GPL(v4l2_m2m_next_buf); @@ -123,26 +111,21 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_next_buf); * v4l2_m2m_buf_remove() - take off a buffer from the list of ready buffers and * return it */ -void *v4l2_m2m_buf_remove(struct v4l2_m2m_ctx *m2m_ctx, enum v4l2_buf_type type) +void *v4l2_m2m_buf_remove(struct v4l2_m2m_queue_ctx *q_ctx) { - struct v4l2_m2m_queue_ctx *q_ctx; - struct videobuf_buffer *vb = NULL; + struct v4l2_m2m_buffer *b = NULL; unsigned long flags; - q_ctx = get_queue_ctx(m2m_ctx, type); - if (!q_ctx) - return NULL; - - spin_lock_irqsave(q_ctx->q.irqlock, flags); + spin_lock_irqsave(&q_ctx->rdy_spinlock, flags); if (!list_empty(&q_ctx->rdy_queue)) { - vb = list_entry(q_ctx->rdy_queue.next, struct videobuf_buffer, - queue); - list_del(&vb->queue); + b = list_entry(q_ctx->rdy_queue.next, struct v4l2_m2m_buffer, + list); + list_del(&b->list); q_ctx->num_rdy--; } - spin_unlock_irqrestore(q_ctx->q.irqlock, flags); + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); - return vb; + return &b->vb; } EXPORT_SYMBOL_GPL(v4l2_m2m_buf_remove); @@ -235,20 +218,20 @@ static void v4l2_m2m_try_schedule(struct v4l2_m2m_ctx *m2m_ctx) return; } - spin_lock_irqsave(m2m_ctx->out_q_ctx.q.irqlock, flags); + spin_lock_irqsave(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); if (list_empty(&m2m_ctx->out_q_ctx.rdy_queue)) { - spin_unlock_irqrestore(m2m_ctx->out_q_ctx.q.irqlock, flags); + spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); dprintk("No input buffers available\n"); return; } if (list_empty(&m2m_ctx->cap_q_ctx.rdy_queue)) { - spin_unlock_irqrestore(m2m_ctx->out_q_ctx.q.irqlock, flags); + spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags_job); dprintk("No output buffers available\n"); return; } - spin_unlock_irqrestore(m2m_ctx->out_q_ctx.q.irqlock, flags); + spin_unlock_irqrestore(&m2m_ctx->out_q_ctx.rdy_spinlock, flags); if (m2m_dev->m2m_ops->job_ready && (!m2m_dev->m2m_ops->job_ready(m2m_ctx->priv))) { @@ -291,6 +274,7 @@ void v4l2_m2m_job_finish(struct v4l2_m2m_dev *m2m_dev, list_del(&m2m_dev->curr_ctx->queue); m2m_dev->curr_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING); + wake_up(&m2m_dev->curr_ctx->finished); m2m_dev->curr_ctx = NULL; spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); @@ -309,10 +293,10 @@ EXPORT_SYMBOL(v4l2_m2m_job_finish); int v4l2_m2m_reqbufs(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, struct v4l2_requestbuffers *reqbufs) { - struct videobuf_queue *vq; + struct vb2_queue *vq; vq = v4l2_m2m_get_vq(m2m_ctx, reqbufs->type); - return videobuf_reqbufs(vq, reqbufs); + return vb2_reqbufs(vq, reqbufs); } EXPORT_SYMBOL_GPL(v4l2_m2m_reqbufs); @@ -324,15 +308,22 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_reqbufs); int v4l2_m2m_querybuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, struct v4l2_buffer *buf) { - struct videobuf_queue *vq; - int ret; + struct vb2_queue *vq; + int ret = 0; + unsigned int i; vq = v4l2_m2m_get_vq(m2m_ctx, buf->type); - ret = videobuf_querybuf(vq, buf); - - if (buf->memory == V4L2_MEMORY_MMAP - && vq->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { - buf->m.offset += DST_QUEUE_OFF_BASE; + ret = vb2_querybuf(vq, buf); + + /* Adjust MMAP memory offsets for the CAPTURE queue */ + if (buf->memory == V4L2_MEMORY_MMAP && !V4L2_TYPE_IS_OUTPUT(vq->type)) { + if (V4L2_TYPE_IS_MULTIPLANAR(vq->type)) { + for (i = 0; i < buf->length; ++i) + buf->m.planes[i].m.mem_offset + += DST_QUEUE_OFF_BASE; + } else { + buf->m.offset += DST_QUEUE_OFF_BASE; + } } return ret; @@ -346,11 +337,11 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_querybuf); int v4l2_m2m_qbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, struct v4l2_buffer *buf) { - struct videobuf_queue *vq; + struct vb2_queue *vq; int ret; vq = v4l2_m2m_get_vq(m2m_ctx, buf->type); - ret = videobuf_qbuf(vq, buf); + ret = vb2_qbuf(vq, buf); if (!ret) v4l2_m2m_try_schedule(m2m_ctx); @@ -365,10 +356,10 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_qbuf); int v4l2_m2m_dqbuf(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, struct v4l2_buffer *buf) { - struct videobuf_queue *vq; + struct vb2_queue *vq; vq = v4l2_m2m_get_vq(m2m_ctx, buf->type); - return videobuf_dqbuf(vq, buf, file->f_flags & O_NONBLOCK); + return vb2_dqbuf(vq, buf, file->f_flags & O_NONBLOCK); } EXPORT_SYMBOL_GPL(v4l2_m2m_dqbuf); @@ -378,11 +369,11 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_dqbuf); int v4l2_m2m_streamon(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, enum v4l2_buf_type type) { - struct videobuf_queue *vq; + struct vb2_queue *vq; int ret; vq = v4l2_m2m_get_vq(m2m_ctx, type); - ret = videobuf_streamon(vq); + ret = vb2_streamon(vq, type); if (!ret) v4l2_m2m_try_schedule(m2m_ctx); @@ -396,10 +387,10 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_streamon); int v4l2_m2m_streamoff(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, enum v4l2_buf_type type) { - struct videobuf_queue *vq; + struct vb2_queue *vq; vq = v4l2_m2m_get_vq(m2m_ctx, type); - return videobuf_streamoff(vq); + return vb2_streamoff(vq, type); } EXPORT_SYMBOL_GPL(v4l2_m2m_streamoff); @@ -414,44 +405,53 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_streamoff); unsigned int v4l2_m2m_poll(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, struct poll_table_struct *wait) { - struct videobuf_queue *src_q, *dst_q; - struct videobuf_buffer *src_vb = NULL, *dst_vb = NULL; + struct vb2_queue *src_q, *dst_q; + struct vb2_buffer *src_vb = NULL, *dst_vb = NULL; unsigned int rc = 0; + unsigned long flags; src_q = v4l2_m2m_get_src_vq(m2m_ctx); dst_q = v4l2_m2m_get_dst_vq(m2m_ctx); - videobuf_queue_lock(src_q); - videobuf_queue_lock(dst_q); - - if (src_q->streaming && !list_empty(&src_q->stream)) - src_vb = list_first_entry(&src_q->stream, - struct videobuf_buffer, stream); - if (dst_q->streaming && !list_empty(&dst_q->stream)) - dst_vb = list_first_entry(&dst_q->stream, - struct videobuf_buffer, stream); - - if (!src_vb && !dst_vb) { + /* + * There has to be at least one buffer queued on each queued_list, which + * means either in driver already or waiting for driver to claim it + * and start processing. + */ + if ((!src_q->streaming || list_empty(&src_q->queued_list)) + && (!dst_q->streaming || list_empty(&dst_q->queued_list))) { rc = POLLERR; goto end; } - if (src_vb) { - poll_wait(file, &src_vb->done, wait); - if (src_vb->state == VIDEOBUF_DONE - || src_vb->state == VIDEOBUF_ERROR) - rc |= POLLOUT | POLLWRNORM; - } - if (dst_vb) { - poll_wait(file, &dst_vb->done, wait); - if (dst_vb->state == VIDEOBUF_DONE - || dst_vb->state == VIDEOBUF_ERROR) - rc |= POLLIN | POLLRDNORM; - } + if (m2m_ctx->m2m_dev->m2m_ops->unlock) + m2m_ctx->m2m_dev->m2m_ops->unlock(m2m_ctx->priv); + + poll_wait(file, &src_q->done_wq, wait); + poll_wait(file, &dst_q->done_wq, wait); + + if (m2m_ctx->m2m_dev->m2m_ops->lock) + m2m_ctx->m2m_dev->m2m_ops->lock(m2m_ctx->priv); + + spin_lock_irqsave(&src_q->done_lock, flags); + if (!list_empty(&src_q->done_list)) + src_vb = list_first_entry(&src_q->done_list, struct vb2_buffer, + done_entry); + if (src_vb && (src_vb->state == VB2_BUF_STATE_DONE + || src_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLOUT | POLLWRNORM; + spin_unlock_irqrestore(&src_q->done_lock, flags); + + spin_lock_irqsave(&dst_q->done_lock, flags); + if (!list_empty(&dst_q->done_list)) + dst_vb = list_first_entry(&dst_q->done_list, struct vb2_buffer, + done_entry); + if (dst_vb && (dst_vb->state == VB2_BUF_STATE_DONE + || dst_vb->state == VB2_BUF_STATE_ERROR)) + rc |= POLLIN | POLLRDNORM; + spin_unlock_irqrestore(&dst_q->done_lock, flags); end: - videobuf_queue_unlock(dst_q); - videobuf_queue_unlock(src_q); return rc; } EXPORT_SYMBOL_GPL(v4l2_m2m_poll); @@ -470,7 +470,7 @@ int v4l2_m2m_mmap(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, struct vm_area_struct *vma) { unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; - struct videobuf_queue *vq; + struct vb2_queue *vq; if (offset < DST_QUEUE_OFF_BASE) { vq = v4l2_m2m_get_src_vq(m2m_ctx); @@ -479,7 +479,7 @@ int v4l2_m2m_mmap(struct file *file, struct v4l2_m2m_ctx *m2m_ctx, vma->vm_pgoff -= (DST_QUEUE_OFF_BASE >> PAGE_SHIFT); } - return videobuf_mmap_mapper(vq, vma); + return vb2_mmap(vq, vma); } EXPORT_SYMBOL(v4l2_m2m_mmap); @@ -531,36 +531,41 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_release); * * Usually called from driver's open() function. */ -struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(void *priv, struct v4l2_m2m_dev *m2m_dev, - void (*vq_init)(void *priv, struct videobuf_queue *, - enum v4l2_buf_type)) +struct v4l2_m2m_ctx *v4l2_m2m_ctx_init(struct v4l2_m2m_dev *m2m_dev, + void *drv_priv, + int (*queue_init)(void *priv, struct vb2_queue *src_vq, struct vb2_queue *dst_vq)) { struct v4l2_m2m_ctx *m2m_ctx; struct v4l2_m2m_queue_ctx *out_q_ctx, *cap_q_ctx; - - if (!vq_init) - return ERR_PTR(-EINVAL); + int ret; m2m_ctx = kzalloc(sizeof *m2m_ctx, GFP_KERNEL); if (!m2m_ctx) return ERR_PTR(-ENOMEM); - m2m_ctx->priv = priv; + m2m_ctx->priv = drv_priv; m2m_ctx->m2m_dev = m2m_dev; + init_waitqueue_head(&m2m_ctx->finished); - out_q_ctx = get_queue_ctx(m2m_ctx, V4L2_BUF_TYPE_VIDEO_OUTPUT); - cap_q_ctx = get_queue_ctx(m2m_ctx, V4L2_BUF_TYPE_VIDEO_CAPTURE); + out_q_ctx = &m2m_ctx->out_q_ctx; + cap_q_ctx = &m2m_ctx->cap_q_ctx; INIT_LIST_HEAD(&out_q_ctx->rdy_queue); INIT_LIST_HEAD(&cap_q_ctx->rdy_queue); + spin_lock_init(&out_q_ctx->rdy_spinlock); + spin_lock_init(&cap_q_ctx->rdy_spinlock); INIT_LIST_HEAD(&m2m_ctx->queue); - vq_init(priv, &out_q_ctx->q, V4L2_BUF_TYPE_VIDEO_OUTPUT); - vq_init(priv, &cap_q_ctx->q, V4L2_BUF_TYPE_VIDEO_CAPTURE); - out_q_ctx->q.priv_data = cap_q_ctx->q.priv_data = priv; + ret = queue_init(drv_priv, &out_q_ctx->q, &cap_q_ctx->q); + + if (ret) + goto err; return m2m_ctx; +err: + kfree(m2m_ctx); + return ERR_PTR(ret); } EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_init); @@ -572,7 +577,6 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_init); void v4l2_m2m_ctx_release(struct v4l2_m2m_ctx *m2m_ctx) { struct v4l2_m2m_dev *m2m_dev; - struct videobuf_buffer *vb; unsigned long flags; m2m_dev = m2m_ctx->m2m_dev; @@ -582,10 +586,7 @@ void v4l2_m2m_ctx_release(struct v4l2_m2m_ctx *m2m_ctx) spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); m2m_dev->m2m_ops->job_abort(m2m_ctx->priv); dprintk("m2m_ctx %p running, will wait to complete", m2m_ctx); - vb = v4l2_m2m_next_dst_buf(m2m_ctx); - BUG_ON(NULL == vb); - wait_event(vb->done, vb->state != VIDEOBUF_ACTIVE - && vb->state != VIDEOBUF_QUEUED); + wait_event(m2m_ctx->finished, !(m2m_ctx->job_flags & TRANS_RUNNING)); } else if (m2m_ctx->job_flags & TRANS_QUEUED) { list_del(&m2m_ctx->queue); m2m_ctx->job_flags &= ~(TRANS_QUEUED | TRANS_RUNNING); @@ -597,11 +598,8 @@ void v4l2_m2m_ctx_release(struct v4l2_m2m_ctx *m2m_ctx) spin_unlock_irqrestore(&m2m_dev->job_spinlock, flags); } - videobuf_stop(&m2m_ctx->cap_q_ctx.q); - videobuf_stop(&m2m_ctx->out_q_ctx.q); - - videobuf_mmap_free(&m2m_ctx->cap_q_ctx.q); - videobuf_mmap_free(&m2m_ctx->out_q_ctx.q); + vb2_queue_release(&m2m_ctx->cap_q_ctx.q); + vb2_queue_release(&m2m_ctx->out_q_ctx.q); kfree(m2m_ctx); } @@ -611,23 +609,21 @@ EXPORT_SYMBOL_GPL(v4l2_m2m_ctx_release); * v4l2_m2m_buf_queue() - add a buffer to the proper ready buffers list. * * Call from buf_queue(), videobuf_queue_ops callback. - * - * Locking: Caller holds q->irqlock (taken by videobuf before calling buf_queue - * callback in the driver). */ -void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, struct videobuf_queue *vq, - struct videobuf_buffer *vb) +void v4l2_m2m_buf_queue(struct v4l2_m2m_ctx *m2m_ctx, struct vb2_buffer *vb) { + struct v4l2_m2m_buffer *b = container_of(vb, struct v4l2_m2m_buffer, vb); struct v4l2_m2m_queue_ctx *q_ctx; + unsigned long flags; - q_ctx = get_queue_ctx(m2m_ctx, vq->type); + q_ctx = get_queue_ctx(m2m_ctx, vb->vb2_queue->type); if (!q_ctx) return; - list_add_tail(&vb->queue, &q_ctx->rdy_queue); + spin_lock_irqsave(&q_ctx->rdy_spinlock, flags); + list_add_tail(&b->list, &q_ctx->rdy_queue); q_ctx->num_rdy++; - - vb->state = VIDEOBUF_QUEUED; + spin_unlock_irqrestore(&q_ctx->rdy_spinlock, flags); } EXPORT_SYMBOL_GPL(v4l2_m2m_buf_queue); diff --git a/drivers/media/video/videobuf2-core.c b/drivers/media/video/videobuf2-core.c new file mode 100644 index 000000000000..cc7ab0a17b68 --- /dev/null +++ b/drivers/media/video/videobuf2-core.c @@ -0,0 +1,1804 @@ +/* + * videobuf2-core.c - V4L2 driver helper framework + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <p.osciak@samsung.com> + * Marek Szyprowski <m.szyprowski@samsung.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. + */ + +#include <linux/err.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/poll.h> +#include <linux/slab.h> +#include <linux/sched.h> + +#include <media/videobuf2-core.h> + +static int debug; +module_param(debug, int, 0644); + +#define dprintk(level, fmt, arg...) \ + do { \ + if (debug >= level) \ + printk(KERN_DEBUG "vb2: " fmt, ## arg); \ + } while (0) + +#define call_memop(q, plane, op, args...) \ + (((q)->mem_ops->op) ? \ + ((q)->mem_ops->op(args)) : 0) + +#define call_qop(q, op, args...) \ + (((q)->ops->op) ? ((q)->ops->op(args)) : 0) + +/** + * __vb2_buf_mem_alloc() - allocate video memory for the given buffer + */ +static int __vb2_buf_mem_alloc(struct vb2_buffer *vb, + unsigned long *plane_sizes) +{ + struct vb2_queue *q = vb->vb2_queue; + void *mem_priv; + int plane; + + /* Allocate memory for all planes in this buffer */ + for (plane = 0; plane < vb->num_planes; ++plane) { + mem_priv = call_memop(q, plane, alloc, q->alloc_ctx[plane], + plane_sizes[plane]); + if (!mem_priv) + goto free; + + /* Associate allocator private data with this plane */ + vb->planes[plane].mem_priv = mem_priv; + vb->v4l2_planes[plane].length = plane_sizes[plane]; + } + + return 0; +free: + /* Free already allocated memory if one of the allocations failed */ + for (; plane > 0; --plane) + call_memop(q, plane, put, vb->planes[plane - 1].mem_priv); + + return -ENOMEM; +} + +/** + * __vb2_buf_mem_free() - free memory of the given buffer + */ +static void __vb2_buf_mem_free(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + unsigned int plane; + + for (plane = 0; plane < vb->num_planes; ++plane) { + call_memop(q, plane, put, vb->planes[plane].mem_priv); + vb->planes[plane].mem_priv = NULL; + dprintk(3, "Freed plane %d of buffer %d\n", + plane, vb->v4l2_buf.index); + } +} + +/** + * __vb2_buf_userptr_put() - release userspace memory associated with + * a USERPTR buffer + */ +static void __vb2_buf_userptr_put(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + unsigned int plane; + + for (plane = 0; plane < vb->num_planes; ++plane) { + void *mem_priv = vb->planes[plane].mem_priv; + + if (mem_priv) { + call_memop(q, plane, put_userptr, mem_priv); + vb->planes[plane].mem_priv = NULL; + } + } +} + +/** + * __setup_offsets() - setup unique offsets ("cookies") for every plane in + * every buffer on the queue + */ +static void __setup_offsets(struct vb2_queue *q) +{ + unsigned int buffer, plane; + struct vb2_buffer *vb; + unsigned long off = 0; + + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + vb = q->bufs[buffer]; + if (!vb) + continue; + + for (plane = 0; plane < vb->num_planes; ++plane) { + vb->v4l2_planes[plane].m.mem_offset = off; + + dprintk(3, "Buffer %d, plane %d offset 0x%08lx\n", + buffer, plane, off); + + off += vb->v4l2_planes[plane].length; + off = PAGE_ALIGN(off); + } + } +} + +/** + * __vb2_queue_alloc() - allocate videobuf buffer structures and (for MMAP type) + * video buffer memory for all buffers/planes on the queue and initializes the + * queue + * + * Returns the number of buffers successfully allocated. + */ +static int __vb2_queue_alloc(struct vb2_queue *q, enum v4l2_memory memory, + unsigned int num_buffers, unsigned int num_planes, + unsigned long plane_sizes[]) +{ + unsigned int buffer; + struct vb2_buffer *vb; + int ret; + + for (buffer = 0; buffer < num_buffers; ++buffer) { + /* Allocate videobuf buffer structures */ + vb = kzalloc(q->buf_struct_size, GFP_KERNEL); + if (!vb) { + dprintk(1, "Memory alloc for buffer struct failed\n"); + break; + } + + /* Length stores number of planes for multiplanar buffers */ + if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) + vb->v4l2_buf.length = num_planes; + + vb->state = VB2_BUF_STATE_DEQUEUED; + vb->vb2_queue = q; + vb->num_planes = num_planes; + vb->v4l2_buf.index = buffer; + vb->v4l2_buf.type = q->type; + vb->v4l2_buf.memory = memory; + + /* Allocate video buffer memory for the MMAP type */ + if (memory == V4L2_MEMORY_MMAP) { + ret = __vb2_buf_mem_alloc(vb, plane_sizes); + if (ret) { + dprintk(1, "Failed allocating memory for " + "buffer %d\n", buffer); + kfree(vb); + break; + } + /* + * Call the driver-provided buffer initialization + * callback, if given. An error in initialization + * results in queue setup failure. + */ + ret = call_qop(q, buf_init, vb); + if (ret) { + dprintk(1, "Buffer %d %p initialization" + " failed\n", buffer, vb); + __vb2_buf_mem_free(vb); + kfree(vb); + break; + } + } + + q->bufs[buffer] = vb; + } + + q->num_buffers = buffer; + + __setup_offsets(q); + + dprintk(1, "Allocated %d buffers, %d plane(s) each\n", + q->num_buffers, num_planes); + + return buffer; +} + +/** + * __vb2_free_mem() - release all video buffer memory for a given queue + */ +static void __vb2_free_mem(struct vb2_queue *q) +{ + unsigned int buffer; + struct vb2_buffer *vb; + + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + vb = q->bufs[buffer]; + if (!vb) + continue; + + /* Free MMAP buffers or release USERPTR buffers */ + if (q->memory == V4L2_MEMORY_MMAP) + __vb2_buf_mem_free(vb); + else + __vb2_buf_userptr_put(vb); + } +} + +/** + * __vb2_queue_free() - free the queue - video memory and related information + * and return the queue to an uninitialized state. Might be called even if the + * queue has already been freed. + */ +static int __vb2_queue_free(struct vb2_queue *q) +{ + unsigned int buffer; + + /* Call driver-provided cleanup function for each buffer, if provided */ + if (q->ops->buf_cleanup) { + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + if (NULL == q->bufs[buffer]) + continue; + q->ops->buf_cleanup(q->bufs[buffer]); + } + } + + /* Release video buffer memory */ + __vb2_free_mem(q); + + /* Free videobuf buffers */ + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + kfree(q->bufs[buffer]); + q->bufs[buffer] = NULL; + } + + q->num_buffers = 0; + q->memory = 0; + + return 0; +} + +/** + * __verify_planes_array() - verify that the planes array passed in struct + * v4l2_buffer from userspace can be safely used + */ +static int __verify_planes_array(struct vb2_buffer *vb, struct v4l2_buffer *b) +{ + /* Is memory for copying plane information present? */ + if (NULL == b->m.planes) { + dprintk(1, "Multi-planar buffer passed but " + "planes array not provided\n"); + return -EINVAL; + } + + if (b->length < vb->num_planes || b->length > VIDEO_MAX_PLANES) { + dprintk(1, "Incorrect planes array length, " + "expected %d, got %d\n", vb->num_planes, b->length); + return -EINVAL; + } + + return 0; +} + +/** + * __fill_v4l2_buffer() - fill in a struct v4l2_buffer with information to be + * returned to userspace + */ +static int __fill_v4l2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b) +{ + struct vb2_queue *q = vb->vb2_queue; + int ret = 0; + + /* Copy back data such as timestamp, input, etc. */ + memcpy(b, &vb->v4l2_buf, offsetof(struct v4l2_buffer, m)); + b->input = vb->v4l2_buf.input; + b->reserved = vb->v4l2_buf.reserved; + + if (V4L2_TYPE_IS_MULTIPLANAR(q->type)) { + ret = __verify_planes_array(vb, b); + if (ret) + return ret; + + /* + * Fill in plane-related data if userspace provided an array + * for it. The memory and size is verified above. + */ + memcpy(b->m.planes, vb->v4l2_planes, + b->length * sizeof(struct v4l2_plane)); + } else { + /* + * We use length and offset in v4l2_planes array even for + * single-planar buffers, but userspace does not. + */ + b->length = vb->v4l2_planes[0].length; + b->bytesused = vb->v4l2_planes[0].bytesused; + if (q->memory == V4L2_MEMORY_MMAP) + b->m.offset = vb->v4l2_planes[0].m.mem_offset; + else if (q->memory == V4L2_MEMORY_USERPTR) + b->m.userptr = vb->v4l2_planes[0].m.userptr; + } + + b->flags = 0; + + switch (vb->state) { + case VB2_BUF_STATE_QUEUED: + case VB2_BUF_STATE_ACTIVE: + b->flags |= V4L2_BUF_FLAG_QUEUED; + break; + case VB2_BUF_STATE_ERROR: + b->flags |= V4L2_BUF_FLAG_ERROR; + /* fall through */ + case VB2_BUF_STATE_DONE: + b->flags |= V4L2_BUF_FLAG_DONE; + break; + case VB2_BUF_STATE_DEQUEUED: + /* nothing */ + break; + } + + if (vb->num_planes_mapped == vb->num_planes) + b->flags |= V4L2_BUF_FLAG_MAPPED; + + return ret; +} + +/** + * vb2_querybuf() - query video buffer information + * @q: videobuf queue + * @b: buffer struct passed from userspace to vidioc_querybuf handler + * in driver + * + * Should be called from vidioc_querybuf ioctl handler in driver. + * This function will verify the passed v4l2_buffer structure and fill the + * relevant information for the userspace. + * + * The return values from this function are intended to be directly returned + * from vidioc_querybuf handler in driver. + */ +int vb2_querybuf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + struct vb2_buffer *vb; + + if (b->type != q->type) { + dprintk(1, "querybuf: wrong buffer type\n"); + return -EINVAL; + } + + if (b->index >= q->num_buffers) { + dprintk(1, "querybuf: buffer index out of range\n"); + return -EINVAL; + } + vb = q->bufs[b->index]; + + return __fill_v4l2_buffer(vb, b); +} +EXPORT_SYMBOL(vb2_querybuf); + +/** + * __verify_userptr_ops() - verify that all memory operations required for + * USERPTR queue type have been provided + */ +static int __verify_userptr_ops(struct vb2_queue *q) +{ + if (!(q->io_modes & VB2_USERPTR) || !q->mem_ops->get_userptr || + !q->mem_ops->put_userptr) + return -EINVAL; + + return 0; +} + +/** + * __verify_mmap_ops() - verify that all memory operations required for + * MMAP queue type have been provided + */ +static int __verify_mmap_ops(struct vb2_queue *q) +{ + if (!(q->io_modes & VB2_MMAP) || !q->mem_ops->alloc || + !q->mem_ops->put || !q->mem_ops->mmap) + return -EINVAL; + + return 0; +} + +/** + * __buffers_in_use() - return true if any buffers on the queue are in use and + * the queue cannot be freed (by the means of REQBUFS(0)) call + */ +static bool __buffers_in_use(struct vb2_queue *q) +{ + unsigned int buffer, plane; + struct vb2_buffer *vb; + + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + vb = q->bufs[buffer]; + for (plane = 0; plane < vb->num_planes; ++plane) { + /* + * If num_users() has not been provided, call_memop + * will return 0, apparently nobody cares about this + * case anyway. If num_users() returns more than 1, + * we are not the only user of the plane's memory. + */ + if (call_memop(q, plane, num_users, + vb->planes[plane].mem_priv) > 1) + return true; + } + } + + return false; +} + +/** + * vb2_reqbufs() - Initiate streaming + * @q: videobuf2 queue + * @req: struct passed from userspace to vidioc_reqbufs handler in driver + * + * Should be called from vidioc_reqbufs ioctl handler of a driver. + * This function: + * 1) verifies streaming parameters passed from the userspace, + * 2) sets up the queue, + * 3) negotiates number of buffers and planes per buffer with the driver + * to be used during streaming, + * 4) allocates internal buffer structures (struct vb2_buffer), according to + * the agreed parameters, + * 5) for MMAP memory type, allocates actual video memory, using the + * memory handling/allocation routines provided during queue initialization + * + * If req->count is 0, all the memory will be freed instead. + * If the queue has been allocated previously (by a previous vb2_reqbufs) call + * and the queue is not busy, memory will be reallocated. + * + * The return values from this function are intended to be directly returned + * from vidioc_reqbufs handler in driver. + */ +int vb2_reqbufs(struct vb2_queue *q, struct v4l2_requestbuffers *req) +{ + unsigned int num_buffers, num_planes; + unsigned long plane_sizes[VIDEO_MAX_PLANES]; + int ret = 0; + + if (q->fileio) { + dprintk(1, "reqbufs: file io in progress\n"); + return -EBUSY; + } + + if (req->memory != V4L2_MEMORY_MMAP + && req->memory != V4L2_MEMORY_USERPTR) { + dprintk(1, "reqbufs: unsupported memory type\n"); + return -EINVAL; + } + + if (req->type != q->type) { + dprintk(1, "reqbufs: requested type is incorrect\n"); + return -EINVAL; + } + + if (q->streaming) { + dprintk(1, "reqbufs: streaming active\n"); + return -EBUSY; + } + + /* + * Make sure all the required memory ops for given memory type + * are available. + */ + if (req->memory == V4L2_MEMORY_MMAP && __verify_mmap_ops(q)) { + dprintk(1, "reqbufs: MMAP for current setup unsupported\n"); + return -EINVAL; + } + + if (req->memory == V4L2_MEMORY_USERPTR && __verify_userptr_ops(q)) { + dprintk(1, "reqbufs: USERPTR for current setup unsupported\n"); + return -EINVAL; + } + + if (req->count == 0 || q->num_buffers != 0) { + /* + * We already have buffers allocated, so first check if they + * are not in use and can be freed. + */ + if (q->memory == V4L2_MEMORY_MMAP && __buffers_in_use(q)) { + dprintk(1, "reqbufs: memory in use, cannot free\n"); + return -EBUSY; + } + + ret = __vb2_queue_free(q); + if (ret != 0) + return ret; + } + + /* + * Make sure the requested values and current defaults are sane. + */ + num_buffers = min_t(unsigned int, req->count, VIDEO_MAX_FRAME); + memset(plane_sizes, 0, sizeof(plane_sizes)); + memset(q->alloc_ctx, 0, sizeof(q->alloc_ctx)); + + /* + * Ask the driver how many buffers and planes per buffer it requires. + * Driver also sets the size and allocator context for each plane. + */ + ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes, + plane_sizes, q->alloc_ctx); + if (ret) + return ret; + + /* Finally, allocate buffers and video memory */ + ret = __vb2_queue_alloc(q, req->memory, num_buffers, num_planes, + plane_sizes); + if (ret < 0) { + dprintk(1, "Memory allocation failed with error: %d\n", ret); + return ret; + } + + /* + * Check if driver can handle the allocated number of buffers. + */ + if (ret < num_buffers) { + unsigned int orig_num_buffers; + + orig_num_buffers = num_buffers = ret; + ret = call_qop(q, queue_setup, q, &num_buffers, &num_planes, + plane_sizes, q->alloc_ctx); + if (ret) + goto free_mem; + + if (orig_num_buffers < num_buffers) { + ret = -ENOMEM; + goto free_mem; + } + + /* + * Ok, driver accepted smaller number of buffers. + */ + ret = num_buffers; + } + + q->memory = req->memory; + + /* + * Return the number of successfully allocated buffers + * to the userspace. + */ + req->count = ret; + + return 0; + +free_mem: + __vb2_queue_free(q); + return ret; +} +EXPORT_SYMBOL_GPL(vb2_reqbufs); + +/** + * vb2_plane_vaddr() - Return a kernel virtual address of a given plane + * @vb: vb2_buffer to which the plane in question belongs to + * @plane_no: plane number for which the address is to be returned + * + * This function returns a kernel virtual address of a given plane if + * such a mapping exist, NULL otherwise. + */ +void *vb2_plane_vaddr(struct vb2_buffer *vb, unsigned int plane_no) +{ + struct vb2_queue *q = vb->vb2_queue; + + if (plane_no > vb->num_planes) + return NULL; + + return call_memop(q, plane_no, vaddr, vb->planes[plane_no].mem_priv); + +} +EXPORT_SYMBOL_GPL(vb2_plane_vaddr); + +/** + * vb2_plane_cookie() - Return allocator specific cookie for the given plane + * @vb: vb2_buffer to which the plane in question belongs to + * @plane_no: plane number for which the cookie is to be returned + * + * This function returns an allocator specific cookie for a given plane if + * available, NULL otherwise. The allocator should provide some simple static + * inline function, which would convert this cookie to the allocator specific + * type that can be used directly by the driver to access the buffer. This can + * be for example physical address, pointer to scatter list or IOMMU mapping. + */ +void *vb2_plane_cookie(struct vb2_buffer *vb, unsigned int plane_no) +{ + struct vb2_queue *q = vb->vb2_queue; + + if (plane_no > vb->num_planes) + return NULL; + + return call_memop(q, plane_no, cookie, vb->planes[plane_no].mem_priv); +} +EXPORT_SYMBOL_GPL(vb2_plane_cookie); + +/** + * vb2_buffer_done() - inform videobuf that an operation on a buffer is finished + * @vb: vb2_buffer returned from the driver + * @state: either VB2_BUF_STATE_DONE if the operation finished successfully + * or VB2_BUF_STATE_ERROR if the operation finished with an error + * + * This function should be called by the driver after a hardware operation on + * a buffer is finished and the buffer may be returned to userspace. The driver + * cannot use this buffer anymore until it is queued back to it by videobuf + * by the means of buf_queue callback. Only buffers previously queued to the + * driver by buf_queue can be passed to this function. + */ +void vb2_buffer_done(struct vb2_buffer *vb, enum vb2_buffer_state state) +{ + struct vb2_queue *q = vb->vb2_queue; + unsigned long flags; + + if (vb->state != VB2_BUF_STATE_ACTIVE) + return; + + if (state != VB2_BUF_STATE_DONE && state != VB2_BUF_STATE_ERROR) + return; + + dprintk(4, "Done processing on buffer %d, state: %d\n", + vb->v4l2_buf.index, vb->state); + + /* Add the buffer to the done buffers list */ + spin_lock_irqsave(&q->done_lock, flags); + vb->state = state; + list_add_tail(&vb->done_entry, &q->done_list); + atomic_dec(&q->queued_count); + spin_unlock_irqrestore(&q->done_lock, flags); + + /* Inform any processes that may be waiting for buffers */ + wake_up(&q->done_wq); +} +EXPORT_SYMBOL_GPL(vb2_buffer_done); + +/** + * __fill_vb2_buffer() - fill a vb2_buffer with information provided in + * a v4l2_buffer by the userspace + */ +static int __fill_vb2_buffer(struct vb2_buffer *vb, struct v4l2_buffer *b, + struct v4l2_plane *v4l2_planes) +{ + unsigned int plane; + int ret; + + if (V4L2_TYPE_IS_MULTIPLANAR(b->type)) { + /* + * Verify that the userspace gave us a valid array for + * plane information. + */ + ret = __verify_planes_array(vb, b); + if (ret) + return ret; + + /* Fill in driver-provided information for OUTPUT types */ + if (V4L2_TYPE_IS_OUTPUT(b->type)) { + /* + * Will have to go up to b->length when API starts + * accepting variable number of planes. + */ + for (plane = 0; plane < vb->num_planes; ++plane) { + v4l2_planes[plane].bytesused = + b->m.planes[plane].bytesused; + v4l2_planes[plane].data_offset = + b->m.planes[plane].data_offset; + } + } + + if (b->memory == V4L2_MEMORY_USERPTR) { + for (plane = 0; plane < vb->num_planes; ++plane) { + v4l2_planes[plane].m.userptr = + b->m.planes[plane].m.userptr; + v4l2_planes[plane].length = + b->m.planes[plane].length; + } + } + } else { + /* + * Single-planar buffers do not use planes array, + * so fill in relevant v4l2_buffer struct fields instead. + * In videobuf we use our internal V4l2_planes struct for + * single-planar buffers as well, for simplicity. + */ + if (V4L2_TYPE_IS_OUTPUT(b->type)) + v4l2_planes[0].bytesused = b->bytesused; + + if (b->memory == V4L2_MEMORY_USERPTR) { + v4l2_planes[0].m.userptr = b->m.userptr; + v4l2_planes[0].length = b->length; + } + } + + vb->v4l2_buf.field = b->field; + vb->v4l2_buf.timestamp = b->timestamp; + + return 0; +} + +/** + * __qbuf_userptr() - handle qbuf of a USERPTR buffer + */ +static int __qbuf_userptr(struct vb2_buffer *vb, struct v4l2_buffer *b) +{ + struct v4l2_plane planes[VIDEO_MAX_PLANES]; + struct vb2_queue *q = vb->vb2_queue; + void *mem_priv; + unsigned int plane; + int ret; + int write = !V4L2_TYPE_IS_OUTPUT(q->type); + + /* Verify and copy relevant information provided by the userspace */ + ret = __fill_vb2_buffer(vb, b, planes); + if (ret) + return ret; + + for (plane = 0; plane < vb->num_planes; ++plane) { + /* Skip the plane if already verified */ + if (vb->v4l2_planes[plane].m.userptr == planes[plane].m.userptr + && vb->v4l2_planes[plane].length == planes[plane].length) + continue; + + dprintk(3, "qbuf: userspace address for plane %d changed, " + "reacquiring memory\n", plane); + + /* Release previously acquired memory if present */ + if (vb->planes[plane].mem_priv) + call_memop(q, plane, put_userptr, + vb->planes[plane].mem_priv); + + vb->planes[plane].mem_priv = NULL; + + /* Acquire each plane's memory */ + if (q->mem_ops->get_userptr) { + mem_priv = q->mem_ops->get_userptr(q->alloc_ctx[plane], + planes[plane].m.userptr, + planes[plane].length, + write); + if (IS_ERR(mem_priv)) { + dprintk(1, "qbuf: failed acquiring userspace " + "memory for plane %d\n", plane); + ret = PTR_ERR(mem_priv); + goto err; + } + vb->planes[plane].mem_priv = mem_priv; + } + } + + /* + * Call driver-specific initialization on the newly acquired buffer, + * if provided. + */ + ret = call_qop(q, buf_init, vb); + if (ret) { + dprintk(1, "qbuf: buffer initialization failed\n"); + goto err; + } + + /* + * Now that everything is in order, copy relevant information + * provided by userspace. + */ + for (plane = 0; plane < vb->num_planes; ++plane) + vb->v4l2_planes[plane] = planes[plane]; + + return 0; +err: + /* In case of errors, release planes that were already acquired */ + for (; plane > 0; --plane) { + call_memop(q, plane, put_userptr, + vb->planes[plane - 1].mem_priv); + vb->planes[plane - 1].mem_priv = NULL; + } + + return ret; +} + +/** + * __qbuf_mmap() - handle qbuf of an MMAP buffer + */ +static int __qbuf_mmap(struct vb2_buffer *vb, struct v4l2_buffer *b) +{ + return __fill_vb2_buffer(vb, b, vb->v4l2_planes); +} + +/** + * __enqueue_in_driver() - enqueue a vb2_buffer in driver for processing + */ +static void __enqueue_in_driver(struct vb2_buffer *vb) +{ + struct vb2_queue *q = vb->vb2_queue; + + vb->state = VB2_BUF_STATE_ACTIVE; + atomic_inc(&q->queued_count); + q->ops->buf_queue(vb); +} + +/** + * vb2_qbuf() - Queue a buffer from userspace + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_qbuf handler + * in driver + * + * Should be called from vidioc_qbuf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) calls buf_prepare callback in the driver (if provided), in which + * driver-specific buffer initialization can be performed, + * 3) if streaming is on, queues the buffer in driver by the means of buf_queue + * callback for processing. + * + * The return values from this function are intended to be directly returned + * from vidioc_qbuf handler in driver. + */ +int vb2_qbuf(struct vb2_queue *q, struct v4l2_buffer *b) +{ + struct vb2_buffer *vb; + int ret = 0; + + if (q->fileio) { + dprintk(1, "qbuf: file io in progress\n"); + return -EBUSY; + } + + if (b->type != q->type) { + dprintk(1, "qbuf: invalid buffer type\n"); + return -EINVAL; + } + + if (b->index >= q->num_buffers) { + dprintk(1, "qbuf: buffer index out of range\n"); + return -EINVAL; + } + + vb = q->bufs[b->index]; + if (NULL == vb) { + /* Should never happen */ + dprintk(1, "qbuf: buffer is NULL\n"); + return -EINVAL; + } + + if (b->memory != q->memory) { + dprintk(1, "qbuf: invalid memory type\n"); + return -EINVAL; + } + + if (vb->state != VB2_BUF_STATE_DEQUEUED) { + dprintk(1, "qbuf: buffer already in use\n"); + return -EINVAL; + } + + if (q->memory == V4L2_MEMORY_MMAP) + ret = __qbuf_mmap(vb, b); + else if (q->memory == V4L2_MEMORY_USERPTR) + ret = __qbuf_userptr(vb, b); + else { + WARN(1, "Invalid queue type\n"); + return -EINVAL; + } + + if (ret) + return ret; + + ret = call_qop(q, buf_prepare, vb); + if (ret) { + dprintk(1, "qbuf: buffer preparation failed\n"); + return ret; + } + + /* + * Add to the queued buffers list, a buffer will stay on it until + * dequeued in dqbuf. + */ + list_add_tail(&vb->queued_entry, &q->queued_list); + vb->state = VB2_BUF_STATE_QUEUED; + + /* + * If already streaming, give the buffer to driver for processing. + * If not, the buffer will be given to driver on next streamon. + */ + if (q->streaming) + __enqueue_in_driver(vb); + + dprintk(1, "qbuf of buffer %d succeeded\n", vb->v4l2_buf.index); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_qbuf); + +/** + * __vb2_wait_for_done_vb() - wait for a buffer to become available + * for dequeuing + * + * Will sleep if required for nonblocking == false. + */ +static int __vb2_wait_for_done_vb(struct vb2_queue *q, int nonblocking) +{ + /* + * All operations on vb_done_list are performed under done_lock + * spinlock protection. However, buffers may be removed from + * it and returned to userspace only while holding both driver's + * lock and the done_lock spinlock. Thus we can be sure that as + * long as we hold the driver's lock, the list will remain not + * empty if list_empty() check succeeds. + */ + + for (;;) { + int ret; + + if (!q->streaming) { + dprintk(1, "Streaming off, will not wait for buffers\n"); + return -EINVAL; + } + + if (!list_empty(&q->done_list)) { + /* + * Found a buffer that we were waiting for. + */ + break; + } + + if (nonblocking) { + dprintk(1, "Nonblocking and no buffers to dequeue, " + "will not wait\n"); + return -EAGAIN; + } + + /* + * We are streaming and blocking, wait for another buffer to + * become ready or for streamoff. Driver's lock is released to + * allow streamoff or qbuf to be called while waiting. + */ + call_qop(q, wait_prepare, q); + + /* + * All locks have been released, it is safe to sleep now. + */ + dprintk(3, "Will sleep waiting for buffers\n"); + ret = wait_event_interruptible(q->done_wq, + !list_empty(&q->done_list) || !q->streaming); + + /* + * We need to reevaluate both conditions again after reacquiring + * the locks or return an error if one occurred. + */ + call_qop(q, wait_finish, q); + if (ret) + return ret; + } + return 0; +} + +/** + * __vb2_get_done_vb() - get a buffer ready for dequeuing + * + * Will sleep if required for nonblocking == false. + */ +static int __vb2_get_done_vb(struct vb2_queue *q, struct vb2_buffer **vb, + int nonblocking) +{ + unsigned long flags; + int ret; + + /* + * Wait for at least one buffer to become available on the done_list. + */ + ret = __vb2_wait_for_done_vb(q, nonblocking); + if (ret) + return ret; + + /* + * Driver's lock has been held since we last verified that done_list + * is not empty, so no need for another list_empty(done_list) check. + */ + spin_lock_irqsave(&q->done_lock, flags); + *vb = list_first_entry(&q->done_list, struct vb2_buffer, done_entry); + list_del(&(*vb)->done_entry); + spin_unlock_irqrestore(&q->done_lock, flags); + + return 0; +} + +/** + * vb2_wait_for_all_buffers() - wait until all buffers are given back to vb2 + * @q: videobuf2 queue + * + * This function will wait until all buffers that have been given to the driver + * by buf_queue() are given back to vb2 with vb2_buffer_done(). It doesn't call + * wait_prepare, wait_finish pair. It is intended to be called with all locks + * taken, for example from stop_streaming() callback. + */ +int vb2_wait_for_all_buffers(struct vb2_queue *q) +{ + if (!q->streaming) { + dprintk(1, "Streaming off, will not wait for buffers\n"); + return -EINVAL; + } + + wait_event(q->done_wq, !atomic_read(&q->queued_count)); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_wait_for_all_buffers); + +/** + * vb2_dqbuf() - Dequeue a buffer to the userspace + * @q: videobuf2 queue + * @b: buffer structure passed from userspace to vidioc_dqbuf handler + * in driver + * @nonblocking: if true, this call will not sleep waiting for a buffer if no + * buffers ready for dequeuing are present. Normally the driver + * would be passing (file->f_flags & O_NONBLOCK) here + * + * Should be called from vidioc_dqbuf ioctl handler of a driver. + * This function: + * 1) verifies the passed buffer, + * 2) calls buf_finish callback in the driver (if provided), in which + * driver can perform any additional operations that may be required before + * returning the buffer to userspace, such as cache sync, + * 3) the buffer struct members are filled with relevant information for + * the userspace. + * + * The return values from this function are intended to be directly returned + * from vidioc_dqbuf handler in driver. + */ +int vb2_dqbuf(struct vb2_queue *q, struct v4l2_buffer *b, bool nonblocking) +{ + struct vb2_buffer *vb = NULL; + int ret; + + if (q->fileio) { + dprintk(1, "dqbuf: file io in progress\n"); + return -EBUSY; + } + + if (b->type != q->type) { + dprintk(1, "dqbuf: invalid buffer type\n"); + return -EINVAL; + } + + ret = __vb2_get_done_vb(q, &vb, nonblocking); + if (ret < 0) { + dprintk(1, "dqbuf: error getting next done buffer\n"); + return ret; + } + + ret = call_qop(q, buf_finish, vb); + if (ret) { + dprintk(1, "dqbuf: buffer finish failed\n"); + return ret; + } + + switch (vb->state) { + case VB2_BUF_STATE_DONE: + dprintk(3, "dqbuf: Returning done buffer\n"); + break; + case VB2_BUF_STATE_ERROR: + dprintk(3, "dqbuf: Returning done buffer with errors\n"); + break; + default: + dprintk(1, "dqbuf: Invalid buffer state\n"); + return -EINVAL; + } + + /* Fill buffer information for the userspace */ + __fill_v4l2_buffer(vb, b); + /* Remove from videobuf queue */ + list_del(&vb->queued_entry); + + dprintk(1, "dqbuf of buffer %d, with state %d\n", + vb->v4l2_buf.index, vb->state); + + vb->state = VB2_BUF_STATE_DEQUEUED; + return 0; +} +EXPORT_SYMBOL_GPL(vb2_dqbuf); + +/** + * vb2_streamon - start streaming + * @q: videobuf2 queue + * @type: type argument passed from userspace to vidioc_streamon handler + * + * Should be called from vidioc_streamon handler of a driver. + * This function: + * 1) verifies current state + * 2) starts streaming and passes any previously queued buffers to the driver + * + * The return values from this function are intended to be directly returned + * from vidioc_streamon handler in the driver. + */ +int vb2_streamon(struct vb2_queue *q, enum v4l2_buf_type type) +{ + struct vb2_buffer *vb; + + if (q->fileio) { + dprintk(1, "streamon: file io in progress\n"); + return -EBUSY; + } + + if (type != q->type) { + dprintk(1, "streamon: invalid stream type\n"); + return -EINVAL; + } + + if (q->streaming) { + dprintk(1, "streamon: already streaming\n"); + return -EBUSY; + } + + /* + * Cannot start streaming on an OUTPUT device if no buffers have + * been queued yet. + */ + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + if (list_empty(&q->queued_list)) { + dprintk(1, "streamon: no output buffers queued\n"); + return -EINVAL; + } + } + + q->streaming = 1; + + /* + * Let driver notice that streaming state has been enabled. + */ + call_qop(q, start_streaming, q); + + /* + * If any buffers were queued before streamon, + * we can now pass them to driver for processing. + */ + list_for_each_entry(vb, &q->queued_list, queued_entry) + __enqueue_in_driver(vb); + + dprintk(3, "Streamon successful\n"); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_streamon); + +/** + * __vb2_queue_cancel() - cancel and stop (pause) streaming + * + * Removes all queued buffers from driver's queue and all buffers queued by + * userspace from videobuf's queue. Returns to state after reqbufs. + */ +static void __vb2_queue_cancel(struct vb2_queue *q) +{ + unsigned int i; + + /* + * Tell driver to stop all transactions and release all queued + * buffers. + */ + if (q->streaming) + call_qop(q, stop_streaming, q); + q->streaming = 0; + + /* + * Remove all buffers from videobuf's list... + */ + INIT_LIST_HEAD(&q->queued_list); + /* + * ...and done list; userspace will not receive any buffers it + * has not already dequeued before initiating cancel. + */ + INIT_LIST_HEAD(&q->done_list); + wake_up_all(&q->done_wq); + + /* + * Reinitialize all buffers for next use. + */ + for (i = 0; i < q->num_buffers; ++i) + q->bufs[i]->state = VB2_BUF_STATE_DEQUEUED; +} + +/** + * vb2_streamoff - stop streaming + * @q: videobuf2 queue + * @type: type argument passed from userspace to vidioc_streamoff handler + * + * Should be called from vidioc_streamoff handler of a driver. + * This function: + * 1) verifies current state, + * 2) stop streaming and dequeues any queued buffers, including those previously + * passed to the driver (after waiting for the driver to finish). + * + * This call can be used for pausing playback. + * The return values from this function are intended to be directly returned + * from vidioc_streamoff handler in the driver + */ +int vb2_streamoff(struct vb2_queue *q, enum v4l2_buf_type type) +{ + if (q->fileio) { + dprintk(1, "streamoff: file io in progress\n"); + return -EBUSY; + } + + if (type != q->type) { + dprintk(1, "streamoff: invalid stream type\n"); + return -EINVAL; + } + + if (!q->streaming) { + dprintk(1, "streamoff: not streaming\n"); + return -EINVAL; + } + + /* + * Cancel will pause streaming and remove all buffers from the driver + * and videobuf, effectively returning control over them to userspace. + */ + __vb2_queue_cancel(q); + + dprintk(3, "Streamoff successful\n"); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_streamoff); + +/** + * __find_plane_by_offset() - find plane associated with the given offset off + */ +static int __find_plane_by_offset(struct vb2_queue *q, unsigned long off, + unsigned int *_buffer, unsigned int *_plane) +{ + struct vb2_buffer *vb; + unsigned int buffer, plane; + + /* + * Go over all buffers and their planes, comparing the given offset + * with an offset assigned to each plane. If a match is found, + * return its buffer and plane numbers. + */ + for (buffer = 0; buffer < q->num_buffers; ++buffer) { + vb = q->bufs[buffer]; + + for (plane = 0; plane < vb->num_planes; ++plane) { + if (vb->v4l2_planes[plane].m.mem_offset == off) { + *_buffer = buffer; + *_plane = plane; + return 0; + } + } + } + + return -EINVAL; +} + +/** + * vb2_mmap() - map video buffers into application address space + * @q: videobuf2 queue + * @vma: vma passed to the mmap file operation handler in the driver + * + * Should be called from mmap file operation handler of a driver. + * This function maps one plane of one of the available video buffers to + * userspace. To map whole video memory allocated on reqbufs, this function + * has to be called once per each plane per each buffer previously allocated. + * + * When the userspace application calls mmap, it passes to it an offset returned + * to it earlier by the means of vidioc_querybuf handler. That offset acts as + * a "cookie", which is then used to identify the plane to be mapped. + * This function finds a plane with a matching offset and a mapping is performed + * by the means of a provided memory operation. + * + * The return values from this function are intended to be directly returned + * from the mmap handler in driver. + */ +int vb2_mmap(struct vb2_queue *q, struct vm_area_struct *vma) +{ + unsigned long off = vma->vm_pgoff << PAGE_SHIFT; + struct vb2_plane *vb_plane; + struct vb2_buffer *vb; + unsigned int buffer, plane; + int ret; + + if (q->memory != V4L2_MEMORY_MMAP) { + dprintk(1, "Queue is not currently set up for mmap\n"); + return -EINVAL; + } + + /* + * Check memory area access mode. + */ + if (!(vma->vm_flags & VM_SHARED)) { + dprintk(1, "Invalid vma flags, VM_SHARED needed\n"); + return -EINVAL; + } + if (V4L2_TYPE_IS_OUTPUT(q->type)) { + if (!(vma->vm_flags & VM_WRITE)) { + dprintk(1, "Invalid vma flags, VM_WRITE needed\n"); + return -EINVAL; + } + } else { + if (!(vma->vm_flags & VM_READ)) { + dprintk(1, "Invalid vma flags, VM_READ needed\n"); + return -EINVAL; + } + } + + /* + * Find the plane corresponding to the offset passed by userspace. + */ + ret = __find_plane_by_offset(q, off, &buffer, &plane); + if (ret) + return ret; + + vb = q->bufs[buffer]; + vb_plane = &vb->planes[plane]; + + ret = q->mem_ops->mmap(vb_plane->mem_priv, vma); + if (ret) + return ret; + + vb_plane->mapped = 1; + vb->num_planes_mapped++; + + dprintk(3, "Buffer %d, plane %d successfully mapped\n", buffer, plane); + return 0; +} +EXPORT_SYMBOL_GPL(vb2_mmap); + +static int __vb2_init_fileio(struct vb2_queue *q, int read); +static int __vb2_cleanup_fileio(struct vb2_queue *q); + +/** + * vb2_poll() - implements poll userspace operation + * @q: videobuf2 queue + * @file: file argument passed to the poll file operation handler + * @wait: wait argument passed to the poll file operation handler + * + * This function implements poll file operation handler for a driver. + * For CAPTURE queues, if a buffer is ready to be dequeued, the userspace will + * be informed that the file descriptor of a video device is available for + * reading. + * For OUTPUT queues, if a buffer is ready to be dequeued, the file descriptor + * will be reported as available for writing. + * + * The return values from this function are intended to be directly returned + * from poll handler in driver. + */ +unsigned int vb2_poll(struct vb2_queue *q, struct file *file, poll_table *wait) +{ + unsigned long flags; + unsigned int ret; + struct vb2_buffer *vb = NULL; + + /* + * Start file io emulator if streaming api has not been used yet. + */ + if (q->num_buffers == 0 && q->fileio == NULL) { + if (!V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_READ)) { + ret = __vb2_init_fileio(q, 1); + if (ret) + return ret; + } + if (V4L2_TYPE_IS_OUTPUT(q->type) && (q->io_modes & VB2_WRITE)) { + ret = __vb2_init_fileio(q, 0); + if (ret) + return ret; + /* + * Write to OUTPUT queue can be done immediately. + */ + return POLLOUT | POLLWRNORM; + } + } + + /* + * There is nothing to wait for if no buffers have already been queued. + */ + if (list_empty(&q->queued_list)) + return POLLERR; + + poll_wait(file, &q->done_wq, wait); + + /* + * Take first buffer available for dequeuing. + */ + spin_lock_irqsave(&q->done_lock, flags); + if (!list_empty(&q->done_list)) + vb = list_first_entry(&q->done_list, struct vb2_buffer, + done_entry); + spin_unlock_irqrestore(&q->done_lock, flags); + + if (vb && (vb->state == VB2_BUF_STATE_DONE + || vb->state == VB2_BUF_STATE_ERROR)) { + return (V4L2_TYPE_IS_OUTPUT(q->type)) ? POLLOUT | POLLWRNORM : + POLLIN | POLLRDNORM; + } + return 0; +} +EXPORT_SYMBOL_GPL(vb2_poll); + +/** + * vb2_queue_init() - initialize a videobuf2 queue + * @q: videobuf2 queue; this structure should be allocated in driver + * + * The vb2_queue structure should be allocated by the driver. The driver is + * responsible of clearing it's content and setting initial values for some + * required entries before calling this function. + * q->ops, q->mem_ops, q->type and q->io_modes are mandatory. Please refer + * to the struct vb2_queue description in include/media/videobuf2-core.h + * for more information. + */ +int vb2_queue_init(struct vb2_queue *q) +{ + BUG_ON(!q); + BUG_ON(!q->ops); + BUG_ON(!q->mem_ops); + BUG_ON(!q->type); + BUG_ON(!q->io_modes); + + BUG_ON(!q->ops->queue_setup); + BUG_ON(!q->ops->buf_queue); + + INIT_LIST_HEAD(&q->queued_list); + INIT_LIST_HEAD(&q->done_list); + spin_lock_init(&q->done_lock); + init_waitqueue_head(&q->done_wq); + + if (q->buf_struct_size == 0) + q->buf_struct_size = sizeof(struct vb2_buffer); + + return 0; +} +EXPORT_SYMBOL_GPL(vb2_queue_init); + +/** + * vb2_queue_release() - stop streaming, release the queue and free memory + * @q: videobuf2 queue + * + * This function stops streaming and performs necessary clean ups, including + * freeing video buffer memory. The driver is responsible for freeing + * the vb2_queue structure itself. + */ +void vb2_queue_release(struct vb2_queue *q) +{ + __vb2_cleanup_fileio(q); + __vb2_queue_cancel(q); + __vb2_queue_free(q); +} +EXPORT_SYMBOL_GPL(vb2_queue_release); + +/** + * struct vb2_fileio_buf - buffer context used by file io emulator + * + * vb2 provides a compatibility layer and emulator of file io (read and + * write) calls on top of streaming API. This structure is used for + * tracking context related to the buffers. + */ +struct vb2_fileio_buf { + void *vaddr; + unsigned int size; + unsigned int pos; + unsigned int queued:1; +}; + +/** + * struct vb2_fileio_data - queue context used by file io emulator + * + * vb2 provides a compatibility layer and emulator of file io (read and + * write) calls on top of streaming API. For proper operation it required + * this structure to save the driver state between each call of the read + * or write function. + */ +struct vb2_fileio_data { + struct v4l2_requestbuffers req; + struct v4l2_buffer b; + struct vb2_fileio_buf bufs[VIDEO_MAX_FRAME]; + unsigned int index; + unsigned int q_count; + unsigned int dq_count; + unsigned int flags; +}; + +/** + * __vb2_init_fileio() - initialize file io emulator + * @q: videobuf2 queue + * @read: mode selector (1 means read, 0 means write) + */ +static int __vb2_init_fileio(struct vb2_queue *q, int read) +{ + struct vb2_fileio_data *fileio; + int i, ret; + unsigned int count = 0; + + /* + * Sanity check + */ + if ((read && !(q->io_modes & VB2_READ)) || + (!read && !(q->io_modes & VB2_WRITE))) + BUG(); + + /* + * Check if device supports mapping buffers to kernel virtual space. + */ + if (!q->mem_ops->vaddr) + return -EBUSY; + + /* + * Check if streaming api has not been already activated. + */ + if (q->streaming || q->num_buffers > 0) + return -EBUSY; + + /* + * Start with count 1, driver can increase it in queue_setup() + */ + count = 1; + + dprintk(3, "setting up file io: mode %s, count %d, flags %08x\n", + (read) ? "read" : "write", count, q->io_flags); + + fileio = kzalloc(sizeof(struct vb2_fileio_data), GFP_KERNEL); + if (fileio == NULL) + return -ENOMEM; + + fileio->flags = q->io_flags; + + /* + * Request buffers and use MMAP type to force driver + * to allocate buffers by itself. + */ + fileio->req.count = count; + fileio->req.memory = V4L2_MEMORY_MMAP; + fileio->req.type = q->type; + ret = vb2_reqbufs(q, &fileio->req); + if (ret) + goto err_kfree; + + /* + * Check if plane_count is correct + * (multiplane buffers are not supported). + */ + if (q->bufs[0]->num_planes != 1) { + fileio->req.count = 0; + ret = -EBUSY; + goto err_reqbufs; + } + + /* + * Get kernel address of each buffer. + */ + for (i = 0; i < q->num_buffers; i++) { + fileio->bufs[i].vaddr = vb2_plane_vaddr(q->bufs[i], 0); + if (fileio->bufs[i].vaddr == NULL) + goto err_reqbufs; + fileio->bufs[i].size = vb2_plane_size(q->bufs[i], 0); + } + + /* + * Read mode requires pre queuing of all buffers. + */ + if (read) { + /* + * Queue all buffers. + */ + for (i = 0; i < q->num_buffers; i++) { + struct v4l2_buffer *b = &fileio->b; + memset(b, 0, sizeof(*b)); + b->type = q->type; + b->memory = q->memory; + b->index = i; + ret = vb2_qbuf(q, b); + if (ret) + goto err_reqbufs; + fileio->bufs[i].queued = 1; + } + + /* + * Start streaming. + */ + ret = vb2_streamon(q, q->type); + if (ret) + goto err_reqbufs; + } + + q->fileio = fileio; + + return ret; + +err_reqbufs: + vb2_reqbufs(q, &fileio->req); + +err_kfree: + kfree(fileio); + return ret; +} + +/** + * __vb2_cleanup_fileio() - free resourced used by file io emulator + * @q: videobuf2 queue + */ +static int __vb2_cleanup_fileio(struct vb2_queue *q) +{ + struct vb2_fileio_data *fileio = q->fileio; + + if (fileio) { + /* + * Hack fileio context to enable direct calls to vb2 ioctl + * interface. + */ + q->fileio = NULL; + + vb2_streamoff(q, q->type); + fileio->req.count = 0; + vb2_reqbufs(q, &fileio->req); + kfree(fileio); + dprintk(3, "file io emulator closed\n"); + } + return 0; +} + +/** + * __vb2_perform_fileio() - perform a single file io (read or write) operation + * @q: videobuf2 queue + * @data: pointed to target userspace buffer + * @count: number of bytes to read or write + * @ppos: file handle position tracking pointer + * @nonblock: mode selector (1 means blocking calls, 0 means nonblocking) + * @read: access mode selector (1 means read, 0 means write) + */ +static size_t __vb2_perform_fileio(struct vb2_queue *q, char __user *data, size_t count, + loff_t *ppos, int nonblock, int read) +{ + struct vb2_fileio_data *fileio; + struct vb2_fileio_buf *buf; + int ret, index; + + dprintk(3, "file io: mode %s, offset %ld, count %zd, %sblocking\n", + read ? "read" : "write", (long)*ppos, count, + nonblock ? "non" : ""); + + if (!data) + return -EINVAL; + + /* + * Initialize emulator on first call. + */ + if (!q->fileio) { + ret = __vb2_init_fileio(q, read); + dprintk(3, "file io: vb2_init_fileio result: %d\n", ret); + if (ret) + return ret; + } + fileio = q->fileio; + + /* + * Hack fileio context to enable direct calls to vb2 ioctl interface. + * The pointer will be restored before returning from this function. + */ + q->fileio = NULL; + + index = fileio->index; + buf = &fileio->bufs[index]; + + /* + * Check if we need to dequeue the buffer. + */ + if (buf->queued) { + struct vb2_buffer *vb; + + /* + * Call vb2_dqbuf to get buffer back. + */ + memset(&fileio->b, 0, sizeof(fileio->b)); + fileio->b.type = q->type; + fileio->b.memory = q->memory; + fileio->b.index = index; + ret = vb2_dqbuf(q, &fileio->b, nonblock); + dprintk(5, "file io: vb2_dqbuf result: %d\n", ret); + if (ret) + goto end; + fileio->dq_count += 1; + + /* + * Get number of bytes filled by the driver + */ + vb = q->bufs[index]; + buf->size = vb2_get_plane_payload(vb, 0); + buf->queued = 0; + } + + /* + * Limit count on last few bytes of the buffer. + */ + if (buf->pos + count > buf->size) { + count = buf->size - buf->pos; + dprintk(5, "reducing read count: %zd\n", count); + } + + /* + * Transfer data to userspace. + */ + dprintk(3, "file io: copying %zd bytes - buffer %d, offset %u\n", + count, index, buf->pos); + if (read) + ret = copy_to_user(data, buf->vaddr + buf->pos, count); + else + ret = copy_from_user(buf->vaddr + buf->pos, data, count); + if (ret) { + dprintk(3, "file io: error copying data\n"); + ret = -EFAULT; + goto end; + } + + /* + * Update counters. + */ + buf->pos += count; + *ppos += count; + + /* + * Queue next buffer if required. + */ + if (buf->pos == buf->size || + (!read && (fileio->flags & VB2_FILEIO_WRITE_IMMEDIATELY))) { + /* + * Check if this is the last buffer to read. + */ + if (read && (fileio->flags & VB2_FILEIO_READ_ONCE) && + fileio->dq_count == 1) { + dprintk(3, "file io: read limit reached\n"); + /* + * Restore fileio pointer and release the context. + */ + q->fileio = fileio; + return __vb2_cleanup_fileio(q); + } + + /* + * Call vb2_qbuf and give buffer to the driver. + */ + memset(&fileio->b, 0, sizeof(fileio->b)); + fileio->b.type = q->type; + fileio->b.memory = q->memory; + fileio->b.index = index; + fileio->b.bytesused = buf->pos; + ret = vb2_qbuf(q, &fileio->b); + dprintk(5, "file io: vb2_dbuf result: %d\n", ret); + if (ret) + goto end; + + /* + * Buffer has been queued, update the status + */ + buf->pos = 0; + buf->queued = 1; + buf->size = q->bufs[0]->v4l2_planes[0].length; + fileio->q_count += 1; + + /* + * Switch to the next buffer + */ + fileio->index = (index + 1) % q->num_buffers; + + /* + * Start streaming if required. + */ + if (!read && !q->streaming) { + ret = vb2_streamon(q, q->type); + if (ret) + goto end; + } + } + + /* + * Return proper number of bytes processed. + */ + if (ret == 0) + ret = count; +end: + /* + * Restore the fileio context and block vb2 ioctl interface. + */ + q->fileio = fileio; + return ret; +} + +size_t vb2_read(struct vb2_queue *q, char __user *data, size_t count, + loff_t *ppos, int nonblocking) +{ + return __vb2_perform_fileio(q, data, count, ppos, nonblocking, 1); +} +EXPORT_SYMBOL_GPL(vb2_read); + +size_t vb2_write(struct vb2_queue *q, char __user *data, size_t count, + loff_t *ppos, int nonblocking) +{ + return __vb2_perform_fileio(q, data, count, ppos, nonblocking, 0); +} +EXPORT_SYMBOL_GPL(vb2_write); + +MODULE_DESCRIPTION("Driver helper framework for Video for Linux 2"); +MODULE_AUTHOR("Pawel Osciak, Marek Szyprowski"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/videobuf2-dma-contig.c b/drivers/media/video/videobuf2-dma-contig.c new file mode 100644 index 000000000000..bb6a5602cf94 --- /dev/null +++ b/drivers/media/video/videobuf2-dma-contig.c @@ -0,0 +1,185 @@ +/* + * videobuf2-dma-contig.c - DMA contig memory allocator for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <p.osciak@samsung.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. + */ + +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/dma-mapping.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-memops.h> + +struct vb2_dc_conf { + struct device *dev; +}; + +struct vb2_dc_buf { + struct vb2_dc_conf *conf; + void *vaddr; + dma_addr_t paddr; + unsigned long size; + struct vm_area_struct *vma; + atomic_t refcount; + struct vb2_vmarea_handler handler; +}; + +static void vb2_dma_contig_put(void *buf_priv); + +static void *vb2_dma_contig_alloc(void *alloc_ctx, unsigned long size) +{ + struct vb2_dc_conf *conf = alloc_ctx; + struct vb2_dc_buf *buf; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + buf->vaddr = dma_alloc_coherent(conf->dev, size, &buf->paddr, + GFP_KERNEL); + if (!buf->vaddr) { + dev_err(conf->dev, "dma_alloc_coherent of size %ld failed\n", + buf->size); + kfree(buf); + return ERR_PTR(-ENOMEM); + } + + buf->conf = conf; + buf->size = size; + + buf->handler.refcount = &buf->refcount; + buf->handler.put = vb2_dma_contig_put; + buf->handler.arg = buf; + + atomic_inc(&buf->refcount); + + return buf; +} + +static void vb2_dma_contig_put(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + + if (atomic_dec_and_test(&buf->refcount)) { + dma_free_coherent(buf->conf->dev, buf->size, buf->vaddr, + buf->paddr); + kfree(buf); + } +} + +static void *vb2_dma_contig_cookie(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + + return (void *)buf->paddr; +} + +static void *vb2_dma_contig_vaddr(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + if (!buf) + return 0; + + return buf->vaddr; +} + +static unsigned int vb2_dma_contig_num_users(void *buf_priv) +{ + struct vb2_dc_buf *buf = buf_priv; + + return atomic_read(&buf->refcount); +} + +static int vb2_dma_contig_mmap(void *buf_priv, struct vm_area_struct *vma) +{ + struct vb2_dc_buf *buf = buf_priv; + + if (!buf) { + printk(KERN_ERR "No buffer to map\n"); + return -EINVAL; + } + + return vb2_mmap_pfn_range(vma, buf->paddr, buf->size, + &vb2_common_vm_ops, &buf->handler); +} + +static void *vb2_dma_contig_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + struct vb2_dc_buf *buf; + struct vm_area_struct *vma; + dma_addr_t paddr = 0; + int ret; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return ERR_PTR(-ENOMEM); + + ret = vb2_get_contig_userptr(vaddr, size, &vma, &paddr); + if (ret) { + printk(KERN_ERR "Failed acquiring VMA for vaddr 0x%08lx\n", + vaddr); + kfree(buf); + return ERR_PTR(ret); + } + + buf->size = size; + buf->paddr = paddr; + buf->vma = vma; + + return buf; +} + +static void vb2_dma_contig_put_userptr(void *mem_priv) +{ + struct vb2_dc_buf *buf = mem_priv; + + if (!buf) + return; + + vb2_put_vma(buf->vma); + kfree(buf); +} + +const struct vb2_mem_ops vb2_dma_contig_memops = { + .alloc = vb2_dma_contig_alloc, + .put = vb2_dma_contig_put, + .cookie = vb2_dma_contig_cookie, + .vaddr = vb2_dma_contig_vaddr, + .mmap = vb2_dma_contig_mmap, + .get_userptr = vb2_dma_contig_get_userptr, + .put_userptr = vb2_dma_contig_put_userptr, + .num_users = vb2_dma_contig_num_users, +}; +EXPORT_SYMBOL_GPL(vb2_dma_contig_memops); + +void *vb2_dma_contig_init_ctx(struct device *dev) +{ + struct vb2_dc_conf *conf; + + conf = kzalloc(sizeof *conf, GFP_KERNEL); + if (!conf) + return ERR_PTR(-ENOMEM); + + conf->dev = dev; + + return conf; +} +EXPORT_SYMBOL_GPL(vb2_dma_contig_init_ctx); + +void vb2_dma_contig_cleanup_ctx(void *alloc_ctx) +{ + kfree(alloc_ctx); +} +EXPORT_SYMBOL_GPL(vb2_dma_contig_cleanup_ctx); + +MODULE_DESCRIPTION("DMA-contig memory handling routines for videobuf2"); +MODULE_AUTHOR("Pawel Osciak"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/videobuf2-dma-sg.c b/drivers/media/video/videobuf2-dma-sg.c new file mode 100644 index 000000000000..20b5c5dcc0ef --- /dev/null +++ b/drivers/media/video/videobuf2-dma-sg.c @@ -0,0 +1,292 @@ +/* + * videobuf2-dma-sg.c - dma scatter/gather memory allocator for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Andrzej Pietrasiewicz <andrzej.p@samsung.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. + */ + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/scatterlist.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-memops.h> +#include <media/videobuf2-dma-sg.h> + +struct vb2_dma_sg_buf { + void *vaddr; + struct page **pages; + int write; + int offset; + struct vb2_dma_sg_desc sg_desc; + atomic_t refcount; + struct vb2_vmarea_handler handler; +}; + +static void vb2_dma_sg_put(void *buf_priv); + +static void *vb2_dma_sg_alloc(void *alloc_ctx, unsigned long size) +{ + struct vb2_dma_sg_buf *buf; + int i; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return NULL; + + buf->vaddr = NULL; + buf->write = 0; + buf->offset = 0; + buf->sg_desc.size = size; + buf->sg_desc.num_pages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT; + + buf->sg_desc.sglist = vmalloc(buf->sg_desc.num_pages * + sizeof(*buf->sg_desc.sglist)); + if (!buf->sg_desc.sglist) + goto fail_sglist_alloc; + memset(buf->sg_desc.sglist, 0, buf->sg_desc.num_pages * + sizeof(*buf->sg_desc.sglist)); + sg_init_table(buf->sg_desc.sglist, buf->sg_desc.num_pages); + + buf->pages = kzalloc(buf->sg_desc.num_pages * sizeof(struct page *), + GFP_KERNEL); + if (!buf->pages) + goto fail_pages_array_alloc; + + for (i = 0; i < buf->sg_desc.num_pages; ++i) { + buf->pages[i] = alloc_page(GFP_KERNEL); + if (NULL == buf->pages[i]) + goto fail_pages_alloc; + sg_set_page(&buf->sg_desc.sglist[i], + buf->pages[i], PAGE_SIZE, 0); + } + + buf->handler.refcount = &buf->refcount; + buf->handler.put = vb2_dma_sg_put; + buf->handler.arg = buf; + + atomic_inc(&buf->refcount); + + printk(KERN_DEBUG "%s: Allocated buffer of %d pages\n", + __func__, buf->sg_desc.num_pages); + + if (!buf->vaddr) + buf->vaddr = vm_map_ram(buf->pages, + buf->sg_desc.num_pages, + -1, + PAGE_KERNEL); + return buf; + +fail_pages_alloc: + while (--i >= 0) + __free_page(buf->pages[i]); + +fail_pages_array_alloc: + vfree(buf->sg_desc.sglist); + +fail_sglist_alloc: + kfree(buf); + return NULL; +} + +static void vb2_dma_sg_put(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + int i = buf->sg_desc.num_pages; + + if (atomic_dec_and_test(&buf->refcount)) { + printk(KERN_DEBUG "%s: Freeing buffer of %d pages\n", __func__, + buf->sg_desc.num_pages); + if (buf->vaddr) + vm_unmap_ram(buf->vaddr, buf->sg_desc.num_pages); + vfree(buf->sg_desc.sglist); + while (--i >= 0) + __free_page(buf->pages[i]); + kfree(buf->pages); + kfree(buf); + } +} + +static void *vb2_dma_sg_get_userptr(void *alloc_ctx, unsigned long vaddr, + unsigned long size, int write) +{ + struct vb2_dma_sg_buf *buf; + unsigned long first, last; + int num_pages_from_user, i; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return NULL; + + buf->vaddr = NULL; + buf->write = write; + buf->offset = vaddr & ~PAGE_MASK; + buf->sg_desc.size = size; + + first = (vaddr & PAGE_MASK) >> PAGE_SHIFT; + last = ((vaddr + size - 1) & PAGE_MASK) >> PAGE_SHIFT; + buf->sg_desc.num_pages = last - first + 1; + + buf->sg_desc.sglist = vmalloc( + buf->sg_desc.num_pages * sizeof(*buf->sg_desc.sglist)); + if (!buf->sg_desc.sglist) + goto userptr_fail_sglist_alloc; + + memset(buf->sg_desc.sglist, 0, + buf->sg_desc.num_pages * sizeof(*buf->sg_desc.sglist)); + sg_init_table(buf->sg_desc.sglist, buf->sg_desc.num_pages); + + buf->pages = kzalloc(buf->sg_desc.num_pages * sizeof(struct page *), + GFP_KERNEL); + if (!buf->pages) + goto userptr_fail_pages_array_alloc; + + down_read(¤t->mm->mmap_sem); + num_pages_from_user = get_user_pages(current, current->mm, + vaddr & PAGE_MASK, + buf->sg_desc.num_pages, + write, + 1, /* force */ + buf->pages, + NULL); + up_read(¤t->mm->mmap_sem); + if (num_pages_from_user != buf->sg_desc.num_pages) + goto userptr_fail_get_user_pages; + + sg_set_page(&buf->sg_desc.sglist[0], buf->pages[0], + PAGE_SIZE - buf->offset, buf->offset); + size -= PAGE_SIZE - buf->offset; + for (i = 1; i < buf->sg_desc.num_pages; ++i) { + sg_set_page(&buf->sg_desc.sglist[i], buf->pages[i], + min_t(size_t, PAGE_SIZE, size), 0); + size -= min_t(size_t, PAGE_SIZE, size); + } + return buf; + +userptr_fail_get_user_pages: + printk(KERN_DEBUG "get_user_pages requested/got: %d/%d]\n", + num_pages_from_user, buf->sg_desc.num_pages); + while (--num_pages_from_user >= 0) + put_page(buf->pages[num_pages_from_user]); + +userptr_fail_pages_array_alloc: + vfree(buf->sg_desc.sglist); + +userptr_fail_sglist_alloc: + kfree(buf); + return NULL; +} + +/* + * @put_userptr: inform the allocator that a USERPTR buffer will no longer + * be used + */ +static void vb2_dma_sg_put_userptr(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + int i = buf->sg_desc.num_pages; + + printk(KERN_DEBUG "%s: Releasing userspace buffer of %d pages\n", + __func__, buf->sg_desc.num_pages); + if (buf->vaddr) + vm_unmap_ram(buf->vaddr, buf->sg_desc.num_pages); + while (--i >= 0) { + if (buf->write) + set_page_dirty_lock(buf->pages[i]); + put_page(buf->pages[i]); + } + vfree(buf->sg_desc.sglist); + kfree(buf->pages); + kfree(buf); +} + +static void *vb2_dma_sg_vaddr(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + + BUG_ON(!buf); + + if (!buf->vaddr) + buf->vaddr = vm_map_ram(buf->pages, + buf->sg_desc.num_pages, + -1, + PAGE_KERNEL); + + /* add offset in case userptr is not page-aligned */ + return buf->vaddr + buf->offset; +} + +static unsigned int vb2_dma_sg_num_users(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + + return atomic_read(&buf->refcount); +} + +static int vb2_dma_sg_mmap(void *buf_priv, struct vm_area_struct *vma) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + unsigned long uaddr = vma->vm_start; + unsigned long usize = vma->vm_end - vma->vm_start; + int i = 0; + + if (!buf) { + printk(KERN_ERR "No memory to map\n"); + return -EINVAL; + } + + do { + int ret; + + ret = vm_insert_page(vma, uaddr, buf->pages[i++]); + if (ret) { + printk(KERN_ERR "Remapping memory, error: %d\n", ret); + return ret; + } + + uaddr += PAGE_SIZE; + usize -= PAGE_SIZE; + } while (usize > 0); + + + /* + * Use common vm_area operations to track buffer refcount. + */ + vma->vm_private_data = &buf->handler; + vma->vm_ops = &vb2_common_vm_ops; + + vma->vm_ops->open(vma); + + return 0; +} + +static void *vb2_dma_sg_cookie(void *buf_priv) +{ + struct vb2_dma_sg_buf *buf = buf_priv; + + return &buf->sg_desc; +} + +const struct vb2_mem_ops vb2_dma_sg_memops = { + .alloc = vb2_dma_sg_alloc, + .put = vb2_dma_sg_put, + .get_userptr = vb2_dma_sg_get_userptr, + .put_userptr = vb2_dma_sg_put_userptr, + .vaddr = vb2_dma_sg_vaddr, + .mmap = vb2_dma_sg_mmap, + .num_users = vb2_dma_sg_num_users, + .cookie = vb2_dma_sg_cookie, +}; +EXPORT_SYMBOL_GPL(vb2_dma_sg_memops); + +MODULE_DESCRIPTION("dma scatter/gather memory handling routines for videobuf2"); +MODULE_AUTHOR("Andrzej Pietrasiewicz"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/videobuf2-memops.c b/drivers/media/video/videobuf2-memops.c new file mode 100644 index 000000000000..053c15794b8d --- /dev/null +++ b/drivers/media/video/videobuf2-memops.c @@ -0,0 +1,232 @@ +/* + * videobuf2-memops.c - generic memory handling routines for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <p.osciak@samsung.com> + * Marek Szyprowski <m.szyprowski@samsung.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. + */ + +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/dma-mapping.h> +#include <linux/vmalloc.h> +#include <linux/mm.h> +#include <linux/sched.h> +#include <linux/file.h> +#include <linux/slab.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-memops.h> + +/** + * vb2_get_vma() - acquire and lock the virtual memory area + * @vma: given virtual memory area + * + * This function attempts to acquire an area mapped in the userspace for + * the duration of a hardware operation. The area is "locked" by performing + * the same set of operation that are done when process calls fork() and + * memory areas are duplicated. + * + * Returns a copy of a virtual memory region on success or NULL. + */ +struct vm_area_struct *vb2_get_vma(struct vm_area_struct *vma) +{ + struct vm_area_struct *vma_copy; + + vma_copy = kmalloc(sizeof(*vma_copy), GFP_KERNEL); + if (vma_copy == NULL) + return NULL; + + if (vma->vm_ops && vma->vm_ops->open) + vma->vm_ops->open(vma); + + if (vma->vm_file) + get_file(vma->vm_file); + + memcpy(vma_copy, vma, sizeof(*vma)); + + vma_copy->vm_mm = NULL; + vma_copy->vm_next = NULL; + vma_copy->vm_prev = NULL; + + return vma_copy; +} + +/** + * vb2_put_userptr() - release a userspace virtual memory area + * @vma: virtual memory region associated with the area to be released + * + * This function releases the previously acquired memory area after a hardware + * operation. + */ +void vb2_put_vma(struct vm_area_struct *vma) +{ + if (!vma) + return; + + if (vma->vm_file) + fput(vma->vm_file); + + if (vma->vm_ops && vma->vm_ops->close) + vma->vm_ops->close(vma); + + kfree(vma); +} + +/** + * vb2_get_contig_userptr() - lock physically contiguous userspace mapped memory + * @vaddr: starting virtual address of the area to be verified + * @size: size of the area + * @res_paddr: will return physical address for the given vaddr + * @res_vma: will return locked copy of struct vm_area for the given area + * + * This function will go through memory area of size @size mapped at @vaddr and + * verify that the underlying physical pages are contiguous. If they are + * contiguous the virtual memory area is locked and a @res_vma is filled with + * the copy and @res_pa set to the physical address of the buffer. + * + * Returns 0 on success. + */ +int vb2_get_contig_userptr(unsigned long vaddr, unsigned long size, + struct vm_area_struct **res_vma, dma_addr_t *res_pa) +{ + struct mm_struct *mm = current->mm; + struct vm_area_struct *vma; + unsigned long offset, start, end; + unsigned long this_pfn, prev_pfn; + dma_addr_t pa = 0; + int ret = -EFAULT; + + start = vaddr; + offset = start & ~PAGE_MASK; + end = start + size; + + down_read(&mm->mmap_sem); + vma = find_vma(mm, start); + + if (vma == NULL || vma->vm_end < end) + goto done; + + for (prev_pfn = 0; start < end; start += PAGE_SIZE) { + ret = follow_pfn(vma, start, &this_pfn); + if (ret) + goto done; + + if (prev_pfn == 0) + pa = this_pfn << PAGE_SHIFT; + else if (this_pfn != prev_pfn + 1) { + ret = -EFAULT; + goto done; + } + prev_pfn = this_pfn; + } + + /* + * Memory is contigous, lock vma and return to the caller + */ + *res_vma = vb2_get_vma(vma); + if (*res_vma == NULL) { + ret = -ENOMEM; + goto done; + } + *res_pa = pa + offset; + ret = 0; + +done: + up_read(&mm->mmap_sem); + return ret; +} + +/** + * vb2_mmap_pfn_range() - map physical pages to userspace + * @vma: virtual memory region for the mapping + * @paddr: starting physical address of the memory to be mapped + * @size: size of the memory to be mapped + * @vm_ops: vm operations to be assigned to the created area + * @priv: private data to be associated with the area + * + * Returns 0 on success. + */ +int vb2_mmap_pfn_range(struct vm_area_struct *vma, unsigned long paddr, + unsigned long size, + const struct vm_operations_struct *vm_ops, + void *priv) +{ + int ret; + + size = min_t(unsigned long, vma->vm_end - vma->vm_start, size); + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + ret = remap_pfn_range(vma, vma->vm_start, paddr >> PAGE_SHIFT, + size, vma->vm_page_prot); + if (ret) { + printk(KERN_ERR "Remapping memory failed, error: %d\n", ret); + return ret; + } + + vma->vm_flags |= VM_DONTEXPAND | VM_RESERVED; + vma->vm_private_data = priv; + vma->vm_ops = vm_ops; + + vma->vm_ops->open(vma); + + printk(KERN_DEBUG "%s: mapped paddr 0x%08lx at 0x%08lx, size %ld\n", + __func__, paddr, vma->vm_start, size); + + return 0; +} + +/** + * vb2_common_vm_open() - increase refcount of the vma + * @vma: virtual memory region for the mapping + * + * This function adds another user to the provided vma. It expects + * struct vb2_vmarea_handler pointer in vma->vm_private_data. + */ +static void vb2_common_vm_open(struct vm_area_struct *vma) +{ + struct vb2_vmarea_handler *h = vma->vm_private_data; + + printk(KERN_DEBUG "%s: %p, refcount: %d, vma: %08lx-%08lx\n", + __func__, h, atomic_read(h->refcount), vma->vm_start, + vma->vm_end); + + atomic_inc(h->refcount); +} + +/** + * vb2_common_vm_close() - decrease refcount of the vma + * @vma: virtual memory region for the mapping + * + * This function releases the user from the provided vma. It expects + * struct vb2_vmarea_handler pointer in vma->vm_private_data. + */ +static void vb2_common_vm_close(struct vm_area_struct *vma) +{ + struct vb2_vmarea_handler *h = vma->vm_private_data; + + printk(KERN_DEBUG "%s: %p, refcount: %d, vma: %08lx-%08lx\n", + __func__, h, atomic_read(h->refcount), vma->vm_start, + vma->vm_end); + + h->put(h->arg); +} + +/** + * vb2_common_vm_ops - common vm_ops used for tracking refcount of mmaped + * video buffers + */ +const struct vm_operations_struct vb2_common_vm_ops = { + .open = vb2_common_vm_open, + .close = vb2_common_vm_close, +}; +EXPORT_SYMBOL_GPL(vb2_common_vm_ops); + +MODULE_DESCRIPTION("common memory handling routines for videobuf2"); +MODULE_AUTHOR("Pawel Osciak"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/videobuf2-vmalloc.c b/drivers/media/video/videobuf2-vmalloc.c new file mode 100644 index 000000000000..b5e6936fb628 --- /dev/null +++ b/drivers/media/video/videobuf2-vmalloc.c @@ -0,0 +1,132 @@ +/* + * videobuf2-vmalloc.c - vmalloc memory allocator for videobuf2 + * + * Copyright (C) 2010 Samsung Electronics + * + * Author: Pawel Osciak <p.osciak@samsung.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. + */ + +#include <linux/module.h> +#include <linux/mm.h> +#include <linux/slab.h> +#include <linux/vmalloc.h> + +#include <media/videobuf2-core.h> +#include <media/videobuf2-memops.h> + +struct vb2_vmalloc_buf { + void *vaddr; + unsigned long size; + atomic_t refcount; + struct vb2_vmarea_handler handler; +}; + +static void vb2_vmalloc_put(void *buf_priv); + +static void *vb2_vmalloc_alloc(void *alloc_ctx, unsigned long size) +{ + struct vb2_vmalloc_buf *buf; + + buf = kzalloc(sizeof *buf, GFP_KERNEL); + if (!buf) + return NULL; + + buf->size = size; + buf->vaddr = vmalloc_user(buf->size); + buf->handler.refcount = &buf->refcount; + buf->handler.put = vb2_vmalloc_put; + buf->handler.arg = buf; + + if (!buf->vaddr) { + printk(KERN_ERR "vmalloc of size %ld failed\n", buf->size); + kfree(buf); + return NULL; + } + + atomic_inc(&buf->refcount); + printk(KERN_DEBUG "Allocated vmalloc buffer of size %ld at vaddr=%p\n", + buf->size, buf->vaddr); + + return buf; +} + +static void vb2_vmalloc_put(void *buf_priv) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + + if (atomic_dec_and_test(&buf->refcount)) { + printk(KERN_DEBUG "%s: Freeing vmalloc mem at vaddr=%p\n", + __func__, buf->vaddr); + vfree(buf->vaddr); + kfree(buf); + } +} + +static void *vb2_vmalloc_vaddr(void *buf_priv) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + + BUG_ON(!buf); + + if (!buf->vaddr) { + printk(KERN_ERR "Address of an unallocated plane requested\n"); + return NULL; + } + + return buf->vaddr; +} + +static unsigned int vb2_vmalloc_num_users(void *buf_priv) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + return atomic_read(&buf->refcount); +} + +static int vb2_vmalloc_mmap(void *buf_priv, struct vm_area_struct *vma) +{ + struct vb2_vmalloc_buf *buf = buf_priv; + int ret; + + if (!buf) { + printk(KERN_ERR "No memory to map\n"); + return -EINVAL; + } + + ret = remap_vmalloc_range(vma, buf->vaddr, 0); + if (ret) { + printk(KERN_ERR "Remapping vmalloc memory, error: %d\n", ret); + return ret; + } + + /* + * Make sure that vm_areas for 2 buffers won't be merged together + */ + vma->vm_flags |= VM_DONTEXPAND; + + /* + * Use common vm_area operations to track buffer refcount. + */ + vma->vm_private_data = &buf->handler; + vma->vm_ops = &vb2_common_vm_ops; + + vma->vm_ops->open(vma); + + return 0; +} + +const struct vb2_mem_ops vb2_vmalloc_memops = { + .alloc = vb2_vmalloc_alloc, + .put = vb2_vmalloc_put, + .vaddr = vb2_vmalloc_vaddr, + .mmap = vb2_vmalloc_mmap, + .num_users = vb2_vmalloc_num_users, +}; +EXPORT_SYMBOL_GPL(vb2_vmalloc_memops); + +MODULE_DESCRIPTION("vmalloc memory handling routines for videobuf2"); +MODULE_AUTHOR("Pawel Osciak"); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/vivi.c b/drivers/media/video/vivi.c index c49c39386bd0..bd104d0ad36c 100644 --- a/drivers/media/video/vivi.c +++ b/drivers/media/video/vivi.c @@ -7,6 +7,9 @@ * John Sokol <sokol--a.t--videotechnology.com> * http://v4l.videotechnology.com/ * + * Conversion to videobuf2 by Pawel Osciak & Marek Szyprowski + * Copyright (c) 2010 Samsung Electronics + * * This program is free software; you can redistribute it and/or modify * it under the terms of the BSD Licence, GNU General Public License * as published by the Free Software Foundation; either version 2 of the @@ -23,12 +26,11 @@ #include <linux/mutex.h> #include <linux/videodev2.h> #include <linux/kthread.h> -#if LINUX_VERSION_CODE >= KERNEL_VERSION(2, 6, 20) #include <linux/freezer.h> -#endif -#include <media/videobuf-vmalloc.h> +#include <media/videobuf2-vmalloc.h> #include <media/v4l2-device.h> #include <media/v4l2-ioctl.h> +#include <media/v4l2-ctrls.h> #include <media/v4l2-common.h> #define VIVI_MODULE_NAME "vivi" @@ -42,7 +44,7 @@ #define MAX_HEIGHT 1200 #define VIVI_MAJOR_VERSION 0 -#define VIVI_MINOR_VERSION 7 +#define VIVI_MINOR_VERSION 8 #define VIVI_RELEASE 0 #define VIVI_VERSION \ KERNEL_VERSION(VIVI_MAJOR_VERSION, VIVI_MINOR_VERSION, VIVI_RELEASE) @@ -133,16 +135,11 @@ static struct vivi_fmt *get_format(struct v4l2_format *f) return &formats[k]; } -struct sg_to_addr { - int pos; - struct scatterlist *sg; -}; - /* buffer for one video frame */ struct vivi_buffer { /* common v4l buffer stuff -- must be first */ - struct videobuf_buffer vb; - + struct vb2_buffer vb; + struct list_head list; struct vivi_fmt *fmt; }; @@ -162,13 +159,20 @@ static LIST_HEAD(vivi_devlist); struct vivi_dev { struct list_head vivi_devlist; struct v4l2_device v4l2_dev; + struct v4l2_ctrl_handler ctrl_handler; /* controls */ - int brightness; - int contrast; - int saturation; - int hue; - int volume; + struct v4l2_ctrl *brightness; + struct v4l2_ctrl *contrast; + struct v4l2_ctrl *saturation; + struct v4l2_ctrl *hue; + struct v4l2_ctrl *volume; + struct v4l2_ctrl *button; + struct v4l2_ctrl *boolean; + struct v4l2_ctrl *int32; + struct v4l2_ctrl *int64; + struct v4l2_ctrl *menu; + struct v4l2_ctrl *string; spinlock_t slock; struct mutex mutex; @@ -181,6 +185,7 @@ struct vivi_dev { /* Several counters */ unsigned ms; unsigned long jiffies; + unsigned button_pressed; int mv_count; /* Controls bars movement */ @@ -190,9 +195,11 @@ struct vivi_dev { /* video capture */ struct vivi_fmt *fmt; unsigned int width, height; - struct videobuf_queue vb_vidq; + struct vb2_queue vb_vidq; + enum v4l2_field field; + unsigned int field_count; - unsigned long generating; + unsigned int open_count; u8 bars[9][3]; u8 line[MAX_WIDTH * 4]; }; @@ -443,10 +450,10 @@ static void gen_text(struct vivi_dev *dev, char *basep, static void vivi_fillbuff(struct vivi_dev *dev, struct vivi_buffer *buf) { - int hmax = buf->vb.height; - int wmax = buf->vb.width; + int wmax = dev->width; + int hmax = dev->height; struct timeval ts; - void *vbuf = videobuf_to_vmalloc(&buf->vb); + void *vbuf = vb2_plane_vaddr(&buf->vb, 0); unsigned ms; char str[100]; int h, line = 1; @@ -472,22 +479,38 @@ static void vivi_fillbuff(struct vivi_dev *dev, struct vivi_buffer *buf) dev->width, dev->height, dev->input); gen_text(dev, vbuf, line++ * 16, 16, str); + mutex_lock(&dev->ctrl_handler.lock); snprintf(str, sizeof(str), " brightness %3d, contrast %3d, saturation %3d, hue %d ", - dev->brightness, - dev->contrast, - dev->saturation, - dev->hue); + dev->brightness->cur.val, + dev->contrast->cur.val, + dev->saturation->cur.val, + dev->hue->cur.val); gen_text(dev, vbuf, line++ * 16, 16, str); - snprintf(str, sizeof(str), " volume %3d ", dev->volume); + snprintf(str, sizeof(str), " volume %3d ", dev->volume->cur.val); gen_text(dev, vbuf, line++ * 16, 16, str); + snprintf(str, sizeof(str), " int32 %d, int64 %lld ", + dev->int32->cur.val, + dev->int64->cur.val64); + gen_text(dev, vbuf, line++ * 16, 16, str); + snprintf(str, sizeof(str), " boolean %d, menu %s, string \"%s\" ", + dev->boolean->cur.val, + dev->menu->qmenu[dev->menu->cur.val], + dev->string->cur.string); + mutex_unlock(&dev->ctrl_handler.lock); + gen_text(dev, vbuf, line++ * 16, 16, str); + if (dev->button_pressed) { + dev->button_pressed--; + snprintf(str, sizeof(str), " button pressed!"); + gen_text(dev, vbuf, line++ * 16, 16, str); + } dev->mv_count += 2; - /* Advice that buffer was filled */ - buf->vb.field_count++; + buf->vb.v4l2_buf.field = dev->field; + dev->field_count++; + buf->vb.v4l2_buf.sequence = dev->field_count >> 1; do_gettimeofday(&ts); - buf->vb.ts = ts; - buf->vb.state = VIDEOBUF_DONE; + buf->vb.v4l2_buf.timestamp = ts; } static void vivi_thread_tick(struct vivi_dev *dev) @@ -504,23 +527,17 @@ static void vivi_thread_tick(struct vivi_dev *dev) goto unlock; } - buf = list_entry(dma_q->active.next, - struct vivi_buffer, vb.queue); + buf = list_entry(dma_q->active.next, struct vivi_buffer, list); + list_del(&buf->list); - /* Nobody is waiting on this buffer, return */ - if (!waitqueue_active(&buf->vb.done)) - goto unlock; - - list_del(&buf->vb.queue); - - do_gettimeofday(&buf->vb.ts); + do_gettimeofday(&buf->vb.v4l2_buf.timestamp); /* Fill buffer */ vivi_fillbuff(dev, buf); dprintk(dev, 1, "filled buffer %p\n", buf); - wake_up(&buf->vb.done); - dprintk(dev, 2, "[%p/%d] wakeup\n", buf, buf->vb. i); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_DONE); + dprintk(dev, 2, "[%p/%d] done\n", buf, buf->vb.v4l2_buf.index); unlock: spin_unlock_irqrestore(&dev->slock, flags); } @@ -571,17 +588,12 @@ static int vivi_thread(void *data) return 0; } -static void vivi_start_generating(struct file *file) +static int vivi_start_generating(struct vivi_dev *dev) { - struct vivi_dev *dev = video_drvdata(file); struct vivi_dmaqueue *dma_q = &dev->vidq; dprintk(dev, 1, "%s\n", __func__); - if (test_and_set_bit(0, &dev->generating)) - return; - file->private_data = dev; - /* Resets frame counters */ dev->ms = 0; dev->mv_count = 0; @@ -593,146 +605,200 @@ static void vivi_start_generating(struct file *file) if (IS_ERR(dma_q->kthread)) { v4l2_err(&dev->v4l2_dev, "kernel_thread() failed\n"); - clear_bit(0, &dev->generating); - return; + return PTR_ERR(dma_q->kthread); } /* Wakes thread */ wake_up_interruptible(&dma_q->wq); dprintk(dev, 1, "returning from %s\n", __func__); + return 0; } -static void vivi_stop_generating(struct file *file) +static void vivi_stop_generating(struct vivi_dev *dev) { - struct vivi_dev *dev = video_drvdata(file); struct vivi_dmaqueue *dma_q = &dev->vidq; dprintk(dev, 1, "%s\n", __func__); - if (!file->private_data) - return; - if (!test_and_clear_bit(0, &dev->generating)) - return; - /* shutdown control thread */ if (dma_q->kthread) { kthread_stop(dma_q->kthread); dma_q->kthread = NULL; } - videobuf_stop(&dev->vb_vidq); - videobuf_mmap_free(&dev->vb_vidq); -} -static int vivi_is_generating(struct vivi_dev *dev) -{ - return test_bit(0, &dev->generating); + /* + * Typical driver might need to wait here until dma engine stops. + * In this case we can abort imiedetly, so it's just a noop. + */ + + /* Release all active buffers */ + while (!list_empty(&dma_q->active)) { + struct vivi_buffer *buf; + buf = list_entry(dma_q->active.next, struct vivi_buffer, list); + list_del(&buf->list); + vb2_buffer_done(&buf->vb, VB2_BUF_STATE_ERROR); + dprintk(dev, 2, "[%p/%d] done\n", buf, buf->vb.v4l2_buf.index); + } } - /* ------------------------------------------------------------------ Videobuf operations ------------------------------------------------------------------*/ -static int -buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size) +static int queue_setup(struct vb2_queue *vq, unsigned int *nbuffers, + unsigned int *nplanes, unsigned long sizes[], + void *alloc_ctxs[]) { - struct vivi_dev *dev = vq->priv_data; + struct vivi_dev *dev = vb2_get_drv_priv(vq); + unsigned long size; + + size = dev->width * dev->height * 2; + + if (0 == *nbuffers) + *nbuffers = 32; + + while (size * *nbuffers > vid_limit * 1024 * 1024) + (*nbuffers)--; - *size = dev->width * dev->height * 2; + *nplanes = 1; - if (0 == *count) - *count = 32; + sizes[0] = size; - while (*size * *count > vid_limit * 1024 * 1024) - (*count)--; + /* + * videobuf2-vmalloc allocator is context-less so no need to set + * alloc_ctxs array. + */ - dprintk(dev, 1, "%s, count=%d, size=%d\n", __func__, - *count, *size); + dprintk(dev, 1, "%s, count=%d, size=%ld\n", __func__, + *nbuffers, size); return 0; } -static void free_buffer(struct videobuf_queue *vq, struct vivi_buffer *buf) +static int buffer_init(struct vb2_buffer *vb) { - struct vivi_dev *dev = vq->priv_data; + struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue); - dprintk(dev, 1, "%s, state: %i\n", __func__, buf->vb.state); + BUG_ON(NULL == dev->fmt); + + /* + * This callback is called once per buffer, after its allocation. + * + * Vivi does not allow changing format during streaming, but it is + * possible to do so when streaming is paused (i.e. in streamoff state). + * Buffers however are not freed when going into streamoff and so + * buffer size verification has to be done in buffer_prepare, on each + * qbuf. + * It would be best to move verification code here to buf_init and + * s_fmt though. + */ - videobuf_vmalloc_free(&buf->vb); - dprintk(dev, 1, "free_buffer: freed\n"); - buf->vb.state = VIDEOBUF_NEEDS_INIT; + return 0; } -static int -buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, - enum v4l2_field field) +static int buffer_prepare(struct vb2_buffer *vb) { - struct vivi_dev *dev = vq->priv_data; + struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue); struct vivi_buffer *buf = container_of(vb, struct vivi_buffer, vb); - int rc; + unsigned long size; - dprintk(dev, 1, "%s, field=%d\n", __func__, field); + dprintk(dev, 1, "%s, field=%d\n", __func__, vb->v4l2_buf.field); BUG_ON(NULL == dev->fmt); + /* + * Theses properties only change when queue is idle, see s_fmt. + * The below checks should not be performed here, on each + * buffer_prepare (i.e. on each qbuf). Most of the code in this function + * should thus be moved to buffer_init and s_fmt. + */ if (dev->width < 48 || dev->width > MAX_WIDTH || dev->height < 32 || dev->height > MAX_HEIGHT) return -EINVAL; - buf->vb.size = dev->width * dev->height * 2; - if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + size = dev->width * dev->height * 2; + if (vb2_plane_size(vb, 0) < size) { + dprintk(dev, 1, "%s data will not fit into plane (%lu < %lu)\n", + __func__, vb2_plane_size(vb, 0), size); return -EINVAL; + } + + vb2_set_plane_payload(&buf->vb, 0, size); - /* These properties only change when queue is idle, see s_fmt */ - buf->fmt = dev->fmt; - buf->vb.width = dev->width; - buf->vb.height = dev->height; - buf->vb.field = field; + buf->fmt = dev->fmt; precalculate_bars(dev); precalculate_line(dev); - if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { - rc = videobuf_iolock(vq, &buf->vb, NULL); - if (rc < 0) - goto fail; - } + return 0; +} - buf->vb.state = VIDEOBUF_PREPARED; +static int buffer_finish(struct vb2_buffer *vb) +{ + struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue); + dprintk(dev, 1, "%s\n", __func__); return 0; +} + +static void buffer_cleanup(struct vb2_buffer *vb) +{ + struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue); + dprintk(dev, 1, "%s\n", __func__); -fail: - free_buffer(vq, buf); - return rc; } -static void -buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +static void buffer_queue(struct vb2_buffer *vb) { - struct vivi_dev *dev = vq->priv_data; + struct vivi_dev *dev = vb2_get_drv_priv(vb->vb2_queue); struct vivi_buffer *buf = container_of(vb, struct vivi_buffer, vb); struct vivi_dmaqueue *vidq = &dev->vidq; + unsigned long flags = 0; dprintk(dev, 1, "%s\n", __func__); - buf->vb.state = VIDEOBUF_QUEUED; - list_add_tail(&buf->vb.queue, &vidq->active); + spin_lock_irqsave(&dev->slock, flags); + list_add_tail(&buf->list, &vidq->active); + spin_unlock_irqrestore(&dev->slock, flags); } -static void buffer_release(struct videobuf_queue *vq, - struct videobuf_buffer *vb) +static int start_streaming(struct vb2_queue *vq) { - struct vivi_dev *dev = vq->priv_data; - struct vivi_buffer *buf = container_of(vb, struct vivi_buffer, vb); + struct vivi_dev *dev = vb2_get_drv_priv(vq); + dprintk(dev, 1, "%s\n", __func__); + return vivi_start_generating(dev); +} +/* abort streaming and wait for last buffer */ +static int stop_streaming(struct vb2_queue *vq) +{ + struct vivi_dev *dev = vb2_get_drv_priv(vq); dprintk(dev, 1, "%s\n", __func__); + vivi_stop_generating(dev); + return 0; +} + +static void vivi_lock(struct vb2_queue *vq) +{ + struct vivi_dev *dev = vb2_get_drv_priv(vq); + mutex_lock(&dev->mutex); +} - free_buffer(vq, buf); +static void vivi_unlock(struct vb2_queue *vq) +{ + struct vivi_dev *dev = vb2_get_drv_priv(vq); + mutex_unlock(&dev->mutex); } -static struct videobuf_queue_ops vivi_video_qops = { - .buf_setup = buffer_setup, - .buf_prepare = buffer_prepare, - .buf_queue = buffer_queue, - .buf_release = buffer_release, + +static struct vb2_ops vivi_video_qops = { + .queue_setup = queue_setup, + .buf_init = buffer_init, + .buf_prepare = buffer_prepare, + .buf_finish = buffer_finish, + .buf_cleanup = buffer_cleanup, + .buf_queue = buffer_queue, + .start_streaming = start_streaming, + .stop_streaming = stop_streaming, + .wait_prepare = vivi_unlock, + .wait_finish = vivi_lock, }; /* ------------------------------------------------------------------ @@ -774,7 +840,7 @@ static int vidioc_g_fmt_vid_cap(struct file *file, void *priv, f->fmt.pix.width = dev->width; f->fmt.pix.height = dev->height; - f->fmt.pix.field = dev->vb_vidq.field; + f->fmt.pix.field = dev->field; f->fmt.pix.pixelformat = dev->fmt->fourcc; f->fmt.pix.bytesperline = (f->fmt.pix.width * dev->fmt->depth) >> 3; @@ -820,82 +886,60 @@ static int vidioc_s_fmt_vid_cap(struct file *file, void *priv, struct v4l2_format *f) { struct vivi_dev *dev = video_drvdata(file); + struct vb2_queue *q = &dev->vb_vidq; int ret = vidioc_try_fmt_vid_cap(file, priv, f); if (ret < 0) return ret; - if (vivi_is_generating(dev)) { + if (vb2_is_streaming(q)) { dprintk(dev, 1, "%s device busy\n", __func__); - ret = -EBUSY; - goto out; + return -EBUSY; } dev->fmt = get_format(f); dev->width = f->fmt.pix.width; dev->height = f->fmt.pix.height; - dev->vb_vidq.field = f->fmt.pix.field; - ret = 0; -out: - return ret; + dev->field = f->fmt.pix.field; + + return 0; } static int vidioc_reqbufs(struct file *file, void *priv, struct v4l2_requestbuffers *p) { struct vivi_dev *dev = video_drvdata(file); - - return videobuf_reqbufs(&dev->vb_vidq, p); + return vb2_reqbufs(&dev->vb_vidq, p); } static int vidioc_querybuf(struct file *file, void *priv, struct v4l2_buffer *p) { struct vivi_dev *dev = video_drvdata(file); - - return videobuf_querybuf(&dev->vb_vidq, p); + return vb2_querybuf(&dev->vb_vidq, p); } static int vidioc_qbuf(struct file *file, void *priv, struct v4l2_buffer *p) { struct vivi_dev *dev = video_drvdata(file); - - return videobuf_qbuf(&dev->vb_vidq, p); + return vb2_qbuf(&dev->vb_vidq, p); } static int vidioc_dqbuf(struct file *file, void *priv, struct v4l2_buffer *p) { struct vivi_dev *dev = video_drvdata(file); - - return videobuf_dqbuf(&dev->vb_vidq, p, - file->f_flags & O_NONBLOCK); + return vb2_dqbuf(&dev->vb_vidq, p, file->f_flags & O_NONBLOCK); } static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) { struct vivi_dev *dev = video_drvdata(file); - int ret; - - if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - ret = videobuf_streamon(&dev->vb_vidq); - if (ret) - return ret; - - vivi_start_generating(file); - return 0; + return vb2_streamon(&dev->vb_vidq, i); } static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) { struct vivi_dev *dev = video_drvdata(file); - int ret; - - if (i != V4L2_BUF_TYPE_VIDEO_CAPTURE) - return -EINVAL; - ret = videobuf_streamoff(&dev->vb_vidq); - if (!ret) - vivi_stop_generating(file); - return ret; + return vb2_streamoff(&dev->vb_vidq, i); } static int vidioc_s_std(struct file *file, void *priv, v4l2_std_id *i) @@ -938,106 +982,47 @@ static int vidioc_s_input(struct file *file, void *priv, unsigned int i) } /* --- controls ---------------------------------------------- */ -static int vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_AUDIO_VOLUME: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 200); - case V4L2_CID_BRIGHTNESS: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 127); - case V4L2_CID_CONTRAST: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 16); - case V4L2_CID_SATURATION: - return v4l2_ctrl_query_fill(qc, 0, 255, 1, 127); - case V4L2_CID_HUE: - return v4l2_ctrl_query_fill(qc, -128, 127, 1, 0); - } - return -EINVAL; -} -static int vidioc_g_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) +static int vivi_s_ctrl(struct v4l2_ctrl *ctrl) { - struct vivi_dev *dev = video_drvdata(file); + struct vivi_dev *dev = container_of(ctrl->handler, struct vivi_dev, ctrl_handler); - switch (ctrl->id) { - case V4L2_CID_AUDIO_VOLUME: - ctrl->value = dev->volume; - return 0; - case V4L2_CID_BRIGHTNESS: - ctrl->value = dev->brightness; - return 0; - case V4L2_CID_CONTRAST: - ctrl->value = dev->contrast; - return 0; - case V4L2_CID_SATURATION: - ctrl->value = dev->saturation; - return 0; - case V4L2_CID_HUE: - ctrl->value = dev->hue; - return 0; - } - return -EINVAL; -} - -static int vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *ctrl) -{ - struct vivi_dev *dev = video_drvdata(file); - struct v4l2_queryctrl qc; - int err; - - qc.id = ctrl->id; - err = vidioc_queryctrl(file, priv, &qc); - if (err < 0) - return err; - if (ctrl->value < qc.minimum || ctrl->value > qc.maximum) - return -ERANGE; - switch (ctrl->id) { - case V4L2_CID_AUDIO_VOLUME: - dev->volume = ctrl->value; - return 0; - case V4L2_CID_BRIGHTNESS: - dev->brightness = ctrl->value; - return 0; - case V4L2_CID_CONTRAST: - dev->contrast = ctrl->value; - return 0; - case V4L2_CID_SATURATION: - dev->saturation = ctrl->value; - return 0; - case V4L2_CID_HUE: - dev->hue = ctrl->value; - return 0; - } - return -EINVAL; + if (ctrl == dev->button) + dev->button_pressed = 30; + return 0; } /* ------------------------------------------------------------------ File operations for the device ------------------------------------------------------------------*/ +static int vivi_open(struct file *file) +{ + struct vivi_dev *dev = video_drvdata(file); + + dprintk(dev, 1, "%s, %p\n", __func__, file); + dev->open_count++; + return 0; +} + static ssize_t vivi_read(struct file *file, char __user *data, size_t count, loff_t *ppos) { struct vivi_dev *dev = video_drvdata(file); - vivi_start_generating(file); - return videobuf_read_stream(&dev->vb_vidq, data, count, ppos, 0, - file->f_flags & O_NONBLOCK); + dprintk(dev, 1, "read called\n"); + return vb2_read(&dev->vb_vidq, data, count, ppos, + file->f_flags & O_NONBLOCK); } static unsigned int vivi_poll(struct file *file, struct poll_table_struct *wait) { struct vivi_dev *dev = video_drvdata(file); - struct videobuf_queue *q = &dev->vb_vidq; + struct vb2_queue *q = &dev->vb_vidq; dprintk(dev, 1, "%s\n", __func__); - - vivi_start_generating(file); - return videobuf_poll_stream(file, q, wait); + return vb2_poll(q, file, wait); } static int vivi_close(struct file *file) @@ -1045,10 +1030,11 @@ static int vivi_close(struct file *file) struct video_device *vdev = video_devdata(file); struct vivi_dev *dev = video_drvdata(file); - vivi_stop_generating(file); + dprintk(dev, 1, "close called (dev=%s), file %p\n", + video_device_node_name(vdev), file); - dprintk(dev, 1, "close called (dev=%s)\n", - video_device_node_name(vdev)); + if (--dev->open_count == 0) + vb2_queue_release(&dev->vb_vidq); return 0; } @@ -1059,8 +1045,7 @@ static int vivi_mmap(struct file *file, struct vm_area_struct *vma) dprintk(dev, 1, "mmap called, vma=0x%08lx\n", (unsigned long)vma); - ret = videobuf_mmap_mapper(&dev->vb_vidq, vma); - + ret = vb2_mmap(&dev->vb_vidq, vma); dprintk(dev, 1, "vma start=0x%08lx, size=%ld, ret=%d\n", (unsigned long)vma->vm_start, (unsigned long)vma->vm_end - (unsigned long)vma->vm_start, @@ -1068,8 +1053,82 @@ static int vivi_mmap(struct file *file, struct vm_area_struct *vma) return ret; } +static const struct v4l2_ctrl_ops vivi_ctrl_ops = { + .s_ctrl = vivi_s_ctrl, +}; + +#define VIVI_CID_CUSTOM_BASE (V4L2_CID_USER_BASE | 0xf000) + +static const struct v4l2_ctrl_config vivi_ctrl_button = { + .ops = &vivi_ctrl_ops, + .id = VIVI_CID_CUSTOM_BASE + 0, + .name = "Button", + .type = V4L2_CTRL_TYPE_BUTTON, +}; + +static const struct v4l2_ctrl_config vivi_ctrl_boolean = { + .ops = &vivi_ctrl_ops, + .id = VIVI_CID_CUSTOM_BASE + 1, + .name = "Boolean", + .type = V4L2_CTRL_TYPE_BOOLEAN, + .min = 0, + .max = 1, + .step = 1, + .def = 1, +}; + +static const struct v4l2_ctrl_config vivi_ctrl_int32 = { + .ops = &vivi_ctrl_ops, + .id = VIVI_CID_CUSTOM_BASE + 2, + .name = "Integer 32 Bits", + .type = V4L2_CTRL_TYPE_INTEGER, + .min = 0x80000000, + .max = 0x7fffffff, + .step = 1, +}; + +static const struct v4l2_ctrl_config vivi_ctrl_int64 = { + .ops = &vivi_ctrl_ops, + .id = VIVI_CID_CUSTOM_BASE + 3, + .name = "Integer 64 Bits", + .type = V4L2_CTRL_TYPE_INTEGER64, +}; + +static const char * const vivi_ctrl_menu_strings[] = { + "Menu Item 0 (Skipped)", + "Menu Item 1", + "Menu Item 2 (Skipped)", + "Menu Item 3", + "Menu Item 4", + "Menu Item 5 (Skipped)", + NULL, +}; + +static const struct v4l2_ctrl_config vivi_ctrl_menu = { + .ops = &vivi_ctrl_ops, + .id = VIVI_CID_CUSTOM_BASE + 4, + .name = "Menu", + .type = V4L2_CTRL_TYPE_MENU, + .min = 1, + .max = 4, + .def = 3, + .menu_skip_mask = 0x04, + .qmenu = vivi_ctrl_menu_strings, +}; + +static const struct v4l2_ctrl_config vivi_ctrl_string = { + .ops = &vivi_ctrl_ops, + .id = VIVI_CID_CUSTOM_BASE + 5, + .name = "String", + .type = V4L2_CTRL_TYPE_STRING, + .min = 2, + .max = 4, + .step = 1, +}; + static const struct v4l2_file_operations vivi_fops = { .owner = THIS_MODULE, + .open = vivi_open, .release = vivi_close, .read = vivi_read, .poll = vivi_poll, @@ -1093,9 +1152,6 @@ static const struct v4l2_ioctl_ops vivi_ioctl_ops = { .vidioc_s_input = vidioc_s_input, .vidioc_streamon = vidioc_streamon, .vidioc_streamoff = vidioc_streamoff, - .vidioc_queryctrl = vidioc_queryctrl, - .vidioc_g_ctrl = vidioc_g_ctrl, - .vidioc_s_ctrl = vidioc_s_ctrl, }; static struct video_device vivi_template = { @@ -1126,6 +1182,7 @@ static int vivi_release(void) video_device_node_name(dev->vfd)); video_unregister_device(dev->vfd); v4l2_device_unregister(&dev->v4l2_dev); + v4l2_ctrl_handler_free(&dev->ctrl_handler); kfree(dev); } @@ -1136,6 +1193,8 @@ static int __init vivi_create_instance(int inst) { struct vivi_dev *dev; struct video_device *vfd; + struct v4l2_ctrl_handler *hdl; + struct vb2_queue *q; int ret; dev = kzalloc(sizeof(*dev), GFP_KERNEL); @@ -1151,20 +1210,46 @@ static int __init vivi_create_instance(int inst) dev->fmt = &formats[0]; dev->width = 640; dev->height = 480; - dev->volume = 200; - dev->brightness = 127; - dev->contrast = 16; - dev->saturation = 127; - dev->hue = 0; + hdl = &dev->ctrl_handler; + v4l2_ctrl_handler_init(hdl, 11); + dev->volume = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops, + V4L2_CID_AUDIO_VOLUME, 0, 255, 1, 200); + dev->brightness = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops, + V4L2_CID_BRIGHTNESS, 0, 255, 1, 127); + dev->contrast = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops, + V4L2_CID_CONTRAST, 0, 255, 1, 16); + dev->saturation = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops, + V4L2_CID_SATURATION, 0, 255, 1, 127); + dev->hue = v4l2_ctrl_new_std(hdl, &vivi_ctrl_ops, + V4L2_CID_HUE, -128, 127, 1, 0); + dev->button = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_button, NULL); + dev->int32 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int32, NULL); + dev->int64 = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_int64, NULL); + dev->boolean = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_boolean, NULL); + dev->menu = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_menu, NULL); + dev->string = v4l2_ctrl_new_custom(hdl, &vivi_ctrl_string, NULL); + if (hdl->error) { + ret = hdl->error; + goto unreg_dev; + } + dev->v4l2_dev.ctrl_handler = hdl; /* initialize locks */ spin_lock_init(&dev->slock); - mutex_init(&dev->mutex); - videobuf_queue_vmalloc_init(&dev->vb_vidq, &vivi_video_qops, - NULL, &dev->slock, V4L2_BUF_TYPE_VIDEO_CAPTURE, - V4L2_FIELD_INTERLACED, - sizeof(struct vivi_buffer), dev, &dev->mutex); + /* initialize queue */ + q = &dev->vb_vidq; + memset(q, 0, sizeof(dev->vb_vidq)); + q->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + q->io_modes = VB2_MMAP | VB2_USERPTR | VB2_READ; + q->drv_priv = dev; + q->buf_struct_size = sizeof(struct vivi_buffer); + q->ops = &vivi_video_qops; + q->mem_ops = &vb2_vmalloc_memops; + + vb2_queue_init(q); + + mutex_init(&dev->mutex); /* init video dma queues */ INIT_LIST_HEAD(&dev->vidq.active); @@ -1178,6 +1263,11 @@ static int __init vivi_create_instance(int inst) *vfd = vivi_template; vfd->debug = debug; vfd->v4l2_dev = &dev->v4l2_dev; + + /* + * Provide a mutex to v4l2 core. It will be used to protect + * all fops and v4l2 ioctls. + */ vfd->lock = &dev->mutex; ret = video_register_device(vfd, VFL_TYPE_GRABBER, video_nr); @@ -1200,6 +1290,7 @@ static int __init vivi_create_instance(int inst) rel_vdev: video_device_release(vfd); unreg_dev: + v4l2_ctrl_handler_free(hdl); v4l2_device_unregister(&dev->v4l2_dev); free_dev: kfree(dev); diff --git a/drivers/media/video/vpx3220.c b/drivers/media/video/vpx3220.c index 91a01b3cdf8c..75301d10a838 100644 --- a/drivers/media/video/vpx3220.c +++ b/drivers/media/video/vpx3220.c @@ -28,6 +28,7 @@ #include <linux/videodev2.h> #include <media/v4l2-device.h> #include <media/v4l2-chip-ident.h> +#include <media/v4l2-ctrls.h> MODULE_DESCRIPTION("vpx3220a/vpx3216b/vpx3214c video decoder driver"); MODULE_AUTHOR("Laurent Pinchart"); @@ -44,16 +45,13 @@ MODULE_PARM_DESC(debug, "Debug level (0-1)"); struct vpx3220 { struct v4l2_subdev sd; + struct v4l2_ctrl_handler hdl; unsigned char reg[255]; v4l2_std_id norm; int ident; int input; int enable; - int bright; - int contrast; - int hue; - int sat; }; static inline struct vpx3220 *to_vpx3220(struct v4l2_subdev *sd) @@ -61,6 +59,11 @@ static inline struct vpx3220 *to_vpx3220(struct v4l2_subdev *sd) return container_of(sd, struct vpx3220, sd); } +static inline struct v4l2_subdev *to_sd(struct v4l2_ctrl *ctrl) +{ + return &container_of(ctrl->handler, struct vpx3220, hdl)->sd; +} + static char *inputs[] = { "internal", "composite", "svideo" }; /* ----------------------------------------------------------------------- */ @@ -417,88 +420,26 @@ static int vpx3220_s_stream(struct v4l2_subdev *sd, int enable) return 0; } -static int vpx3220_queryctrl(struct v4l2_subdev *sd, struct v4l2_queryctrl *qc) -{ - switch (qc->id) { - case V4L2_CID_BRIGHTNESS: - v4l2_ctrl_query_fill(qc, -128, 127, 1, 0); - break; - - case V4L2_CID_CONTRAST: - v4l2_ctrl_query_fill(qc, 0, 63, 1, 32); - break; - - case V4L2_CID_SATURATION: - v4l2_ctrl_query_fill(qc, 0, 4095, 1, 2048); - break; - - case V4L2_CID_HUE: - v4l2_ctrl_query_fill(qc, -512, 511, 1, 0); - break; - - default: - return -EINVAL; - } - return 0; -} - -static int vpx3220_g_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) +static int vpx3220_s_ctrl(struct v4l2_ctrl *ctrl) { - struct vpx3220 *decoder = to_vpx3220(sd); + struct v4l2_subdev *sd = to_sd(ctrl); switch (ctrl->id) { case V4L2_CID_BRIGHTNESS: - ctrl->value = decoder->bright; - break; + vpx3220_write(sd, 0xe6, ctrl->val); + return 0; case V4L2_CID_CONTRAST: - ctrl->value = decoder->contrast; - break; + /* Bit 7 and 8 is for noise shaping */ + vpx3220_write(sd, 0xe7, ctrl->val + 192); + return 0; case V4L2_CID_SATURATION: - ctrl->value = decoder->sat; - break; + vpx3220_fp_write(sd, 0xa0, ctrl->val); + return 0; case V4L2_CID_HUE: - ctrl->value = decoder->hue; - break; - default: - return -EINVAL; + vpx3220_fp_write(sd, 0x1c, ctrl->val); + return 0; } - return 0; -} - -static int vpx3220_s_ctrl(struct v4l2_subdev *sd, struct v4l2_control *ctrl) -{ - struct vpx3220 *decoder = to_vpx3220(sd); - - switch (ctrl->id) { - case V4L2_CID_BRIGHTNESS: - if (decoder->bright != ctrl->value) { - decoder->bright = ctrl->value; - vpx3220_write(sd, 0xe6, decoder->bright); - } - break; - case V4L2_CID_CONTRAST: - if (decoder->contrast != ctrl->value) { - /* Bit 7 and 8 is for noise shaping */ - decoder->contrast = ctrl->value; - vpx3220_write(sd, 0xe7, decoder->contrast + 192); - } - break; - case V4L2_CID_SATURATION: - if (decoder->sat != ctrl->value) { - decoder->sat = ctrl->value; - vpx3220_fp_write(sd, 0xa0, decoder->sat); - } - break; - case V4L2_CID_HUE: - if (decoder->hue != ctrl->value) { - decoder->hue = ctrl->value; - vpx3220_fp_write(sd, 0x1c, decoder->hue); - } - break; - default: - return -EINVAL; - } - return 0; + return -EINVAL; } static int vpx3220_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ident *chip) @@ -511,12 +452,20 @@ static int vpx3220_g_chip_ident(struct v4l2_subdev *sd, struct v4l2_dbg_chip_ide /* ----------------------------------------------------------------------- */ +static const struct v4l2_ctrl_ops vpx3220_ctrl_ops = { + .s_ctrl = vpx3220_s_ctrl, +}; + static const struct v4l2_subdev_core_ops vpx3220_core_ops = { .g_chip_ident = vpx3220_g_chip_ident, .init = vpx3220_init, - .g_ctrl = vpx3220_g_ctrl, - .s_ctrl = vpx3220_s_ctrl, - .queryctrl = vpx3220_queryctrl, + .g_ext_ctrls = v4l2_subdev_g_ext_ctrls, + .try_ext_ctrls = v4l2_subdev_try_ext_ctrls, + .s_ext_ctrls = v4l2_subdev_s_ext_ctrls, + .g_ctrl = v4l2_subdev_g_ctrl, + .s_ctrl = v4l2_subdev_s_ctrl, + .queryctrl = v4l2_subdev_queryctrl, + .querymenu = v4l2_subdev_querymenu, .s_std = vpx3220_s_std, }; @@ -558,10 +507,24 @@ static int vpx3220_probe(struct i2c_client *client, decoder->norm = V4L2_STD_PAL; decoder->input = 0; decoder->enable = 1; - decoder->bright = 32768; - decoder->contrast = 32768; - decoder->hue = 32768; - decoder->sat = 32768; + v4l2_ctrl_handler_init(&decoder->hdl, 4); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_BRIGHTNESS, -128, 127, 1, 0); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_CONTRAST, 0, 63, 1, 32); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_SATURATION, 0, 4095, 1, 2048); + v4l2_ctrl_new_std(&decoder->hdl, &vpx3220_ctrl_ops, + V4L2_CID_HUE, -512, 511, 1, 0); + sd->ctrl_handler = &decoder->hdl; + if (decoder->hdl.error) { + int err = decoder->hdl.error; + + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); + return err; + } + v4l2_ctrl_handler_setup(&decoder->hdl); ver = i2c_smbus_read_byte_data(client, 0x00); pn = (i2c_smbus_read_byte_data(client, 0x02) << 8) + @@ -599,9 +562,11 @@ static int vpx3220_probe(struct i2c_client *client, static int vpx3220_remove(struct i2c_client *client) { struct v4l2_subdev *sd = i2c_get_clientdata(client); + struct vpx3220 *decoder = to_vpx3220(sd); v4l2_device_unregister_subdev(sd); - kfree(to_vpx3220(sd)); + v4l2_ctrl_handler_free(&decoder->hdl); + kfree(decoder); return 0; } diff --git a/drivers/message/fusion/lsi/mpi_cnfg.h b/drivers/message/fusion/lsi/mpi_cnfg.h index 013c7d881948..22027e7946f7 100644 --- a/drivers/message/fusion/lsi/mpi_cnfg.h +++ b/drivers/message/fusion/lsi/mpi_cnfg.h @@ -2593,6 +2593,7 @@ typedef struct _CONFIG_PAGE_SAS_IO_UNIT_0 #define MPI_SAS_IOUNIT0_RATE_SATA_OOB_COMPLETE (0x03) #define MPI_SAS_IOUNIT0_RATE_1_5 (0x08) #define MPI_SAS_IOUNIT0_RATE_3_0 (0x09) +#define MPI_SAS_IOUNIT0_RATE_6_0 (0x0A) /* see mpi_sas.h for values for SAS IO Unit Page 0 ControllerPhyDeviceInfo values */ diff --git a/drivers/message/fusion/lsi/mpi_ioc.h b/drivers/message/fusion/lsi/mpi_ioc.h index 8faa4fab7b89..fd6222882a0e 100644 --- a/drivers/message/fusion/lsi/mpi_ioc.h +++ b/drivers/message/fusion/lsi/mpi_ioc.h @@ -841,6 +841,7 @@ typedef struct _EVENT_DATA_SAS_PHY_LINK_STATUS #define MPI_EVENT_SAS_PLS_LR_RATE_SATA_OOB_COMPLETE (0x03) #define MPI_EVENT_SAS_PLS_LR_RATE_1_5 (0x08) #define MPI_EVENT_SAS_PLS_LR_RATE_3_0 (0x09) +#define MPI_EVENT_SAS_PLS_LR_RATE_6_0 (0x0A) /* SAS Discovery Event data */ diff --git a/drivers/message/fusion/mptbase.c b/drivers/message/fusion/mptbase.c index 3358c0af3466..ec8080c98081 100644 --- a/drivers/message/fusion/mptbase.c +++ b/drivers/message/fusion/mptbase.c @@ -7418,7 +7418,12 @@ mpt_display_event_info(MPT_ADAPTER *ioc, EventNotificationReply_t *pEventReply) case MPI_EVENT_SAS_PLS_LR_RATE_3_0: snprintf(evStr, EVENT_DESCR_STR_SZ, "SAS PHY Link Status: Phy=%d:" - " Rate 3.0 Gpbs",PhyNumber); + " Rate 3.0 Gbps", PhyNumber); + break; + case MPI_EVENT_SAS_PLS_LR_RATE_6_0: + snprintf(evStr, EVENT_DESCR_STR_SZ, + "SAS PHY Link Status: Phy=%d:" + " Rate 6.0 Gbps", PhyNumber); break; default: snprintf(evStr, EVENT_DESCR_STR_SZ, diff --git a/drivers/message/fusion/mptctl.c b/drivers/message/fusion/mptctl.c index e8deb8ed0499..878bda0cce70 100644 --- a/drivers/message/fusion/mptctl.c +++ b/drivers/message/fusion/mptctl.c @@ -1314,8 +1314,10 @@ mptctl_getiocinfo (unsigned long arg, unsigned int data_size) else karg->adapterType = MPT_IOCTL_INTERFACE_SCSI; - if (karg->hdr.port > 1) + if (karg->hdr.port > 1) { + kfree(karg); return -EINVAL; + } port = karg->hdr.port; karg->port = port; diff --git a/drivers/message/fusion/mptsas.c b/drivers/message/fusion/mptsas.c index 8aefb1829fcd..f5a14afad2cd 100644 --- a/drivers/message/fusion/mptsas.c +++ b/drivers/message/fusion/mptsas.c @@ -1973,7 +1973,6 @@ static struct scsi_host_template mptsas_driver_template = { .change_queue_depth = mptscsih_change_queue_depth, .eh_abort_handler = mptscsih_abort, .eh_device_reset_handler = mptscsih_dev_reset, - .eh_bus_reset_handler = mptscsih_bus_reset, .eh_host_reset_handler = mptscsih_host_reset, .bios_param = mptscsih_bios_param, .can_queue = MPT_SAS_CAN_QUEUE, @@ -3063,6 +3062,9 @@ static int mptsas_probe_one_phy(struct device *dev, case MPI_SAS_IOUNIT0_RATE_3_0: phy->negotiated_linkrate = SAS_LINK_RATE_3_0_GBPS; break; + case MPI_SAS_IOUNIT0_RATE_6_0: + phy->negotiated_linkrate = SAS_LINK_RATE_6_0_GBPS; + break; case MPI_SAS_IOUNIT0_RATE_SATA_OOB_COMPLETE: case MPI_SAS_IOUNIT0_RATE_UNKNOWN: default: @@ -3691,7 +3693,8 @@ mptsas_send_link_status_event(struct fw_event_work *fw_event) } if (link_rate == MPI_SAS_IOUNIT0_RATE_1_5 || - link_rate == MPI_SAS_IOUNIT0_RATE_3_0) { + link_rate == MPI_SAS_IOUNIT0_RATE_3_0 || + link_rate == MPI_SAS_IOUNIT0_RATE_6_0) { if (!port_info) { if (ioc->old_sas_discovery_protocal) { diff --git a/drivers/misc/Kconfig b/drivers/misc/Kconfig index cc8e49db45fe..a8ad7eacf0aa 100644 --- a/drivers/misc/Kconfig +++ b/drivers/misc/Kconfig @@ -2,6 +2,14 @@ # Misc strange devices # +# This one has to live outside of the MISC_DEVICES conditional, +# because it may be selected by drivers/platform/x86/hp_accel. +config SENSORS_LIS3LV02D + tristate + depends on INPUT + select INPUT_POLLDEV + default n + menuconfig MISC_DEVICES bool "Misc devices" ---help--- @@ -457,5 +465,6 @@ source "drivers/misc/eeprom/Kconfig" source "drivers/misc/cb710/Kconfig" source "drivers/misc/iwmc3200top/Kconfig" source "drivers/misc/ti-st/Kconfig" +source "drivers/misc/lis3lv02d/Kconfig" endif # MISC_DEVICES diff --git a/drivers/misc/Makefile b/drivers/misc/Makefile index 98009cc20cb9..804f421bc079 100644 --- a/drivers/misc/Makefile +++ b/drivers/misc/Makefile @@ -42,3 +42,4 @@ obj-$(CONFIG_ARM_CHARLCD) += arm-charlcd.o obj-$(CONFIG_PCH_PHUB) += pch_phub.o obj-y += ti-st/ obj-$(CONFIG_AB8500_PWM) += ab8500-pwm.o +obj-y += lis3lv02d/ diff --git a/drivers/misc/lis3lv02d/Kconfig b/drivers/misc/lis3lv02d/Kconfig new file mode 100644 index 000000000000..8f474e6fc7b4 --- /dev/null +++ b/drivers/misc/lis3lv02d/Kconfig @@ -0,0 +1,37 @@ +# +# STMicroelectonics LIS3LV02D and similar accelerometers +# + +config SENSORS_LIS3_SPI + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (SPI)" + depends on !ACPI && SPI_MASTER && INPUT + select SENSORS_LIS3LV02D + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via SPI. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the laptop to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the SPI transport + is called lis3lv02d_spi. + +config SENSORS_LIS3_I2C + tristate "STMicroeletronics LIS3LV02Dx three-axis digital accelerometer (I2C)" + depends on I2C && INPUT + select SENSORS_LIS3LV02D + default n + help + This driver provides support for the LIS3LV02Dx accelerometer connected + via I2C. The accelerometer data is readable via + /sys/devices/platform/lis3lv02d. + + This driver also provides an absolute input class device, allowing + the device to act as a pinball machine-esque joystick. + + This driver can also be built as modules. If so, the core module + will be called lis3lv02d and a specific module for the I2C transport + is called lis3lv02d_i2c. diff --git a/drivers/misc/lis3lv02d/Makefile b/drivers/misc/lis3lv02d/Makefile new file mode 100644 index 000000000000..4bf58b16fcf8 --- /dev/null +++ b/drivers/misc/lis3lv02d/Makefile @@ -0,0 +1,7 @@ +# +# STMicroelectonics LIS3LV02D and similar accelerometers +# + +obj-$(CONFIG_SENSORS_LIS3LV02D) += lis3lv02d.o +obj-$(CONFIG_SENSORS_LIS3_SPI) += lis3lv02d_spi.o +obj-$(CONFIG_SENSORS_LIS3_I2C) += lis3lv02d_i2c.o diff --git a/drivers/hwmon/lis3lv02d.c b/drivers/misc/lis3lv02d/lis3lv02d.c index d805e8e57967..b928bc14e97b 100644 --- a/drivers/hwmon/lis3lv02d.c +++ b/drivers/misc/lis3lv02d/lis3lv02d.c @@ -38,7 +38,7 @@ #include <linux/uaccess.h> #include <linux/miscdevice.h> #include <linux/pm_runtime.h> -#include <asm/atomic.h> +#include <linux/atomic.h> #include "lis3lv02d.h" #define DRIVER_NAME "lis3lv02d" @@ -88,7 +88,6 @@ struct lis3lv02d lis3_dev = { .misc_wait = __WAIT_QUEUE_HEAD_INITIALIZER(lis3_dev.misc_wait), }; - EXPORT_SYMBOL_GPL(lis3_dev); /* just like param_set_int() but does sanity-check so that it won't point diff --git a/drivers/hwmon/lis3lv02d.h b/drivers/misc/lis3lv02d/lis3lv02d.h index a1939589eb2c..a1939589eb2c 100644 --- a/drivers/hwmon/lis3lv02d.h +++ b/drivers/misc/lis3lv02d/lis3lv02d.h diff --git a/drivers/hwmon/lis3lv02d_i2c.c b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c index 8853afce85ce..b20dfb4522d2 100644 --- a/drivers/hwmon/lis3lv02d_i2c.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_i2c.c @@ -33,7 +33,7 @@ #include <linux/delay.h> #include "lis3lv02d.h" -#define DRV_NAME "lis3lv02d_i2c" +#define DRV_NAME "lis3lv02d_i2c" static const char reg_vdd[] = "Vdd"; static const char reg_vdd_io[] = "Vdd_IO"; diff --git a/drivers/hwmon/lis3lv02d_spi.c b/drivers/misc/lis3lv02d/lis3lv02d_spi.c index 2549de1de4e2..962c9642cf35 100644 --- a/drivers/hwmon/lis3lv02d_spi.c +++ b/drivers/misc/lis3lv02d/lis3lv02d_spi.c @@ -16,10 +16,11 @@ #include <linux/interrupt.h> #include <linux/workqueue.h> #include <linux/spi/spi.h> +#include <linux/pm.h> #include "lis3lv02d.h" -#define DRV_NAME "lis3lv02d_spi" +#define DRV_NAME "lis3lv02d_spi" #define LIS3_SPI_READ 0x80 static int lis3_spi_read(struct lis3lv02d *lis3, int reg, u8 *v) @@ -88,9 +89,10 @@ static int __devexit lis302dl_spi_remove(struct spi_device *spi) return lis3lv02d_remove_fs(&lis3_dev); } -#ifdef CONFIG_PM -static int lis3lv02d_spi_suspend(struct spi_device *spi, pm_message_t mesg) +#ifdef CONFIG_PM_SLEEP +static int lis3lv02d_spi_suspend(struct device *dev) { + struct spi_device *spi = to_spi_device(dev); struct lis3lv02d *lis3 = spi_get_drvdata(spi); if (!lis3->pdata || !lis3->pdata->wakeup_flags) @@ -99,8 +101,9 @@ static int lis3lv02d_spi_suspend(struct spi_device *spi, pm_message_t mesg) return 0; } -static int lis3lv02d_spi_resume(struct spi_device *spi) +static int lis3lv02d_spi_resume(struct device *dev) { + struct spi_device *spi = to_spi_device(dev); struct lis3lv02d *lis3 = spi_get_drvdata(spi); if (!lis3->pdata || !lis3->pdata->wakeup_flags) @@ -108,21 +111,19 @@ static int lis3lv02d_spi_resume(struct spi_device *spi) return 0; } - -#else -#define lis3lv02d_spi_suspend NULL -#define lis3lv02d_spi_resume NULL #endif +static SIMPLE_DEV_PM_OPS(lis3lv02d_spi_pm, lis3lv02d_spi_suspend, + lis3lv02d_spi_resume); + static struct spi_driver lis302dl_spi_driver = { .driver = { .name = DRV_NAME, .owner = THIS_MODULE, + .pm = &lis3lv02d_spi_pm, }, .probe = lis302dl_spi_probe, .remove = __devexit_p(lis302dl_spi_remove), - .suspend = lis3lv02d_spi_suspend, - .resume = lis3lv02d_spi_resume, }; static int __init lis302dl_init(void) diff --git a/drivers/mmc/host/Kconfig b/drivers/mmc/host/Kconfig index afe8c6fa166a..54f91321749a 100644 --- a/drivers/mmc/host/Kconfig +++ b/drivers/mmc/host/Kconfig @@ -225,7 +225,7 @@ config MMC_OMAP config MMC_OMAP_HS tristate "TI OMAP High Speed Multimedia Card Interface support" - depends on ARCH_OMAP2430 || ARCH_OMAP3 || ARCH_OMAP4 + depends on SOC_OMAP2430 || ARCH_OMAP3 || ARCH_OMAP4 help This selects the TI OMAP High Speed Multimedia card Interface. If you have an OMAP2430 or OMAP3 board or OMAP4 board with a diff --git a/drivers/mmc/host/mmci.c b/drivers/mmc/host/mmci.c index 2d6de3e03e2d..5bbb87d10251 100644 --- a/drivers/mmc/host/mmci.c +++ b/drivers/mmc/host/mmci.c @@ -2,7 +2,7 @@ * linux/drivers/mmc/host/mmci.c - ARM PrimeCell MMCI PL180/1 driver * * Copyright (C) 2003 Deep Blue Solutions, Ltd, All Rights Reserved. - * Copyright (C) 2010 ST-Ericsson AB. + * Copyright (C) 2010 ST-Ericsson SA * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License version 2 as @@ -25,8 +25,10 @@ #include <linux/clk.h> #include <linux/scatterlist.h> #include <linux/gpio.h> -#include <linux/amba/mmci.h> #include <linux/regulator/consumer.h> +#include <linux/dmaengine.h> +#include <linux/dma-mapping.h> +#include <linux/amba/mmci.h> #include <asm/div64.h> #include <asm/io.h> @@ -142,9 +144,6 @@ mmci_request_end(struct mmci_host *host, struct mmc_request *mrq) host->mrq = NULL; host->cmd = NULL; - if (mrq->data) - mrq->data->bytes_xfered = host->data_xfered; - /* * Need to drop the host lock here; mmc_request_done may call * back into the driver... @@ -189,6 +188,248 @@ static void mmci_init_sg(struct mmci_host *host, struct mmc_data *data) sg_miter_start(&host->sg_miter, data->sg, data->sg_len, flags); } +/* + * All the DMA operation mode stuff goes inside this ifdef. + * This assumes that you have a generic DMA device interface, + * no custom DMA interfaces are supported. + */ +#ifdef CONFIG_DMA_ENGINE +static void __devinit mmci_dma_setup(struct mmci_host *host) +{ + struct mmci_platform_data *plat = host->plat; + const char *rxname, *txname; + dma_cap_mask_t mask; + + if (!plat || !plat->dma_filter) { + dev_info(mmc_dev(host->mmc), "no DMA platform data\n"); + return; + } + + /* Try to acquire a generic DMA engine slave channel */ + dma_cap_zero(mask); + dma_cap_set(DMA_SLAVE, mask); + + /* + * If only an RX channel is specified, the driver will + * attempt to use it bidirectionally, however if it is + * is specified but cannot be located, DMA will be disabled. + */ + if (plat->dma_rx_param) { + host->dma_rx_channel = dma_request_channel(mask, + plat->dma_filter, + plat->dma_rx_param); + /* E.g if no DMA hardware is present */ + if (!host->dma_rx_channel) + dev_err(mmc_dev(host->mmc), "no RX DMA channel\n"); + } + + if (plat->dma_tx_param) { + host->dma_tx_channel = dma_request_channel(mask, + plat->dma_filter, + plat->dma_tx_param); + if (!host->dma_tx_channel) + dev_warn(mmc_dev(host->mmc), "no TX DMA channel\n"); + } else { + host->dma_tx_channel = host->dma_rx_channel; + } + + if (host->dma_rx_channel) + rxname = dma_chan_name(host->dma_rx_channel); + else + rxname = "none"; + + if (host->dma_tx_channel) + txname = dma_chan_name(host->dma_tx_channel); + else + txname = "none"; + + dev_info(mmc_dev(host->mmc), "DMA channels RX %s, TX %s\n", + rxname, txname); + + /* + * Limit the maximum segment size in any SG entry according to + * the parameters of the DMA engine device. + */ + if (host->dma_tx_channel) { + struct device *dev = host->dma_tx_channel->device->dev; + unsigned int max_seg_size = dma_get_max_seg_size(dev); + + if (max_seg_size < host->mmc->max_seg_size) + host->mmc->max_seg_size = max_seg_size; + } + if (host->dma_rx_channel) { + struct device *dev = host->dma_rx_channel->device->dev; + unsigned int max_seg_size = dma_get_max_seg_size(dev); + + if (max_seg_size < host->mmc->max_seg_size) + host->mmc->max_seg_size = max_seg_size; + } +} + +/* + * This is used in __devinit or __devexit so inline it + * so it can be discarded. + */ +static inline void mmci_dma_release(struct mmci_host *host) +{ + struct mmci_platform_data *plat = host->plat; + + if (host->dma_rx_channel) + dma_release_channel(host->dma_rx_channel); + if (host->dma_tx_channel && plat->dma_tx_param) + dma_release_channel(host->dma_tx_channel); + host->dma_rx_channel = host->dma_tx_channel = NULL; +} + +static void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) +{ + struct dma_chan *chan = host->dma_current; + enum dma_data_direction dir; + u32 status; + int i; + + /* Wait up to 1ms for the DMA to complete */ + for (i = 0; ; i++) { + status = readl(host->base + MMCISTATUS); + if (!(status & MCI_RXDATAAVLBLMASK) || i >= 100) + break; + udelay(10); + } + + /* + * Check to see whether we still have some data left in the FIFO - + * this catches DMA controllers which are unable to monitor the + * DMALBREQ and DMALSREQ signals while allowing us to DMA to non- + * contiguous buffers. On TX, we'll get a FIFO underrun error. + */ + if (status & MCI_RXDATAAVLBLMASK) { + dmaengine_terminate_all(chan); + if (!data->error) + data->error = -EIO; + } + + if (data->flags & MMC_DATA_WRITE) { + dir = DMA_TO_DEVICE; + } else { + dir = DMA_FROM_DEVICE; + } + + dma_unmap_sg(chan->device->dev, data->sg, data->sg_len, dir); + + /* + * Use of DMA with scatter-gather is impossible. + * Give up with DMA and switch back to PIO mode. + */ + if (status & MCI_RXDATAAVLBLMASK) { + dev_err(mmc_dev(host->mmc), "buggy DMA detected. Taking evasive action.\n"); + mmci_dma_release(host); + } +} + +static void mmci_dma_data_error(struct mmci_host *host) +{ + dev_err(mmc_dev(host->mmc), "error during DMA transfer!\n"); + dmaengine_terminate_all(host->dma_current); +} + +static int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +{ + struct variant_data *variant = host->variant; + struct dma_slave_config conf = { + .src_addr = host->phybase + MMCIFIFO, + .dst_addr = host->phybase + MMCIFIFO, + .src_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .dst_addr_width = DMA_SLAVE_BUSWIDTH_4_BYTES, + .src_maxburst = variant->fifohalfsize >> 2, /* # of words */ + .dst_maxburst = variant->fifohalfsize >> 2, /* # of words */ + }; + struct mmc_data *data = host->data; + struct dma_chan *chan; + struct dma_device *device; + struct dma_async_tx_descriptor *desc; + int nr_sg; + + host->dma_current = NULL; + + if (data->flags & MMC_DATA_READ) { + conf.direction = DMA_FROM_DEVICE; + chan = host->dma_rx_channel; + } else { + conf.direction = DMA_TO_DEVICE; + chan = host->dma_tx_channel; + } + + /* If there's no DMA channel, fall back to PIO */ + if (!chan) + return -EINVAL; + + /* If less than or equal to the fifo size, don't bother with DMA */ + if (host->size <= variant->fifosize) + return -EINVAL; + + device = chan->device; + nr_sg = dma_map_sg(device->dev, data->sg, data->sg_len, conf.direction); + if (nr_sg == 0) + return -EINVAL; + + dmaengine_slave_config(chan, &conf); + desc = device->device_prep_slave_sg(chan, data->sg, nr_sg, + conf.direction, DMA_CTRL_ACK); + if (!desc) + goto unmap_exit; + + /* Okay, go for it. */ + host->dma_current = chan; + + dev_vdbg(mmc_dev(host->mmc), + "Submit MMCI DMA job, sglen %d blksz %04x blks %04x flags %08x\n", + data->sg_len, data->blksz, data->blocks, data->flags); + dmaengine_submit(desc); + dma_async_issue_pending(chan); + + datactrl |= MCI_DPSM_DMAENABLE; + + /* Trigger the DMA transfer */ + writel(datactrl, host->base + MMCIDATACTRL); + + /* + * Let the MMCI say when the data is ended and it's time + * to fire next DMA request. When that happens, MMCI will + * call mmci_data_end() + */ + writel(readl(host->base + MMCIMASK0) | MCI_DATAENDMASK, + host->base + MMCIMASK0); + return 0; + +unmap_exit: + dmaengine_terminate_all(chan); + dma_unmap_sg(device->dev, data->sg, data->sg_len, conf.direction); + return -ENOMEM; +} +#else +/* Blank functions if the DMA engine is not available */ +static inline void mmci_dma_setup(struct mmci_host *host) +{ +} + +static inline void mmci_dma_release(struct mmci_host *host) +{ +} + +static inline void mmci_dma_unmap(struct mmci_host *host, struct mmc_data *data) +{ +} + +static inline void mmci_dma_data_error(struct mmci_host *host) +{ +} + +static inline int mmci_dma_start_data(struct mmci_host *host, unsigned int datactrl) +{ + return -ENOSYS; +} +#endif + static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) { struct variant_data *variant = host->variant; @@ -202,9 +443,7 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) host->data = data; host->size = data->blksz * data->blocks; - host->data_xfered = 0; - - mmci_init_sg(host, data); + data->bytes_xfered = 0; clks = (unsigned long long)data->timeout_ns * host->cclk; do_div(clks, 1000000000UL); @@ -219,15 +458,29 @@ static void mmci_start_data(struct mmci_host *host, struct mmc_data *data) BUG_ON(1 << blksz_bits != data->blksz); datactrl = MCI_DPSM_ENABLE | blksz_bits << 4; - if (data->flags & MMC_DATA_READ) { + + if (data->flags & MMC_DATA_READ) datactrl |= MCI_DPSM_DIRECTION; + + /* + * Attempt to use DMA operation mode, if this + * should fail, fall back to PIO mode + */ + if (!mmci_dma_start_data(host, datactrl)) + return; + + /* IRQ mode, map the SG list for CPU reading/writing */ + mmci_init_sg(host, data); + + if (data->flags & MMC_DATA_READ) { irqmask = MCI_RXFIFOHALFFULLMASK; /* - * If we have less than a FIFOSIZE of bytes to transfer, - * trigger a PIO interrupt as soon as any data is available. + * If we have less than the fifo 'half-full' threshold to + * transfer, trigger a PIO interrupt as soon as any data + * is available. */ - if (host->size < variant->fifosize) + if (host->size < variant->fifohalfsize) irqmask |= MCI_RXDATAAVLBLMASK; } else { /* @@ -283,49 +536,51 @@ mmci_data_irq(struct mmci_host *host, struct mmc_data *data, if (status & (MCI_DATACRCFAIL|MCI_DATATIMEOUT|MCI_TXUNDERRUN|MCI_RXOVERRUN)) { u32 remain, success; - /* Calculate how far we are into the transfer */ + /* Terminate the DMA transfer */ + if (dma_inprogress(host)) + mmci_dma_data_error(host); + + /* + * Calculate how far we are into the transfer. Note that + * the data counter gives the number of bytes transferred + * on the MMC bus, not on the host side. On reads, this + * can be as much as a FIFO-worth of data ahead. This + * matters for FIFO overruns only. + */ remain = readl(host->base + MMCIDATACNT); success = data->blksz * data->blocks - remain; - dev_dbg(mmc_dev(host->mmc), "MCI ERROR IRQ (status %08x)\n", status); + dev_dbg(mmc_dev(host->mmc), "MCI ERROR IRQ, status 0x%08x at 0x%08x\n", + status, success); if (status & MCI_DATACRCFAIL) { /* Last block was not successful */ - host->data_xfered = round_down(success - 1, data->blksz); + success -= 1; data->error = -EILSEQ; } else if (status & MCI_DATATIMEOUT) { - host->data_xfered = round_down(success, data->blksz); data->error = -ETIMEDOUT; - } else if (status & (MCI_TXUNDERRUN|MCI_RXOVERRUN)) { - host->data_xfered = round_down(success, data->blksz); + } else if (status & MCI_TXUNDERRUN) { + data->error = -EIO; + } else if (status & MCI_RXOVERRUN) { + if (success > host->variant->fifosize) + success -= host->variant->fifosize; + else + success = 0; data->error = -EIO; } - - /* - * We hit an error condition. Ensure that any data - * partially written to a page is properly coherent. - */ - if (data->flags & MMC_DATA_READ) { - struct sg_mapping_iter *sg_miter = &host->sg_miter; - unsigned long flags; - - local_irq_save(flags); - if (sg_miter_next(sg_miter)) { - flush_dcache_page(sg_miter->page); - sg_miter_stop(sg_miter); - } - local_irq_restore(flags); - } + data->bytes_xfered = round_down(success, data->blksz); } if (status & MCI_DATABLOCKEND) dev_err(mmc_dev(host->mmc), "stray MCI_DATABLOCKEND interrupt\n"); if (status & MCI_DATAEND || data->error) { + if (dma_inprogress(host)) + mmci_dma_unmap(host, data); mmci_stop_data(host); if (!data->error) /* The error clause is handled above, success! */ - host->data_xfered += data->blksz * data->blocks; + data->bytes_xfered = data->blksz * data->blocks; if (!data->stop) { mmci_request_end(host, data->mrq); @@ -498,9 +753,6 @@ static irqreturn_t mmci_pio_irq(int irq, void *dev_id) if (remain) break; - if (status & MCI_RXACTIVE) - flush_dcache_page(sg_miter->page); - status = readl(base + MMCISTATUS); } while (1); @@ -509,10 +761,10 @@ static irqreturn_t mmci_pio_irq(int irq, void *dev_id) local_irq_restore(flags); /* - * If we're nearing the end of the read, switch to - * "any data available" mode. + * If we have less than the fifo 'half-full' threshold to transfer, + * trigger a PIO interrupt as soon as any data is available. */ - if (status & MCI_RXACTIVE && host->size < variant->fifosize) + if (status & MCI_RXACTIVE && host->size < variant->fifohalfsize) mmci_set_mask1(host, MCI_RXDATAAVLBLMASK); /* @@ -713,7 +965,8 @@ static const struct mmc_host_ops mmci_ops = { .get_cd = mmci_get_cd, }; -static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) +static int __devinit mmci_probe(struct amba_device *dev, + const struct amba_id *id) { struct mmci_platform_data *plat = dev->dev.platform_data; struct variant_data *variant = id->data; @@ -776,6 +1029,7 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) dev_dbg(mmc_dev(mmc), "eventual mclk rate: %u Hz\n", host->mclk); } + host->phybase = dev->res.start; host->base = ioremap(dev->res.start, resource_size(&dev->res)); if (!host->base) { ret = -ENOMEM; @@ -903,9 +1157,12 @@ static int __devinit mmci_probe(struct amba_device *dev, struct amba_id *id) amba_set_drvdata(dev, mmc); - dev_info(&dev->dev, "%s: PL%03x rev%u at 0x%08llx irq %d,%d\n", - mmc_hostname(mmc), amba_part(dev), amba_rev(dev), - (unsigned long long)dev->res.start, dev->irq[0], dev->irq[1]); + dev_info(&dev->dev, "%s: PL%03x manf %x rev%u at 0x%08llx irq %d,%d (pio)\n", + mmc_hostname(mmc), amba_part(dev), amba_manf(dev), + amba_rev(dev), (unsigned long long)dev->res.start, + dev->irq[0], dev->irq[1]); + + mmci_dma_setup(host); mmc_add_host(mmc); @@ -952,6 +1209,7 @@ static int __devexit mmci_remove(struct amba_device *dev) writel(0, host->base + MMCICOMMAND); writel(0, host->base + MMCIDATACTRL); + mmci_dma_release(host); free_irq(dev->irq[0], host); if (!host->singleirq) free_irq(dev->irq[1], host); diff --git a/drivers/mmc/host/mmci.h b/drivers/mmc/host/mmci.h index c1df7b82d36c..ec9a7bc6d0df 100644 --- a/drivers/mmc/host/mmci.h +++ b/drivers/mmc/host/mmci.h @@ -148,8 +148,10 @@ struct clk; struct variant_data; +struct dma_chan; struct mmci_host { + phys_addr_t phybase; void __iomem *base; struct mmc_request *mrq; struct mmc_command *cmd; @@ -161,8 +163,6 @@ struct mmci_host { int gpio_cd_irq; bool singleirq; - unsigned int data_xfered; - spinlock_t lock; unsigned int mclk; @@ -181,5 +181,16 @@ struct mmci_host { struct sg_mapping_iter sg_miter; unsigned int size; struct regulator *vcc; + +#ifdef CONFIG_DMA_ENGINE + /* DMA stuff */ + struct dma_chan *dma_current; + struct dma_chan *dma_rx_channel; + struct dma_chan *dma_tx_channel; + +#define dma_inprogress(host) ((host)->dma_current) +#else +#define dma_inprogress(host) (0) +#endif }; diff --git a/drivers/mmc/host/msm_sdcc.c b/drivers/mmc/host/msm_sdcc.c index 153ab977a013..97c9b3638d57 100644 --- a/drivers/mmc/host/msm_sdcc.c +++ b/drivers/mmc/host/msm_sdcc.c @@ -36,6 +36,7 @@ #include <linux/io.h> #include <linux/memory.h> #include <linux/gfp.h> +#include <linux/gpio.h> #include <asm/cacheflush.h> #include <asm/div64.h> @@ -941,6 +942,38 @@ msmsdcc_request(struct mmc_host *mmc, struct mmc_request *mrq) spin_unlock_irqrestore(&host->lock, flags); } +static void msmsdcc_setup_gpio(struct msmsdcc_host *host, bool enable) +{ + struct msm_mmc_gpio_data *curr; + int i, rc = 0; + + if (!host->plat->gpio_data && host->gpio_config_status == enable) + return; + + curr = host->plat->gpio_data; + for (i = 0; i < curr->size; i++) { + if (enable) { + rc = gpio_request(curr->gpio[i].no, + curr->gpio[i].name); + if (rc) { + pr_err("%s: gpio_request(%d, %s) failed %d\n", + mmc_hostname(host->mmc), + curr->gpio[i].no, + curr->gpio[i].name, rc); + goto free_gpios; + } + } else { + gpio_free(curr->gpio[i].no); + } + } + host->gpio_config_status = enable; + return; + +free_gpios: + for (; i >= 0; i--) + gpio_free(curr->gpio[i].no); +} + static void msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) { @@ -953,6 +986,8 @@ msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) msmsdcc_enable_clocks(host); + spin_unlock_irqrestore(&host->lock, flags); + if (ios->clock) { if (ios->clock != host->clk_rate) { rc = clk_set_rate(host->clk, ios->clock); @@ -979,9 +1014,11 @@ msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) switch (ios->power_mode) { case MMC_POWER_OFF: + msmsdcc_setup_gpio(host, false); break; case MMC_POWER_UP: pwr |= MCI_PWR_UP; + msmsdcc_setup_gpio(host, true); break; case MMC_POWER_ON: pwr |= MCI_PWR_ON; @@ -998,9 +1035,10 @@ msmsdcc_set_ios(struct mmc_host *mmc, struct mmc_ios *ios) msmsdcc_writel(host, pwr, MMCIPOWER); } #if BUSCLK_PWRSAVE + spin_lock_irqsave(&host->lock, flags); msmsdcc_disable_clocks(host, 1); -#endif spin_unlock_irqrestore(&host->lock, flags); +#endif } static void msmsdcc_enable_sdio_irq(struct mmc_host *mmc, int enable) diff --git a/drivers/mmc/host/msm_sdcc.h b/drivers/mmc/host/msm_sdcc.h index 939557af266d..42d7bbc977c5 100644 --- a/drivers/mmc/host/msm_sdcc.h +++ b/drivers/mmc/host/msm_sdcc.h @@ -243,6 +243,7 @@ struct msmsdcc_host { unsigned int cmd_datactrl; struct mmc_command *cmd_cmd; u32 cmd_c; + bool gpio_config_status; bool prog_scan; bool prog_enable; diff --git a/drivers/mmc/host/omap_hsmmc.c b/drivers/mmc/host/omap_hsmmc.c index 078fdf11af03..8c42573f42ea 100644 --- a/drivers/mmc/host/omap_hsmmc.c +++ b/drivers/mmc/host/omap_hsmmc.c @@ -260,7 +260,7 @@ static int omap_hsmmc_1_set_power(struct device *dev, int slot, int power_on, return ret; } -static int omap_hsmmc_23_set_power(struct device *dev, int slot, int power_on, +static int omap_hsmmc_235_set_power(struct device *dev, int slot, int power_on, int vdd) { struct omap_hsmmc_host *host = @@ -316,6 +316,12 @@ static int omap_hsmmc_23_set_power(struct device *dev, int slot, int power_on, return ret; } +static int omap_hsmmc_4_set_power(struct device *dev, int slot, int power_on, + int vdd) +{ + return 0; +} + static int omap_hsmmc_1_set_sleep(struct device *dev, int slot, int sleep, int vdd, int cardsleep) { @@ -326,7 +332,7 @@ static int omap_hsmmc_1_set_sleep(struct device *dev, int slot, int sleep, return regulator_set_mode(host->vcc, mode); } -static int omap_hsmmc_23_set_sleep(struct device *dev, int slot, int sleep, +static int omap_hsmmc_235_set_sleep(struct device *dev, int slot, int sleep, int vdd, int cardsleep) { struct omap_hsmmc_host *host = @@ -365,6 +371,12 @@ static int omap_hsmmc_23_set_sleep(struct device *dev, int slot, int sleep, return regulator_enable(host->vcc_aux); } +static int omap_hsmmc_4_set_sleep(struct device *dev, int slot, int sleep, + int vdd, int cardsleep) +{ + return 0; +} + static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) { struct regulator *reg; @@ -379,10 +391,14 @@ static int omap_hsmmc_reg_get(struct omap_hsmmc_host *host) break; case OMAP_MMC2_DEVID: case OMAP_MMC3_DEVID: + case OMAP_MMC5_DEVID: /* Off-chip level shifting, or none */ - mmc_slot(host).set_power = omap_hsmmc_23_set_power; - mmc_slot(host).set_sleep = omap_hsmmc_23_set_sleep; + mmc_slot(host).set_power = omap_hsmmc_235_set_power; + mmc_slot(host).set_sleep = omap_hsmmc_235_set_sleep; break; + case OMAP_MMC4_DEVID: + mmc_slot(host).set_power = omap_hsmmc_4_set_power; + mmc_slot(host).set_sleep = omap_hsmmc_4_set_sleep; default: pr_err("MMC%d configuration not supported!\n", host->id); return -EINVAL; diff --git a/drivers/mtd/nand/Kconfig b/drivers/mtd/nand/Kconfig index c89592239bc7..178e2006063d 100644 --- a/drivers/mtd/nand/Kconfig +++ b/drivers/mtd/nand/Kconfig @@ -106,23 +106,6 @@ config MTD_NAND_OMAP2 help Support for NAND flash on Texas Instruments OMAP2 and OMAP3 platforms. -config MTD_NAND_OMAP_PREFETCH - bool "GPMC prefetch support for NAND Flash device" - depends on MTD_NAND_OMAP2 - default y - help - The NAND device can be accessed for Read/Write using GPMC PREFETCH engine - to improve the performance. - -config MTD_NAND_OMAP_PREFETCH_DMA - depends on MTD_NAND_OMAP_PREFETCH - bool "DMA mode" - default n - help - The GPMC PREFETCH engine can be configured eigther in MPU interrupt mode - or in DMA interrupt mode. - Say y for DMA mode or MPU mode will be used - config MTD_NAND_IDS tristate diff --git a/drivers/mtd/nand/omap2.c b/drivers/mtd/nand/omap2.c index 15682ec8530e..4e33972ad17a 100644 --- a/drivers/mtd/nand/omap2.c +++ b/drivers/mtd/nand/omap2.c @@ -11,6 +11,7 @@ #include <linux/platform_device.h> #include <linux/dma-mapping.h> #include <linux/delay.h> +#include <linux/interrupt.h> #include <linux/jiffies.h> #include <linux/sched.h> #include <linux/mtd/mtd.h> @@ -24,6 +25,7 @@ #include <plat/nand.h> #define DRIVER_NAME "omap2-nand" +#define OMAP_NAND_TIMEOUT_MS 5000 #define NAND_Ecc_P1e (1 << 0) #define NAND_Ecc_P2e (1 << 1) @@ -96,26 +98,19 @@ static const char *part_probes[] = { "cmdlinepart", NULL }; #endif -#ifdef CONFIG_MTD_NAND_OMAP_PREFETCH -static int use_prefetch = 1; - -/* "modprobe ... use_prefetch=0" etc */ -module_param(use_prefetch, bool, 0); -MODULE_PARM_DESC(use_prefetch, "enable/disable use of PREFETCH"); - -#ifdef CONFIG_MTD_NAND_OMAP_PREFETCH_DMA -static int use_dma = 1; +/* oob info generated runtime depending on ecc algorithm and layout selected */ +static struct nand_ecclayout omap_oobinfo; +/* Define some generic bad / good block scan pattern which are used + * while scanning a device for factory marked good / bad blocks + */ +static uint8_t scan_ff_pattern[] = { 0xff }; +static struct nand_bbt_descr bb_descrip_flashbased = { + .options = NAND_BBT_SCANEMPTY | NAND_BBT_SCANALLPAGES, + .offs = 0, + .len = 1, + .pattern = scan_ff_pattern, +}; -/* "modprobe ... use_dma=0" etc */ -module_param(use_dma, bool, 0); -MODULE_PARM_DESC(use_dma, "enable/disable use of DMA"); -#else -static const int use_dma; -#endif -#else -const int use_prefetch; -static const int use_dma; -#endif struct omap_nand_info { struct nand_hw_control controller; @@ -129,6 +124,13 @@ struct omap_nand_info { unsigned long phys_base; struct completion comp; int dma_ch; + int gpmc_irq; + enum { + OMAP_NAND_IO_READ = 0, /* read */ + OMAP_NAND_IO_WRITE, /* write */ + } iomode; + u_char *buf; + int buf_len; }; /** @@ -256,7 +258,8 @@ static void omap_read_buf_pref(struct mtd_info *mtd, u_char *buf, int len) } /* configure and start prefetch transfer */ - ret = gpmc_prefetch_enable(info->gpmc_cs, 0x0, len, 0x0); + ret = gpmc_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x0, len, 0x0); if (ret) { /* PFPW engine is busy, use cpu copy method */ if (info->nand.options & NAND_BUSWIDTH_16) @@ -288,9 +291,10 @@ static void omap_write_buf_pref(struct mtd_info *mtd, { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); - uint32_t pref_count = 0, w_count = 0; + uint32_t w_count = 0; int i = 0, ret = 0; u16 *p; + unsigned long tim, limit; /* take care of subpage writes */ if (len % 2 != 0) { @@ -300,7 +304,8 @@ static void omap_write_buf_pref(struct mtd_info *mtd, } /* configure and start prefetch transfer */ - ret = gpmc_prefetch_enable(info->gpmc_cs, 0x0, len, 0x1); + ret = gpmc_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x0, len, 0x1); if (ret) { /* PFPW engine is busy, use cpu copy method */ if (info->nand.options & NAND_BUSWIDTH_16) @@ -316,15 +321,17 @@ static void omap_write_buf_pref(struct mtd_info *mtd, iowrite16(*p++, info->nand.IO_ADDR_W); } /* wait for data to flushed-out before reset the prefetch */ - do { - pref_count = gpmc_read_status(GPMC_PREFETCH_COUNT); - } while (pref_count); + tim = 0; + limit = (loops_per_jiffy * + msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + while (gpmc_read_status(GPMC_PREFETCH_COUNT) && (tim++ < limit)) + cpu_relax(); + /* disable and stop the PFPW engine */ gpmc_prefetch_reset(info->gpmc_cs); } } -#ifdef CONFIG_MTD_NAND_OMAP_PREFETCH_DMA /* * omap_nand_dma_cb: callback on the completion of dma transfer * @lch: logical channel @@ -348,14 +355,15 @@ static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, { struct omap_nand_info *info = container_of(mtd, struct omap_nand_info, mtd); - uint32_t prefetch_status = 0; enum dma_data_direction dir = is_write ? DMA_TO_DEVICE : DMA_FROM_DEVICE; dma_addr_t dma_addr; int ret; + unsigned long tim, limit; - /* The fifo depth is 64 bytes. We have a sync at each frame and frame - * length is 64 bytes. + /* The fifo depth is 64 bytes max. + * But configure the FIFO-threahold to 32 to get a sync at each frame + * and frame length is 32 bytes. */ int buf_len = len >> 6; @@ -396,9 +404,10 @@ static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, OMAP24XX_DMA_GPMC, OMAP_DMA_SRC_SYNC); } /* configure and start prefetch transfer */ - ret = gpmc_prefetch_enable(info->gpmc_cs, 0x1, len, is_write); + ret = gpmc_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX, 0x1, len, is_write); if (ret) - /* PFPW engine is busy, use cpu copy methode */ + /* PFPW engine is busy, use cpu copy method */ goto out_copy; init_completion(&info->comp); @@ -407,10 +416,11 @@ static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, /* setup and start DMA using dma_addr */ wait_for_completion(&info->comp); + tim = 0; + limit = (loops_per_jiffy * msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + while (gpmc_read_status(GPMC_PREFETCH_COUNT) && (tim++ < limit)) + cpu_relax(); - do { - prefetch_status = gpmc_read_status(GPMC_PREFETCH_COUNT); - } while (prefetch_status); /* disable and stop the PFPW engine */ gpmc_prefetch_reset(info->gpmc_cs); @@ -426,14 +436,6 @@ out_copy: : omap_write_buf8(mtd, (u_char *) addr, len); return 0; } -#else -static void omap_nand_dma_cb(int lch, u16 ch_status, void *data) {} -static inline int omap_nand_dma_transfer(struct mtd_info *mtd, void *addr, - unsigned int len, int is_write) -{ - return 0; -} -#endif /** * omap_read_buf_dma_pref - read data from NAND controller into buffer @@ -466,6 +468,157 @@ static void omap_write_buf_dma_pref(struct mtd_info *mtd, omap_nand_dma_transfer(mtd, (u_char *) buf, len, 0x1); } +/* + * omap_nand_irq - GMPC irq handler + * @this_irq: gpmc irq number + * @dev: omap_nand_info structure pointer is passed here + */ +static irqreturn_t omap_nand_irq(int this_irq, void *dev) +{ + struct omap_nand_info *info = (struct omap_nand_info *) dev; + u32 bytes; + u32 irq_stat; + + irq_stat = gpmc_read_status(GPMC_GET_IRQ_STATUS); + bytes = gpmc_read_status(GPMC_PREFETCH_FIFO_CNT); + bytes = bytes & 0xFFFC; /* io in multiple of 4 bytes */ + if (info->iomode == OMAP_NAND_IO_WRITE) { /* checks for write io */ + if (irq_stat & 0x2) + goto done; + + if (info->buf_len && (info->buf_len < bytes)) + bytes = info->buf_len; + else if (!info->buf_len) + bytes = 0; + iowrite32_rep(info->nand.IO_ADDR_W, + (u32 *)info->buf, bytes >> 2); + info->buf = info->buf + bytes; + info->buf_len -= bytes; + + } else { + ioread32_rep(info->nand.IO_ADDR_R, + (u32 *)info->buf, bytes >> 2); + info->buf = info->buf + bytes; + + if (irq_stat & 0x2) + goto done; + } + gpmc_cs_configure(info->gpmc_cs, GPMC_SET_IRQ_STATUS, irq_stat); + + return IRQ_HANDLED; + +done: + complete(&info->comp); + /* disable irq */ + gpmc_cs_configure(info->gpmc_cs, GPMC_ENABLE_IRQ, 0); + + /* clear status */ + gpmc_cs_configure(info->gpmc_cs, GPMC_SET_IRQ_STATUS, irq_stat); + + return IRQ_HANDLED; +} + +/* + * omap_read_buf_irq_pref - read data from NAND controller into buffer + * @mtd: MTD device structure + * @buf: buffer to store date + * @len: number of bytes to read + */ +static void omap_read_buf_irq_pref(struct mtd_info *mtd, u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + int ret = 0; + + if (len <= mtd->oobsize) { + omap_read_buf_pref(mtd, buf, len); + return; + } + + info->iomode = OMAP_NAND_IO_READ; + info->buf = buf; + init_completion(&info->comp); + + /* configure and start prefetch transfer */ + ret = gpmc_prefetch_enable(info->gpmc_cs, + PREFETCH_FIFOTHRESHOLD_MAX/2, 0x0, len, 0x0); + if (ret) + /* PFPW engine is busy, use cpu copy method */ + goto out_copy; + + info->buf_len = len; + /* enable irq */ + gpmc_cs_configure(info->gpmc_cs, GPMC_ENABLE_IRQ, + (GPMC_IRQ_FIFOEVENTENABLE | GPMC_IRQ_COUNT_EVENT)); + + /* waiting for read to complete */ + wait_for_completion(&info->comp); + + /* disable and stop the PFPW engine */ + gpmc_prefetch_reset(info->gpmc_cs); + return; + +out_copy: + if (info->nand.options & NAND_BUSWIDTH_16) + omap_read_buf16(mtd, buf, len); + else + omap_read_buf8(mtd, buf, len); +} + +/* + * omap_write_buf_irq_pref - write buffer to NAND controller + * @mtd: MTD device structure + * @buf: data buffer + * @len: number of bytes to write + */ +static void omap_write_buf_irq_pref(struct mtd_info *mtd, + const u_char *buf, int len) +{ + struct omap_nand_info *info = container_of(mtd, + struct omap_nand_info, mtd); + int ret = 0; + unsigned long tim, limit; + + if (len <= mtd->oobsize) { + omap_write_buf_pref(mtd, buf, len); + return; + } + + info->iomode = OMAP_NAND_IO_WRITE; + info->buf = (u_char *) buf; + init_completion(&info->comp); + + /* configure and start prefetch transfer : size=24 */ + ret = gpmc_prefetch_enable(info->gpmc_cs, + (PREFETCH_FIFOTHRESHOLD_MAX * 3) / 8, 0x0, len, 0x1); + if (ret) + /* PFPW engine is busy, use cpu copy method */ + goto out_copy; + + info->buf_len = len; + /* enable irq */ + gpmc_cs_configure(info->gpmc_cs, GPMC_ENABLE_IRQ, + (GPMC_IRQ_FIFOEVENTENABLE | GPMC_IRQ_COUNT_EVENT)); + + /* waiting for write to complete */ + wait_for_completion(&info->comp); + /* wait for data to flushed-out before reset the prefetch */ + tim = 0; + limit = (loops_per_jiffy * msecs_to_jiffies(OMAP_NAND_TIMEOUT_MS)); + while (gpmc_read_status(GPMC_PREFETCH_COUNT) && (tim++ < limit)) + cpu_relax(); + + /* disable and stop the PFPW engine */ + gpmc_prefetch_reset(info->gpmc_cs); + return; + +out_copy: + if (info->nand.options & NAND_BUSWIDTH_16) + omap_write_buf16(mtd, buf, len); + else + omap_write_buf8(mtd, buf, len); +} + /** * omap_verify_buf - Verify chip data against buffer * @mtd: MTD device structure @@ -487,8 +640,6 @@ static int omap_verify_buf(struct mtd_info *mtd, const u_char * buf, int len) return 0; } -#ifdef CONFIG_MTD_NAND_OMAP_HWECC - /** * gen_true_ecc - This function will generate true ECC value * @ecc_buf: buffer to store ecc code @@ -708,8 +859,6 @@ static void omap_enable_hwecc(struct mtd_info *mtd, int mode) gpmc_enable_hwecc(info->gpmc_cs, mode, dev_width, info->nand.ecc.size); } -#endif - /** * omap_wait - wait until the command is done * @mtd: MTD device structure @@ -779,6 +928,7 @@ static int __devinit omap_nand_probe(struct platform_device *pdev) struct omap_nand_info *info; struct omap_nand_platform_data *pdata; int err; + int i, offset; pdata = pdev->dev.platform_data; if (pdata == NULL) { @@ -804,7 +954,7 @@ static int __devinit omap_nand_probe(struct platform_device *pdev) info->mtd.name = dev_name(&pdev->dev); info->mtd.owner = THIS_MODULE; - info->nand.options |= pdata->devsize ? NAND_BUSWIDTH_16 : 0; + info->nand.options = pdata->devsize; info->nand.options |= NAND_SKIP_BBTSCAN; /* NAND write protect off */ @@ -842,28 +992,13 @@ static int __devinit omap_nand_probe(struct platform_device *pdev) info->nand.chip_delay = 50; } - if (use_prefetch) { - + switch (pdata->xfer_type) { + case NAND_OMAP_PREFETCH_POLLED: info->nand.read_buf = omap_read_buf_pref; info->nand.write_buf = omap_write_buf_pref; - if (use_dma) { - err = omap_request_dma(OMAP24XX_DMA_GPMC, "NAND", - omap_nand_dma_cb, &info->comp, &info->dma_ch); - if (err < 0) { - info->dma_ch = -1; - printk(KERN_WARNING "DMA request failed." - " Non-dma data transfer mode\n"); - } else { - omap_set_dma_dest_burst_mode(info->dma_ch, - OMAP_DMA_DATA_BURST_16); - omap_set_dma_src_burst_mode(info->dma_ch, - OMAP_DMA_DATA_BURST_16); - - info->nand.read_buf = omap_read_buf_dma_pref; - info->nand.write_buf = omap_write_buf_dma_pref; - } - } - } else { + break; + + case NAND_OMAP_POLLED: if (info->nand.options & NAND_BUSWIDTH_16) { info->nand.read_buf = omap_read_buf16; info->nand.write_buf = omap_write_buf16; @@ -871,20 +1006,61 @@ static int __devinit omap_nand_probe(struct platform_device *pdev) info->nand.read_buf = omap_read_buf8; info->nand.write_buf = omap_write_buf8; } + break; + + case NAND_OMAP_PREFETCH_DMA: + err = omap_request_dma(OMAP24XX_DMA_GPMC, "NAND", + omap_nand_dma_cb, &info->comp, &info->dma_ch); + if (err < 0) { + info->dma_ch = -1; + dev_err(&pdev->dev, "DMA request failed!\n"); + goto out_release_mem_region; + } else { + omap_set_dma_dest_burst_mode(info->dma_ch, + OMAP_DMA_DATA_BURST_16); + omap_set_dma_src_burst_mode(info->dma_ch, + OMAP_DMA_DATA_BURST_16); + + info->nand.read_buf = omap_read_buf_dma_pref; + info->nand.write_buf = omap_write_buf_dma_pref; + } + break; + + case NAND_OMAP_PREFETCH_IRQ: + err = request_irq(pdata->gpmc_irq, + omap_nand_irq, IRQF_SHARED, "gpmc-nand", info); + if (err) { + dev_err(&pdev->dev, "requesting irq(%d) error:%d", + pdata->gpmc_irq, err); + goto out_release_mem_region; + } else { + info->gpmc_irq = pdata->gpmc_irq; + info->nand.read_buf = omap_read_buf_irq_pref; + info->nand.write_buf = omap_write_buf_irq_pref; + } + break; + + default: + dev_err(&pdev->dev, + "xfer_type(%d) not supported!\n", pdata->xfer_type); + err = -EINVAL; + goto out_release_mem_region; } - info->nand.verify_buf = omap_verify_buf; -#ifdef CONFIG_MTD_NAND_OMAP_HWECC - info->nand.ecc.bytes = 3; - info->nand.ecc.size = 512; - info->nand.ecc.calculate = omap_calculate_ecc; - info->nand.ecc.hwctl = omap_enable_hwecc; - info->nand.ecc.correct = omap_correct_data; - info->nand.ecc.mode = NAND_ECC_HW; + info->nand.verify_buf = omap_verify_buf; -#else - info->nand.ecc.mode = NAND_ECC_SOFT; -#endif + /* selsect the ecc type */ + if (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_DEFAULT) + info->nand.ecc.mode = NAND_ECC_SOFT; + else if ((pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW) || + (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW_ROMCODE)) { + info->nand.ecc.bytes = 3; + info->nand.ecc.size = 512; + info->nand.ecc.calculate = omap_calculate_ecc; + info->nand.ecc.hwctl = omap_enable_hwecc; + info->nand.ecc.correct = omap_correct_data; + info->nand.ecc.mode = NAND_ECC_HW; + } /* DIP switches on some boards change between 8 and 16 bit * bus widths for flash. Try the other width if the first try fails. @@ -897,6 +1073,26 @@ static int __devinit omap_nand_probe(struct platform_device *pdev) } } + /* rom code layout */ + if (pdata->ecc_opt == OMAP_ECC_HAMMING_CODE_HW_ROMCODE) { + + if (info->nand.options & NAND_BUSWIDTH_16) + offset = 2; + else { + offset = 1; + info->nand.badblock_pattern = &bb_descrip_flashbased; + } + omap_oobinfo.eccbytes = 3 * (info->mtd.oobsize/16); + for (i = 0; i < omap_oobinfo.eccbytes; i++) + omap_oobinfo.eccpos[i] = i+offset; + + omap_oobinfo.oobfree->offset = offset + omap_oobinfo.eccbytes; + omap_oobinfo.oobfree->length = info->mtd.oobsize - + (offset + omap_oobinfo.eccbytes); + + info->nand.ecc.layout = &omap_oobinfo; + } + #ifdef CONFIG_MTD_PARTITIONS err = parse_mtd_partitions(&info->mtd, part_probes, &info->parts, 0); if (err > 0) @@ -926,9 +1122,12 @@ static int omap_nand_remove(struct platform_device *pdev) mtd); platform_set_drvdata(pdev, NULL); - if (use_dma) + if (info->dma_ch != -1) omap_free_dma(info->dma_ch); + if (info->gpmc_irq) + free_irq(info->gpmc_irq, info); + /* Release NAND device, its internal structures and partitions */ nand_release(&info->mtd); iounmap(info->nand.IO_ADDR_R); @@ -947,16 +1146,8 @@ static struct platform_driver omap_nand_driver = { static int __init omap_nand_init(void) { - printk(KERN_INFO "%s driver initializing\n", DRIVER_NAME); + pr_info("%s driver initializing\n", DRIVER_NAME); - /* This check is required if driver is being - * loaded run time as a module - */ - if ((1 == use_dma) && (0 == use_prefetch)) { - printk(KERN_INFO"Wrong parameters: 'use_dma' can not be 1 " - "without use_prefetch'. Prefetch will not be" - " used in either mode (mpu or dma)\n"); - } return platform_driver_register(&omap_nand_driver); } diff --git a/drivers/mtd/onenand/Kconfig b/drivers/mtd/onenand/Kconfig index 4dbd0f58eebf..4f426195f8db 100644 --- a/drivers/mtd/onenand/Kconfig +++ b/drivers/mtd/onenand/Kconfig @@ -32,7 +32,7 @@ config MTD_ONENAND_OMAP2 config MTD_ONENAND_SAMSUNG tristate "OneNAND on Samsung SOC controller support" - depends on ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_S5PV310 + depends on ARCH_S3C64XX || ARCH_S5PC100 || ARCH_S5PV210 || ARCH_EXYNOS4 help Support for a OneNAND flash device connected to an Samsung SOC. S3C64XX/S5PC100 use command mapping method. diff --git a/drivers/mtd/onenand/omap2.c b/drivers/mtd/onenand/omap2.c index ac31f461cc1c..ec26399e3cf2 100644 --- a/drivers/mtd/onenand/omap2.c +++ b/drivers/mtd/onenand/omap2.c @@ -63,7 +63,7 @@ struct omap2_onenand { struct completion dma_done; int dma_channel; int freq; - int (*setup)(void __iomem *base, int freq); + int (*setup)(void __iomem *base, int *freq_ptr); struct regulator *regulator; }; @@ -148,11 +148,9 @@ static int omap2_onenand_wait(struct mtd_info *mtd, int state) wait_err("controller error", state, ctrl, intr); return -EIO; } - if ((intr & intr_flags) != intr_flags) { - wait_err("timeout", state, ctrl, intr); - return -EIO; - } - return 0; + if ((intr & intr_flags) == intr_flags) + return 0; + /* Continue in wait for interrupt branch */ } if (state != FL_READING) { @@ -581,7 +579,7 @@ static int __adjust_timing(struct device *dev, void *data) /* DMA is not in use so this is all that is needed */ /* Revisit for OMAP3! */ - ret = c->setup(c->onenand.base, c->freq); + ret = c->setup(c->onenand.base, &c->freq); return ret; } @@ -673,7 +671,7 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) } if (pdata->onenand_setup != NULL) { - r = pdata->onenand_setup(c->onenand.base, c->freq); + r = pdata->onenand_setup(c->onenand.base, &c->freq); if (r < 0) { dev_err(&pdev->dev, "Onenand platform setup failed: " "%d\n", r); @@ -718,8 +716,8 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) } dev_info(&pdev->dev, "initializing on CS%d, phys base 0x%08lx, virtual " - "base %p\n", c->gpmc_cs, c->phys_base, - c->onenand.base); + "base %p, freq %d MHz\n", c->gpmc_cs, c->phys_base, + c->onenand.base, c->freq); c->pdev = pdev; c->mtd.name = dev_name(&pdev->dev); @@ -754,24 +752,6 @@ static int __devinit omap2_onenand_probe(struct platform_device *pdev) if ((r = onenand_scan(&c->mtd, 1)) < 0) goto err_release_regulator; - switch ((c->onenand.version_id >> 4) & 0xf) { - case 0: - c->freq = 40; - break; - case 1: - c->freq = 54; - break; - case 2: - c->freq = 66; - break; - case 3: - c->freq = 83; - break; - case 4: - c->freq = 104; - break; - } - #ifdef CONFIG_MTD_PARTITIONS r = parse_mtd_partitions(&c->mtd, part_probes, &c->parts, 0); if (r > 0) diff --git a/drivers/mtd/ubi/build.c b/drivers/mtd/ubi/build.c index 5ebe280225d6..ec0ad19c691a 100644 --- a/drivers/mtd/ubi/build.c +++ b/drivers/mtd/ubi/build.c @@ -711,7 +711,7 @@ static int io_init(struct ubi_device *ubi) } /* Similar for the data offset */ - ubi->leb_start = ubi->vid_hdr_offset + UBI_EC_HDR_SIZE; + ubi->leb_start = ubi->vid_hdr_offset + UBI_VID_HDR_SIZE; ubi->leb_start = ALIGN(ubi->leb_start, ubi->min_io_size); dbg_msg("vid_hdr_offset %d", ubi->vid_hdr_offset); @@ -923,6 +923,8 @@ int ubi_attach_mtd_dev(struct mtd_info *mtd, int ubi_num, int vid_hdr_offset) spin_lock_init(&ubi->volumes_lock); ubi_msg("attaching mtd%d to ubi%d", mtd->index, ubi_num); + dbg_msg("sizeof(struct ubi_scan_leb) %zu", sizeof(struct ubi_scan_leb)); + dbg_msg("sizeof(struct ubi_wl_entry) %zu", sizeof(struct ubi_wl_entry)); err = io_init(ubi); if (err) diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index 811775aa8ee8..889e25c49323 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -146,6 +146,28 @@ int ubi_io_read(const struct ubi_device *ubi, void *buf, int pnum, int offset, if (err) return err; + /* + * Deliberately corrupt the buffer to improve robustness. Indeed, if we + * do not do this, the following may happen: + * 1. The buffer contains data from previous operation, e.g., read from + * another PEB previously. The data looks like expected, e.g., if we + * just do not read anything and return - the caller would not + * notice this. E.g., if we are reading a VID header, the buffer may + * contain a valid VID header from another PEB. + * 2. The driver is buggy and returns us success or -EBADMSG or + * -EUCLEAN, but it does not actually put any data to the buffer. + * + * This may confuse UBI or upper layers - they may think the buffer + * contains valid data while in fact it is just old data. This is + * especially possible because UBI (and UBIFS) relies on CRC, and + * treats data as correct even in case of ECC errors if the CRC is + * correct. + * + * Try to prevent this situation by changing the first byte of the + * buffer. + */ + *((uint8_t *)buf) ^= 0xFF; + addr = (loff_t)pnum * ubi->peb_size + offset; retry: err = ubi->mtd->read(ubi->mtd, addr, len, &read, buf); @@ -166,7 +188,7 @@ retry: return UBI_IO_BITFLIPS; } - if (read != len && retries++ < UBI_IO_RETRIES) { + if (retries++ < UBI_IO_RETRIES) { dbg_io("error %d%s while reading %d bytes from PEB %d:%d," " read only %zd bytes, retry", err, errstr, len, pnum, offset, read); @@ -480,6 +502,13 @@ static int nor_erase_prepare(struct ubi_device *ubi, int pnum) size_t written; loff_t addr; uint32_t data = 0; + /* + * Note, we cannot generally define VID header buffers on stack, + * because of the way we deal with these buffers (see the header + * comment in this file). But we know this is a NOR-specific piece of + * code, so we can do this. But yes, this is error-prone and we should + * (pre-)allocate VID header buffer instead. + */ struct ubi_vid_hdr vid_hdr; /* @@ -507,11 +536,13 @@ static int nor_erase_prepare(struct ubi_device *ubi, int pnum) * PEB. */ err1 = ubi_io_read_vid_hdr(ubi, pnum, &vid_hdr, 0); - if (err1 == UBI_IO_BAD_HDR_EBADMSG || err1 == UBI_IO_BAD_HDR) { + if (err1 == UBI_IO_BAD_HDR_EBADMSG || err1 == UBI_IO_BAD_HDR || + err1 == UBI_IO_FF) { struct ubi_ec_hdr ec_hdr; err1 = ubi_io_read_ec_hdr(ubi, pnum, &ec_hdr, 0); - if (err1 == UBI_IO_BAD_HDR_EBADMSG || err1 == UBI_IO_BAD_HDR) + if (err1 == UBI_IO_BAD_HDR_EBADMSG || err1 == UBI_IO_BAD_HDR || + err1 == UBI_IO_FF) /* * Both VID and EC headers are corrupted, so we can * safely erase this PEB and not afraid that it will be @@ -1294,10 +1325,12 @@ int ubi_dbg_check_write(struct ubi_device *ubi, const void *buf, int pnum, int offset, int len) { int err, i; + size_t read; + loff_t addr = (loff_t)pnum * ubi->peb_size + offset; mutex_lock(&ubi->dbg_buf_mutex); - err = ubi_io_read(ubi, ubi->dbg_peb_buf, pnum, offset, len); - if (err) + err = ubi->mtd->read(ubi->mtd, addr, len, &read, ubi->dbg_peb_buf); + if (err && err != -EUCLEAN) goto out_unlock; for (i = 0; i < len; i++) { diff --git a/drivers/mtd/ubi/scan.c b/drivers/mtd/ubi/scan.c index 79ca304fc4db..b65cc088fde5 100644 --- a/drivers/mtd/ubi/scan.c +++ b/drivers/mtd/ubi/scan.c @@ -39,32 +39,46 @@ * eraseblocks are put to the @free list and the physical eraseblock to be * erased are put to the @erase list. * + * About corruptions + * ~~~~~~~~~~~~~~~~~ + * + * UBI protects EC and VID headers with CRC-32 checksums, so it can detect + * whether the headers are corrupted or not. Sometimes UBI also protects the + * data with CRC-32, e.g., when it executes the atomic LEB change operation, or + * when it moves the contents of a PEB for wear-leveling purposes. + * * UBI tries to distinguish between 2 types of corruptions. - * 1. Corruptions caused by power cuts. These are harmless and expected - * corruptions and UBI tries to handle them gracefully, without printing too - * many warnings and error messages. The idea is that we do not lose - * important data in these case - we may lose only the data which was being - * written to the media just before the power cut happened, and the upper - * layers (e.g., UBIFS) are supposed to handle these situations. UBI puts - * these PEBs to the head of the @erase list and they are scheduled for - * erasure. + * + * 1. Corruptions caused by power cuts. These are expected corruptions and UBI + * tries to handle them gracefully, without printing too many warnings and + * error messages. The idea is that we do not lose important data in these case + * - we may lose only the data which was being written to the media just before + * the power cut happened, and the upper layers (e.g., UBIFS) are supposed to + * handle such data losses (e.g., by using the FS journal). + * + * When UBI detects a corruption (CRC-32 mismatch) in a PEB, and it looks like + * the reason is a power cut, UBI puts this PEB to the @erase list, and all + * PEBs in the @erase list are scheduled for erasure later. * * 2. Unexpected corruptions which are not caused by power cuts. During - * scanning, such PEBs are put to the @corr list and UBI preserves them. - * Obviously, this lessens the amount of available PEBs, and if at some - * point UBI runs out of free PEBs, it switches to R/O mode. UBI also loudly - * informs about such PEBs every time the MTD device is attached. + * scanning, such PEBs are put to the @corr list and UBI preserves them. + * Obviously, this lessens the amount of available PEBs, and if at some point + * UBI runs out of free PEBs, it switches to R/O mode. UBI also loudly informs + * about such PEBs every time the MTD device is attached. * * However, it is difficult to reliably distinguish between these types of - * corruptions and UBI's strategy is as follows. UBI assumes (2.) if the VID - * header is corrupted and the data area does not contain all 0xFFs, and there - * were not bit-flips or integrity errors while reading the data area. Otherwise - * UBI assumes (1.). The assumptions are: - * o if the data area contains only 0xFFs, there is no data, and it is safe - * to just erase this PEB. - * o if the data area has bit-flips and data integrity errors (ECC errors on + * corruptions and UBI's strategy is as follows. UBI assumes corruption type 2 + * if the VID header is corrupted and the data area does not contain all 0xFFs, + * and there were no bit-flips or integrity errors while reading the data area. + * Otherwise UBI assumes corruption type 1. So the decision criteria are as + * follows. + * o If the data area contains only 0xFFs, there is no data, and it is safe + * to just erase this PEB - this is corruption type 1. + * o If the data area has bit-flips or data integrity errors (ECC errors on * NAND), it is probably a PEB which was being erased when power cut - * happened. + * happened, so this is corruption type 1. However, this is just a guess, + * which might be wrong. + * o Otherwise this it corruption type 2. */ #include <linux/err.h> @@ -115,7 +129,7 @@ static int add_to_list(struct ubi_scan_info *si, int pnum, int ec, int to_head, } else BUG(); - seb = kmalloc(sizeof(struct ubi_scan_leb), GFP_KERNEL); + seb = kmem_cache_alloc(si->scan_leb_slab, GFP_KERNEL); if (!seb) return -ENOMEM; @@ -144,7 +158,7 @@ static int add_corrupted(struct ubi_scan_info *si, int pnum, int ec) dbg_bld("add to corrupted: PEB %d, EC %d", pnum, ec); - seb = kmalloc(sizeof(struct ubi_scan_leb), GFP_KERNEL); + seb = kmem_cache_alloc(si->scan_leb_slab, GFP_KERNEL); if (!seb) return -ENOMEM; @@ -553,7 +567,7 @@ int ubi_scan_add_used(struct ubi_device *ubi, struct ubi_scan_info *si, if (err) return err; - seb = kmalloc(sizeof(struct ubi_scan_leb), GFP_KERNEL); + seb = kmem_cache_alloc(si->scan_leb_slab, GFP_KERNEL); if (!seb) return -ENOMEM; @@ -1152,9 +1166,15 @@ struct ubi_scan_info *ubi_scan(struct ubi_device *ubi) si->volumes = RB_ROOT; err = -ENOMEM; + si->scan_leb_slab = kmem_cache_create("ubi_scan_leb_slab", + sizeof(struct ubi_scan_leb), + 0, 0, NULL); + if (!si->scan_leb_slab) + goto out_si; + ech = kzalloc(ubi->ec_hdr_alsize, GFP_KERNEL); if (!ech) - goto out_si; + goto out_slab; vidh = ubi_zalloc_vid_hdr(ubi, GFP_KERNEL); if (!vidh) @@ -1215,6 +1235,8 @@ out_vidh: ubi_free_vid_hdr(ubi, vidh); out_ech: kfree(ech); +out_slab: + kmem_cache_destroy(si->scan_leb_slab); out_si: ubi_scan_destroy_si(si); return ERR_PTR(err); @@ -1223,11 +1245,12 @@ out_si: /** * destroy_sv - free the scanning volume information * @sv: scanning volume information + * @si: scanning information * * This function destroys the volume RB-tree (@sv->root) and the scanning * volume information. */ -static void destroy_sv(struct ubi_scan_volume *sv) +static void destroy_sv(struct ubi_scan_info *si, struct ubi_scan_volume *sv) { struct ubi_scan_leb *seb; struct rb_node *this = sv->root.rb_node; @@ -1247,7 +1270,7 @@ static void destroy_sv(struct ubi_scan_volume *sv) this->rb_right = NULL; } - kfree(seb); + kmem_cache_free(si->scan_leb_slab, seb); } } kfree(sv); @@ -1265,19 +1288,19 @@ void ubi_scan_destroy_si(struct ubi_scan_info *si) list_for_each_entry_safe(seb, seb_tmp, &si->alien, u.list) { list_del(&seb->u.list); - kfree(seb); + kmem_cache_free(si->scan_leb_slab, seb); } list_for_each_entry_safe(seb, seb_tmp, &si->erase, u.list) { list_del(&seb->u.list); - kfree(seb); + kmem_cache_free(si->scan_leb_slab, seb); } list_for_each_entry_safe(seb, seb_tmp, &si->corr, u.list) { list_del(&seb->u.list); - kfree(seb); + kmem_cache_free(si->scan_leb_slab, seb); } list_for_each_entry_safe(seb, seb_tmp, &si->free, u.list) { list_del(&seb->u.list); - kfree(seb); + kmem_cache_free(si->scan_leb_slab, seb); } /* Destroy the volume RB-tree */ @@ -1298,10 +1321,11 @@ void ubi_scan_destroy_si(struct ubi_scan_info *si) rb->rb_right = NULL; } - destroy_sv(sv); + destroy_sv(si, sv); } } + kmem_cache_destroy(si->scan_leb_slab); kfree(si); } diff --git a/drivers/mtd/ubi/scan.h b/drivers/mtd/ubi/scan.h index a3264f0bef2b..d48aef15ab5d 100644 --- a/drivers/mtd/ubi/scan.h +++ b/drivers/mtd/ubi/scan.h @@ -109,6 +109,7 @@ struct ubi_scan_volume { * @mean_ec: mean erase counter value * @ec_sum: a temporary variable used when calculating @mean_ec * @ec_count: a temporary variable used when calculating @mean_ec + * @scan_leb_slab: slab cache for &struct ubi_scan_leb objects * * This data structure contains the result of scanning and may be used by other * UBI sub-systems to build final UBI data structures, further error-recovery @@ -134,6 +135,7 @@ struct ubi_scan_info { int mean_ec; uint64_t ec_sum; int ec_count; + struct kmem_cache *scan_leb_slab; }; struct ubi_device; diff --git a/drivers/net/bnx2x/bnx2x_init.h b/drivers/net/bnx2x/bnx2x_init.h index 5a268e9a0895..fa6dbe3f2058 100644 --- a/drivers/net/bnx2x/bnx2x_init.h +++ b/drivers/net/bnx2x/bnx2x_init.h @@ -241,7 +241,7 @@ static const struct { /* Block IGU, MISC, PXP and PXP2 parity errors as long as we don't * want to handle "system kill" flow at the moment. */ - BLOCK_PRTY_INFO(PXP, 0x3ffffff, 0x3ffffff, 0x3ffffff, 0x3ffffff), + BLOCK_PRTY_INFO(PXP, 0x7ffffff, 0x3ffffff, 0x3ffffff, 0x7ffffff), BLOCK_PRTY_INFO_0(PXP2, 0xffffffff, 0xffffffff, 0xffffffff, 0xffffffff), BLOCK_PRTY_INFO_1(PXP2, 0x7ff, 0x7f, 0x7f, 0x7ff), BLOCK_PRTY_INFO(HC, 0x7, 0x7, 0x7, 0), diff --git a/drivers/net/bnx2x/bnx2x_main.c b/drivers/net/bnx2x/bnx2x_main.c index 061733e5e5ca..5b0d9a7c2860 100644 --- a/drivers/net/bnx2x/bnx2x_main.c +++ b/drivers/net/bnx2x/bnx2x_main.c @@ -145,13 +145,6 @@ static struct { { "Broadcom NetXtreme II BCM57712E XGb" } }; -#ifndef PCI_DEVICE_ID_NX2_57712 -#define PCI_DEVICE_ID_NX2_57712 0x1662 -#endif -#ifndef PCI_DEVICE_ID_NX2_57712E -#define PCI_DEVICE_ID_NX2_57712E 0x1663 -#endif - static DEFINE_PCI_DEVICE_TABLE(bnx2x_pci_tbl) = { { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57710), BCM57710 }, { PCI_VDEVICE(BROADCOM, PCI_DEVICE_ID_NX2_57711), BCM57711 }, diff --git a/drivers/net/can/softing/softing_main.c b/drivers/net/can/softing/softing_main.c index 5157e15e96eb..aeea9f9ff6e8 100644 --- a/drivers/net/can/softing/softing_main.c +++ b/drivers/net/can/softing/softing_main.c @@ -633,6 +633,7 @@ static const struct net_device_ops softing_netdev_ops = { }; static const struct can_bittiming_const softing_btr_const = { + .name = "softing", .tseg1_min = 1, .tseg1_max = 16, .tseg2_min = 1, diff --git a/drivers/net/dm9000.c b/drivers/net/dm9000.c index c30935587207..317708113601 100644 --- a/drivers/net/dm9000.c +++ b/drivers/net/dm9000.c @@ -802,10 +802,7 @@ dm9000_init_dm9000(struct net_device *dev) /* Checksum mode */ dm9000_set_rx_csum_unlocked(dev, db->rx_csum); - /* GPIO0 on pre-activate PHY */ - iow(db, DM9000_GPR, 0); /* REG_1F bit0 activate phyxcer */ iow(db, DM9000_GPCR, GPCR_GEP_CNTL); /* Let GPIO0 output */ - iow(db, DM9000_GPR, 0); /* Enable PHY */ ncr = (db->flags & DM9000_PLATF_EXT_PHY) ? NCR_EXT_PHY : 0; @@ -852,8 +849,8 @@ static void dm9000_timeout(struct net_device *dev) unsigned long flags; /* Save previous register address */ - reg_save = readb(db->io_addr); spin_lock_irqsave(&db->lock, flags); + reg_save = readb(db->io_addr); netif_stop_queue(dev); dm9000_reset(db); @@ -1194,6 +1191,10 @@ dm9000_open(struct net_device *dev) if (request_irq(dev->irq, dm9000_interrupt, irqflags, dev->name, dev)) return -EAGAIN; + /* GPIO0 on pre-activate PHY, Reg 1F is not set by reset */ + iow(db, DM9000_GPR, 0); /* REG_1F bit0 activate phyxcer */ + mdelay(1); /* delay needs by DM9000B */ + /* Initialize DM9000 board */ dm9000_reset(db); dm9000_init_dm9000(dev); diff --git a/drivers/net/dnet.c b/drivers/net/dnet.c index 9d8a20b72fa9..8318ea06cb6d 100644 --- a/drivers/net/dnet.c +++ b/drivers/net/dnet.c @@ -337,8 +337,6 @@ static int dnet_mii_init(struct dnet *bp) for (i = 0; i < PHY_MAX_ADDR; i++) bp->mii_bus->irq[i] = PHY_POLL; - platform_set_drvdata(bp->dev, bp->mii_bus); - if (mdiobus_register(bp->mii_bus)) { err = -ENXIO; goto err_out_free_mdio_irq; @@ -863,6 +861,7 @@ static int __devinit dnet_probe(struct platform_device *pdev) bp = netdev_priv(dev); bp->dev = dev; + platform_set_drvdata(pdev, dev); SET_NETDEV_DEV(dev, &pdev->dev); spin_lock_init(&bp->lock); diff --git a/drivers/net/fec.c b/drivers/net/fec.c index 634c0daeecec..885d8baff7d5 100644 --- a/drivers/net/fec.c +++ b/drivers/net/fec.c @@ -74,7 +74,8 @@ static struct platform_device_id fec_devtype[] = { }, { .name = "imx28-fec", .driver_data = FEC_QUIRK_ENET_MAC | FEC_QUIRK_SWAP_FRAME, - } + }, + { } }; static unsigned char macaddr[ETH_ALEN]; diff --git a/drivers/net/macb.c b/drivers/net/macb.c index f69e73e2191e..79ccb54ab00c 100644 --- a/drivers/net/macb.c +++ b/drivers/net/macb.c @@ -260,7 +260,7 @@ static int macb_mii_init(struct macb *bp) for (i = 0; i < PHY_MAX_ADDR; i++) bp->mii_bus->irq[i] = PHY_POLL; - platform_set_drvdata(bp->dev, bp->mii_bus); + dev_set_drvdata(&bp->dev->dev, bp->mii_bus); if (mdiobus_register(bp->mii_bus)) goto err_out_free_mdio_irq; diff --git a/drivers/net/pcmcia/fmvj18x_cs.c b/drivers/net/pcmcia/fmvj18x_cs.c index 9226cda4d054..530ab5a10bd3 100644 --- a/drivers/net/pcmcia/fmvj18x_cs.c +++ b/drivers/net/pcmcia/fmvj18x_cs.c @@ -691,6 +691,7 @@ static struct pcmcia_device_id fmvj18x_ids[] = { PCMCIA_PFC_DEVICE_MANF_CARD(0, 0x0105, 0x0e0a), PCMCIA_PFC_DEVICE_MANF_CARD(0, 0x0032, 0x0e01), PCMCIA_PFC_DEVICE_MANF_CARD(0, 0x0032, 0x0a05), + PCMCIA_PFC_DEVICE_MANF_CARD(0, 0x0032, 0x0b05), PCMCIA_PFC_DEVICE_MANF_CARD(0, 0x0032, 0x1101), PCMCIA_DEVICE_NULL, }; diff --git a/drivers/net/r8169.c b/drivers/net/r8169.c index 469ab0b7ce31..ef2133b16f8c 100644 --- a/drivers/net/r8169.c +++ b/drivers/net/r8169.c @@ -617,8 +617,9 @@ static void ocp_write(struct rtl8169_private *tp, u8 mask, u16 reg, u32 data) } } -static void rtl8168_oob_notify(void __iomem *ioaddr, u8 cmd) +static void rtl8168_oob_notify(struct rtl8169_private *tp, u8 cmd) { + void __iomem *ioaddr = tp->mmio_addr; int i; RTL_W8(ERIDR, cmd); @@ -630,7 +631,7 @@ static void rtl8168_oob_notify(void __iomem *ioaddr, u8 cmd) break; } - ocp_write(ioaddr, 0x1, 0x30, 0x00000001); + ocp_write(tp, 0x1, 0x30, 0x00000001); } #define OOB_CMD_RESET 0x00 @@ -2868,8 +2869,11 @@ static void r8168_pll_power_down(struct rtl8169_private *tp) { void __iomem *ioaddr = tp->mmio_addr; - if (tp->mac_version == RTL_GIGA_MAC_VER_27) + if (((tp->mac_version == RTL_GIGA_MAC_VER_27) || + (tp->mac_version == RTL_GIGA_MAC_VER_28)) && + (ocp_read(tp, 0x0f, 0x0010) & 0x00008000)) { return; + } if (((tp->mac_version == RTL_GIGA_MAC_VER_23) || (tp->mac_version == RTL_GIGA_MAC_VER_24)) && @@ -2891,6 +2895,8 @@ static void r8168_pll_power_down(struct rtl8169_private *tp) switch (tp->mac_version) { case RTL_GIGA_MAC_VER_25: case RTL_GIGA_MAC_VER_26: + case RTL_GIGA_MAC_VER_27: + case RTL_GIGA_MAC_VER_28: RTL_W8(PMCH, RTL_R8(PMCH) & ~0x80); break; } @@ -2900,12 +2906,17 @@ static void r8168_pll_power_up(struct rtl8169_private *tp) { void __iomem *ioaddr = tp->mmio_addr; - if (tp->mac_version == RTL_GIGA_MAC_VER_27) + if (((tp->mac_version == RTL_GIGA_MAC_VER_27) || + (tp->mac_version == RTL_GIGA_MAC_VER_28)) && + (ocp_read(tp, 0x0f, 0x0010) & 0x00008000)) { return; + } switch (tp->mac_version) { case RTL_GIGA_MAC_VER_25: case RTL_GIGA_MAC_VER_26: + case RTL_GIGA_MAC_VER_27: + case RTL_GIGA_MAC_VER_28: RTL_W8(PMCH, RTL_R8(PMCH) | 0x80); break; } @@ -3042,7 +3053,7 @@ rtl8169_init_one(struct pci_dev *pdev, const struct pci_device_id *ent) goto err_out_mwi_2; } - tp->cp_cmd = PCIMulRW | RxChkSum; + tp->cp_cmd = RxChkSum; if ((sizeof(dma_addr_t) > 4) && !pci_set_dma_mask(pdev, DMA_BIT_MASK(64)) && use_dac) { @@ -3318,7 +3329,8 @@ static void rtl8169_hw_reset(struct rtl8169_private *tp) /* Disable interrupts */ rtl8169_irq_mask_and_ack(ioaddr); - if (tp->mac_version == RTL_GIGA_MAC_VER_28) { + if (tp->mac_version == RTL_GIGA_MAC_VER_27 || + tp->mac_version == RTL_GIGA_MAC_VER_28) { while (RTL_R8(TxPoll) & NPQ) udelay(20); @@ -3847,8 +3859,7 @@ static void rtl_hw_start_8168(struct net_device *dev) Cxpl_dbg_sel | \ ASF | \ PktCntrDisable | \ - PCIDAC | \ - PCIMulRW) + Mac_dbgo_sel) static void rtl_hw_start_8102e_1(void __iomem *ioaddr, struct pci_dev *pdev) { @@ -3878,8 +3889,6 @@ static void rtl_hw_start_8102e_1(void __iomem *ioaddr, struct pci_dev *pdev) if ((cfg1 & LEDS0) && (cfg1 & LEDS1)) RTL_W8(Config1, cfg1 & ~LEDS0); - RTL_W16(CPlusCmd, RTL_R16(CPlusCmd) & ~R810X_CPCMD_QUIRK_MASK); - rtl_ephy_init(ioaddr, e_info_8102e_1, ARRAY_SIZE(e_info_8102e_1)); } @@ -3891,8 +3900,6 @@ static void rtl_hw_start_8102e_2(void __iomem *ioaddr, struct pci_dev *pdev) RTL_W8(Config1, MEMMAP | IOMAP | VPD | PMEnable); RTL_W8(Config3, RTL_R8(Config3) & ~Beacon_en); - - RTL_W16(CPlusCmd, RTL_R16(CPlusCmd) & ~R810X_CPCMD_QUIRK_MASK); } static void rtl_hw_start_8102e_3(void __iomem *ioaddr, struct pci_dev *pdev) @@ -3918,6 +3925,8 @@ static void rtl_hw_start_8101(struct net_device *dev) } } + RTL_W8(Cfg9346, Cfg9346_Unlock); + switch (tp->mac_version) { case RTL_GIGA_MAC_VER_07: rtl_hw_start_8102e_1(ioaddr, pdev); @@ -3932,14 +3941,13 @@ static void rtl_hw_start_8101(struct net_device *dev) break; } - RTL_W8(Cfg9346, Cfg9346_Unlock); + RTL_W8(Cfg9346, Cfg9346_Lock); RTL_W8(MaxTxPacketSize, TxPacketMax); rtl_set_rx_max_size(ioaddr, rx_buf_sz); - tp->cp_cmd |= rtl_rw_cpluscmd(ioaddr) | PCIMulRW; - + tp->cp_cmd &= ~R810X_CPCMD_QUIRK_MASK; RTL_W16(CPlusCmd, tp->cp_cmd); RTL_W16(IntrMitigate, 0x0000); @@ -3949,14 +3957,10 @@ static void rtl_hw_start_8101(struct net_device *dev) RTL_W8(ChipCmd, CmdTxEnb | CmdRxEnb); rtl_set_rx_tx_config_registers(tp); - RTL_W8(Cfg9346, Cfg9346_Lock); - RTL_R8(IntrMask); rtl_set_rx_mode(dev); - RTL_W8(ChipCmd, CmdTxEnb | CmdRxEnb); - RTL_W16(MultiIntr, RTL_R16(MultiIntr) & 0xf000); RTL_W16(IntrMask, tp->intr_event); diff --git a/drivers/net/sfc/ethtool.c b/drivers/net/sfc/ethtool.c index 272cfe724e1b..0836b3711b47 100644 --- a/drivers/net/sfc/ethtool.c +++ b/drivers/net/sfc/ethtool.c @@ -569,9 +569,14 @@ static void efx_ethtool_self_test(struct net_device *net_dev, struct ethtool_test *test, u64 *data) { struct efx_nic *efx = netdev_priv(net_dev); - struct efx_self_tests efx_tests; + struct efx_self_tests *efx_tests; int already_up; - int rc; + int rc = -ENOMEM; + + efx_tests = kzalloc(sizeof(*efx_tests), GFP_KERNEL); + if (!efx_tests) + goto fail; + ASSERT_RTNL(); if (efx->state != STATE_RUNNING) { @@ -589,13 +594,11 @@ static void efx_ethtool_self_test(struct net_device *net_dev, if (rc) { netif_err(efx, drv, efx->net_dev, "failed opening device.\n"); - goto fail2; + goto fail1; } } - memset(&efx_tests, 0, sizeof(efx_tests)); - - rc = efx_selftest(efx, &efx_tests, test->flags); + rc = efx_selftest(efx, efx_tests, test->flags); if (!already_up) dev_close(efx->net_dev); @@ -604,10 +607,11 @@ static void efx_ethtool_self_test(struct net_device *net_dev, rc == 0 ? "passed" : "failed", (test->flags & ETH_TEST_FL_OFFLINE) ? "off" : "on"); - fail2: - fail1: +fail1: /* Fill ethtool results structures */ - efx_ethtool_fill_self_tests(efx, &efx_tests, NULL, data); + efx_ethtool_fill_self_tests(efx, efx_tests, NULL, data); + kfree(efx_tests); +fail: if (rc) test->flags |= ETH_TEST_FL_FAILED; } diff --git a/drivers/net/skge.c b/drivers/net/skge.c index 42daf98ba736..35b28f42d208 100644 --- a/drivers/net/skge.c +++ b/drivers/net/skge.c @@ -3856,9 +3856,6 @@ static struct net_device *skge_devinit(struct skge_hw *hw, int port, memcpy_fromio(dev->dev_addr, hw->regs + B2_MAC_1 + port*8, ETH_ALEN); memcpy(dev->perm_addr, dev->dev_addr, dev->addr_len); - /* device is off until link detection */ - netif_carrier_off(dev); - return dev; } diff --git a/drivers/net/smc91x.h b/drivers/net/smc91x.h index ee747919a766..68d48ab6eacf 100644 --- a/drivers/net/smc91x.h +++ b/drivers/net/smc91x.h @@ -206,68 +206,6 @@ SMC_outw(u16 val, void __iomem *ioaddr, int reg) #define RPC_LSA_DEFAULT RPC_LED_TX_RX #define RPC_LSB_DEFAULT RPC_LED_100_10 -#elif defined(CONFIG_MACH_LPD79520) || \ - defined(CONFIG_MACH_LPD7A400) || \ - defined(CONFIG_MACH_LPD7A404) - -/* The LPD7X_IOBARRIER is necessary to overcome a mismatch between the - * way that the CPU handles chip selects and the way that the SMC chip - * expects the chip select to operate. Refer to - * Documentation/arm/Sharp-LH/IOBarrier for details. The read from - * IOBARRIER is a byte, in order that we read the least-common - * denominator. It would be wasteful to read 32 bits from an 8-bit - * accessible region. - * - * There is no explicit protection against interrupts intervening - * between the writew and the IOBARRIER. In SMC ISR there is a - * preamble that performs an IOBARRIER in the extremely unlikely event - * that the driver interrupts itself between a writew to the chip an - * the IOBARRIER that follows *and* the cache is large enough that the - * first off-chip access while handing the interrupt is to the SMC - * chip. Other devices in the same address space as the SMC chip must - * be aware of the potential for trouble and perform a similar - * IOBARRIER on entry to their ISR. - */ - -#include <mach/constants.h> /* IOBARRIER_VIRT */ - -#define SMC_CAN_USE_8BIT 0 -#define SMC_CAN_USE_16BIT 1 -#define SMC_CAN_USE_32BIT 0 -#define SMC_NOWAIT 0 -#define LPD7X_IOBARRIER readb (IOBARRIER_VIRT) - -#define SMC_inw(a,r)\ - ({ unsigned short v = readw ((void*) ((a) + (r))); LPD7X_IOBARRIER; v; }) -#define SMC_outw(v,a,r) ({ writew ((v), (a) + (r)); LPD7X_IOBARRIER; }) - -#define SMC_insw LPD7_SMC_insw -static inline void LPD7_SMC_insw (unsigned char* a, int r, - unsigned char* p, int l) -{ - unsigned short* ps = (unsigned short*) p; - while (l-- > 0) { - *ps++ = readw (a + r); - LPD7X_IOBARRIER; - } -} - -#define SMC_outsw LPD7_SMC_outsw -static inline void LPD7_SMC_outsw (unsigned char* a, int r, - unsigned char* p, int l) -{ - unsigned short* ps = (unsigned short*) p; - while (l-- > 0) { - writew (*ps++, a + r); - LPD7X_IOBARRIER; - } -} - -#define SMC_INTERRUPT_PREAMBLE LPD7X_IOBARRIER - -#define RPC_LSA_DEFAULT RPC_LED_TX_RX -#define RPC_LSB_DEFAULT RPC_LED_100_10 - #elif defined(CONFIG_ARCH_VERSATILE) #define SMC_CAN_USE_8BIT 1 diff --git a/drivers/net/usb/dm9601.c b/drivers/net/usb/dm9601.c index 02b622e3b9fb..5002f5be47be 100644 --- a/drivers/net/usb/dm9601.c +++ b/drivers/net/usb/dm9601.c @@ -651,6 +651,10 @@ static const struct usb_device_id products[] = { .driver_info = (unsigned long)&dm9601_info, }, { + USB_DEVICE(0x0fe6, 0x9700), /* DM9601 USB to Fast Ethernet Adapter */ + .driver_info = (unsigned long)&dm9601_info, + }, + { USB_DEVICE(0x0a46, 0x9000), /* DM9000E */ .driver_info = (unsigned long)&dm9601_info, }, diff --git a/drivers/net/wireless/ath/ath9k/hif_usb.c b/drivers/net/wireless/ath/ath9k/hif_usb.c index 5ab3084eb9cb..07b1633b7f3f 100644 --- a/drivers/net/wireless/ath/ath9k/hif_usb.c +++ b/drivers/net/wireless/ath/ath9k/hif_usb.c @@ -219,8 +219,9 @@ static int __hif_usb_tx(struct hif_device_usb *hif_dev) struct tx_buf *tx_buf = NULL; struct sk_buff *nskb = NULL; int ret = 0, i; - u16 *hdr, tx_skb_cnt = 0; + u16 tx_skb_cnt = 0; u8 *buf; + __le16 *hdr; if (hif_dev->tx.tx_skb_cnt == 0) return 0; @@ -245,9 +246,9 @@ static int __hif_usb_tx(struct hif_device_usb *hif_dev) buf = tx_buf->buf; buf += tx_buf->offset; - hdr = (u16 *)buf; - *hdr++ = nskb->len; - *hdr++ = ATH_USB_TX_STREAM_MODE_TAG; + hdr = (__le16 *)buf; + *hdr++ = cpu_to_le16(nskb->len); + *hdr++ = cpu_to_le16(ATH_USB_TX_STREAM_MODE_TAG); buf += 4; memcpy(buf, nskb->data, nskb->len); tx_buf->len = nskb->len + 4; diff --git a/drivers/net/wireless/ath/ath9k/mac.c b/drivers/net/wireless/ath/ath9k/mac.c index c75d40fb86f1..5efc869d65ff 100644 --- a/drivers/net/wireless/ath/ath9k/mac.c +++ b/drivers/net/wireless/ath/ath9k/mac.c @@ -891,7 +891,7 @@ void ath9k_hw_set_interrupts(struct ath_hw *ah, enum ath9k_int ints) struct ath_common *common = ath9k_hw_common(ah); if (!(ints & ATH9K_INT_GLOBAL)) - ath9k_hw_enable_interrupts(ah); + ath9k_hw_disable_interrupts(ah); ath_dbg(common, ATH_DBG_INTERRUPT, "0x%x => 0x%x\n", omask, ints); @@ -969,7 +969,8 @@ void ath9k_hw_set_interrupts(struct ath_hw *ah, enum ath9k_int ints) REG_CLR_BIT(ah, AR_IMR_S5, AR_IMR_S5_TIM_TIMER); } - ath9k_hw_enable_interrupts(ah); + if (ints & ATH9K_INT_GLOBAL) + ath9k_hw_enable_interrupts(ah); return; } diff --git a/drivers/net/wireless/ath/carl9170/usb.c b/drivers/net/wireless/ath/carl9170/usb.c index 537732e5964f..f82c400be288 100644 --- a/drivers/net/wireless/ath/carl9170/usb.c +++ b/drivers/net/wireless/ath/carl9170/usb.c @@ -118,6 +118,8 @@ static struct usb_device_id carl9170_usb_ids[] = { { USB_DEVICE(0x057c, 0x8402) }, /* Qwest/Actiontec 802AIN Wireless N USB Network Adapter */ { USB_DEVICE(0x1668, 0x1200) }, + /* Airlive X.USB a/b/g/n */ + { USB_DEVICE(0x1b75, 0x9170) }, /* terminate */ {} diff --git a/drivers/net/wireless/iwlwifi/iwl-5000.c b/drivers/net/wireless/iwlwifi/iwl-5000.c index 79ab0a6b1386..537fb8c84e3a 100644 --- a/drivers/net/wireless/iwlwifi/iwl-5000.c +++ b/drivers/net/wireless/iwlwifi/iwl-5000.c @@ -51,7 +51,7 @@ #include "iwl-agn-debugfs.h" /* Highest firmware API version supported */ -#define IWL5000_UCODE_API_MAX 2 +#define IWL5000_UCODE_API_MAX 5 #define IWL5150_UCODE_API_MAX 2 /* Lowest firmware API version supported */ diff --git a/drivers/net/wireless/p54/p54pci.c b/drivers/net/wireless/p54/p54pci.c index 1eacba4daa5b..0494d7b102d4 100644 --- a/drivers/net/wireless/p54/p54pci.c +++ b/drivers/net/wireless/p54/p54pci.c @@ -199,6 +199,7 @@ static void p54p_check_rx_ring(struct ieee80211_hw *dev, u32 *index, while (i != idx) { u16 len; struct sk_buff *skb; + dma_addr_t dma_addr; desc = &ring[i]; len = le16_to_cpu(desc->len); skb = rx_buf[i]; @@ -216,17 +217,20 @@ static void p54p_check_rx_ring(struct ieee80211_hw *dev, u32 *index, len = priv->common.rx_mtu; } + dma_addr = le32_to_cpu(desc->host_addr); + pci_dma_sync_single_for_cpu(priv->pdev, dma_addr, + priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE); skb_put(skb, len); if (p54_rx(dev, skb)) { - pci_unmap_single(priv->pdev, - le32_to_cpu(desc->host_addr), - priv->common.rx_mtu + 32, - PCI_DMA_FROMDEVICE); + pci_unmap_single(priv->pdev, dma_addr, + priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE); rx_buf[i] = NULL; - desc->host_addr = 0; + desc->host_addr = cpu_to_le32(0); } else { skb_trim(skb, 0); + pci_dma_sync_single_for_device(priv->pdev, dma_addr, + priv->common.rx_mtu + 32, PCI_DMA_FROMDEVICE); desc->len = cpu_to_le16(priv->common.rx_mtu + 32); } diff --git a/drivers/net/wireless/p54/p54usb.c b/drivers/net/wireless/p54/p54usb.c index 21713a7638c4..9b344a921e74 100644 --- a/drivers/net/wireless/p54/p54usb.c +++ b/drivers/net/wireless/p54/p54usb.c @@ -98,6 +98,7 @@ static struct usb_device_id p54u_table[] __devinitdata = { {USB_DEVICE(0x1413, 0x5400)}, /* Telsey 802.11g USB2.0 Adapter */ {USB_DEVICE(0x1435, 0x0427)}, /* Inventel UR054G */ {USB_DEVICE(0x1668, 0x1050)}, /* Actiontec 802UIG-1 */ + {USB_DEVICE(0x1740, 0x1000)}, /* Senao NUB-350 */ {USB_DEVICE(0x2001, 0x3704)}, /* DLink DWL-G122 rev A2 */ {USB_DEVICE(0x2001, 0x3705)}, /* D-Link DWL-G120 rev C1 */ {USB_DEVICE(0x413c, 0x5513)}, /* Dell WLA3310 USB Wireless Adapter */ diff --git a/drivers/net/wireless/rndis_wlan.c b/drivers/net/wireless/rndis_wlan.c index 848cc2cce247..518542b4bf9e 100644 --- a/drivers/net/wireless/rndis_wlan.c +++ b/drivers/net/wireless/rndis_wlan.c @@ -2597,6 +2597,9 @@ static int rndis_set_power_mgmt(struct wiphy *wiphy, struct net_device *dev, __le32 mode; int ret; + if (priv->device_type != RNDIS_BCM4320B) + return -ENOTSUPP; + netdev_dbg(usbdev->net, "%s(): %s, %d\n", __func__, enabled ? "enabled" : "disabled", timeout); diff --git a/drivers/nfc/Kconfig b/drivers/nfc/Kconfig index ffedfd492754..ea1580085347 100644 --- a/drivers/nfc/Kconfig +++ b/drivers/nfc/Kconfig @@ -3,7 +3,7 @@ # menuconfig NFC_DEVICES - bool "NFC devices" + bool "Near Field Communication (NFC) devices" default n ---help--- You'll have to say Y if your computer contains an NFC device that diff --git a/drivers/nfc/pn544.c b/drivers/nfc/pn544.c index bae647264dd6..724f65d8f9e4 100644 --- a/drivers/nfc/pn544.c +++ b/drivers/nfc/pn544.c @@ -60,7 +60,7 @@ enum pn544_irq { struct pn544_info { struct miscdevice miscdev; struct i2c_client *i2c_dev; - struct regulator_bulk_data regs[2]; + struct regulator_bulk_data regs[3]; enum pn544_state state; wait_queue_head_t read_wait; @@ -74,6 +74,7 @@ struct pn544_info { static const char reg_vdd_io[] = "Vdd_IO"; static const char reg_vbat[] = "VBat"; +static const char reg_vsim[] = "VSim"; /* sysfs interface */ static ssize_t pn544_test(struct device *dev, @@ -740,6 +741,7 @@ static int __devinit pn544_probe(struct i2c_client *client, info->regs[0].supply = reg_vdd_io; info->regs[1].supply = reg_vbat; + info->regs[2].supply = reg_vsim; r = regulator_bulk_get(&client->dev, ARRAY_SIZE(info->regs), info->regs); if (r < 0) diff --git a/drivers/of/pdt.c b/drivers/of/pdt.c index 28295d0a50f6..4d87b5dc9284 100644 --- a/drivers/of/pdt.c +++ b/drivers/of/pdt.c @@ -36,19 +36,55 @@ unsigned int of_pdt_unique_id __initdata; (p)->unique_id = of_pdt_unique_id++; \ } while (0) -static inline const char *of_pdt_node_name(struct device_node *dp) +static char * __init of_pdt_build_full_name(struct device_node *dp) { - return dp->path_component_name; + int len, ourlen, plen; + char *n; + + dp->path_component_name = build_path_component(dp); + + plen = strlen(dp->parent->full_name); + ourlen = strlen(dp->path_component_name); + len = ourlen + plen + 2; + + n = prom_early_alloc(len); + strcpy(n, dp->parent->full_name); + if (!of_node_is_root(dp->parent)) { + strcpy(n + plen, "/"); + plen++; + } + strcpy(n + plen, dp->path_component_name); + + return n; } -#else +#else /* CONFIG_SPARC */ static inline void of_pdt_incr_unique_id(void *p) { } static inline void irq_trans_init(struct device_node *dp) { } -static inline const char *of_pdt_node_name(struct device_node *dp) +static char * __init of_pdt_build_full_name(struct device_node *dp) { - return dp->name; + static int failsafe_id = 0; /* for generating unique names on failure */ + char *buf; + int len; + + if (of_pdt_prom_ops->pkg2path(dp->phandle, NULL, 0, &len)) + goto failsafe; + + buf = prom_early_alloc(len + 1); + if (of_pdt_prom_ops->pkg2path(dp->phandle, buf, len, &len)) + goto failsafe; + return buf; + + failsafe: + buf = prom_early_alloc(strlen(dp->parent->full_name) + + strlen(dp->name) + 16); + sprintf(buf, "%s/%s@unknown%i", + of_node_is_root(dp->parent) ? "" : dp->parent->full_name, + dp->name, failsafe_id++); + pr_err("%s: pkg2path failed; assigning %s\n", __func__, buf); + return buf; } #endif /* !CONFIG_SPARC */ @@ -132,47 +168,6 @@ static char * __init of_pdt_get_one_property(phandle node, const char *name) return buf; } -static char * __init of_pdt_try_pkg2path(phandle node) -{ - char *res, *buf = NULL; - int len; - - if (!of_pdt_prom_ops->pkg2path) - return NULL; - - if (of_pdt_prom_ops->pkg2path(node, buf, 0, &len)) - return NULL; - buf = prom_early_alloc(len + 1); - if (of_pdt_prom_ops->pkg2path(node, buf, len, &len)) { - pr_err("%s: package-to-path failed\n", __func__); - return NULL; - } - - res = strrchr(buf, '/'); - if (!res) { - pr_err("%s: couldn't find / in %s\n", __func__, buf); - return NULL; - } - return res+1; -} - -/* - * When fetching the node's name, first try using package-to-path; if - * that fails (either because the arch hasn't supplied a PROM callback, - * or some other random failure), fall back to just looking at the node's - * 'name' property. - */ -static char * __init of_pdt_build_name(phandle node) -{ - char *buf; - - buf = of_pdt_try_pkg2path(node); - if (!buf) - buf = of_pdt_get_one_property(node, "name"); - - return buf; -} - static struct device_node * __init of_pdt_create_node(phandle node, struct device_node *parent) { @@ -187,7 +182,7 @@ static struct device_node * __init of_pdt_create_node(phandle node, kref_init(&dp->kref); - dp->name = of_pdt_build_name(node); + dp->name = of_pdt_get_one_property(node, "name"); dp->type = of_pdt_get_one_property(node, "device_type"); dp->phandle = node; @@ -198,26 +193,6 @@ static struct device_node * __init of_pdt_create_node(phandle node, return dp; } -static char * __init of_pdt_build_full_name(struct device_node *dp) -{ - int len, ourlen, plen; - char *n; - - plen = strlen(dp->parent->full_name); - ourlen = strlen(of_pdt_node_name(dp)); - len = ourlen + plen + 2; - - n = prom_early_alloc(len); - strcpy(n, dp->parent->full_name); - if (!of_node_is_root(dp->parent)) { - strcpy(n + plen, "/"); - plen++; - } - strcpy(n + plen, of_pdt_node_name(dp)); - - return n; -} - static struct device_node * __init of_pdt_build_tree(struct device_node *parent, phandle node, struct device_node ***nextp) @@ -240,9 +215,6 @@ static struct device_node * __init of_pdt_build_tree(struct device_node *parent, *(*nextp) = dp; *nextp = &dp->allnext; -#if defined(CONFIG_SPARC) - dp->path_component_name = build_path_component(dp); -#endif dp->full_name = of_pdt_build_full_name(dp); dp->child = of_pdt_build_tree(dp, diff --git a/drivers/parisc/dino.c b/drivers/parisc/dino.c index 9383063d2b16..bcd5d54b7d4d 100644 --- a/drivers/parisc/dino.c +++ b/drivers/parisc/dino.c @@ -296,25 +296,25 @@ static struct pci_port_ops dino_port_ops = { .outl = dino_out32 }; -static void dino_mask_irq(unsigned int irq) +static void dino_mask_irq(struct irq_data *d) { - struct dino_device *dino_dev = get_irq_chip_data(irq); - int local_irq = gsc_find_local_irq(irq, dino_dev->global_irq, DINO_LOCAL_IRQS); + struct dino_device *dino_dev = irq_data_get_irq_chip_data(d); + int local_irq = gsc_find_local_irq(d->irq, dino_dev->global_irq, DINO_LOCAL_IRQS); - DBG(KERN_WARNING "%s(0x%p, %d)\n", __func__, dino_dev, irq); + DBG(KERN_WARNING "%s(0x%p, %d)\n", __func__, dino_dev, d->irq); /* Clear the matching bit in the IMR register */ dino_dev->imr &= ~(DINO_MASK_IRQ(local_irq)); __raw_writel(dino_dev->imr, dino_dev->hba.base_addr+DINO_IMR); } -static void dino_unmask_irq(unsigned int irq) +static void dino_unmask_irq(struct irq_data *d) { - struct dino_device *dino_dev = get_irq_chip_data(irq); - int local_irq = gsc_find_local_irq(irq, dino_dev->global_irq, DINO_LOCAL_IRQS); + struct dino_device *dino_dev = irq_data_get_irq_chip_data(d); + int local_irq = gsc_find_local_irq(d->irq, dino_dev->global_irq, DINO_LOCAL_IRQS); u32 tmp; - DBG(KERN_WARNING "%s(0x%p, %d)\n", __func__, dino_dev, irq); + DBG(KERN_WARNING "%s(0x%p, %d)\n", __func__, dino_dev, d->irq); /* ** clear pending IRQ bits @@ -346,9 +346,9 @@ static void dino_unmask_irq(unsigned int irq) } static struct irq_chip dino_interrupt_type = { - .name = "GSC-PCI", - .unmask = dino_unmask_irq, - .mask = dino_mask_irq, + .name = "GSC-PCI", + .irq_unmask = dino_unmask_irq, + .irq_mask = dino_mask_irq, }; diff --git a/drivers/parisc/eisa.c b/drivers/parisc/eisa.c index e860038b0b84..deeec32a5803 100644 --- a/drivers/parisc/eisa.c +++ b/drivers/parisc/eisa.c @@ -144,8 +144,9 @@ static unsigned int eisa_irq_level __read_mostly; /* default to edge triggered * /* called by free irq */ -static void eisa_mask_irq(unsigned int irq) +static void eisa_mask_irq(struct irq_data *d) { + unsigned int irq = d->irq; unsigned long flags; EISA_DBG("disable irq %d\n", irq); @@ -164,8 +165,9 @@ static void eisa_mask_irq(unsigned int irq) } /* called by request irq */ -static void eisa_unmask_irq(unsigned int irq) +static void eisa_unmask_irq(struct irq_data *d) { + unsigned int irq = d->irq; unsigned long flags; EISA_DBG("enable irq %d\n", irq); @@ -183,9 +185,9 @@ static void eisa_unmask_irq(unsigned int irq) } static struct irq_chip eisa_interrupt_type = { - .name = "EISA", - .unmask = eisa_unmask_irq, - .mask = eisa_mask_irq, + .name = "EISA", + .irq_unmask = eisa_unmask_irq, + .irq_mask = eisa_mask_irq, }; static irqreturn_t eisa_irq(int wax_irq, void *intr_dev) diff --git a/drivers/parisc/gsc.c b/drivers/parisc/gsc.c index 772b1939ac21..ef31080cf591 100644 --- a/drivers/parisc/gsc.c +++ b/drivers/parisc/gsc.c @@ -105,13 +105,13 @@ int gsc_find_local_irq(unsigned int irq, int *global_irqs, int limit) return NO_IRQ; } -static void gsc_asic_mask_irq(unsigned int irq) +static void gsc_asic_mask_irq(struct irq_data *d) { - struct gsc_asic *irq_dev = get_irq_chip_data(irq); - int local_irq = gsc_find_local_irq(irq, irq_dev->global_irq, 32); + struct gsc_asic *irq_dev = irq_data_get_irq_chip_data(d); + int local_irq = gsc_find_local_irq(d->irq, irq_dev->global_irq, 32); u32 imr; - DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, irq, + DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, d->irq, irq_dev->name, imr); /* Disable the IRQ line by clearing the bit in the IMR */ @@ -120,13 +120,13 @@ static void gsc_asic_mask_irq(unsigned int irq) gsc_writel(imr, irq_dev->hpa + OFFSET_IMR); } -static void gsc_asic_unmask_irq(unsigned int irq) +static void gsc_asic_unmask_irq(struct irq_data *d) { - struct gsc_asic *irq_dev = get_irq_chip_data(irq); - int local_irq = gsc_find_local_irq(irq, irq_dev->global_irq, 32); + struct gsc_asic *irq_dev = irq_data_get_irq_chip_data(d); + int local_irq = gsc_find_local_irq(d->irq, irq_dev->global_irq, 32); u32 imr; - DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, irq, + DEBPRINTK(KERN_DEBUG "%s(%d) %s: IMR 0x%x\n", __func__, d->irq, irq_dev->name, imr); /* Enable the IRQ line by setting the bit in the IMR */ @@ -140,9 +140,9 @@ static void gsc_asic_unmask_irq(unsigned int irq) } static struct irq_chip gsc_asic_interrupt_type = { - .name = "GSC-ASIC", - .unmask = gsc_asic_unmask_irq, - .mask = gsc_asic_mask_irq, + .name = "GSC-ASIC", + .irq_unmask = gsc_asic_unmask_irq, + .irq_mask = gsc_asic_mask_irq, }; int gsc_assign_irq(struct irq_chip *type, void *data) diff --git a/drivers/parisc/iosapic.c b/drivers/parisc/iosapic.c index 0327894bf235..95930d016235 100644 --- a/drivers/parisc/iosapic.c +++ b/drivers/parisc/iosapic.c @@ -615,10 +615,10 @@ iosapic_set_irt_data( struct vector_info *vi, u32 *dp0, u32 *dp1) } -static void iosapic_mask_irq(unsigned int irq) +static void iosapic_mask_irq(struct irq_data *d) { unsigned long flags; - struct vector_info *vi = get_irq_chip_data(irq); + struct vector_info *vi = irq_data_get_irq_chip_data(d); u32 d0, d1; spin_lock_irqsave(&iosapic_lock, flags); @@ -628,9 +628,9 @@ static void iosapic_mask_irq(unsigned int irq) spin_unlock_irqrestore(&iosapic_lock, flags); } -static void iosapic_unmask_irq(unsigned int irq) +static void iosapic_unmask_irq(struct irq_data *d) { - struct vector_info *vi = get_irq_chip_data(irq); + struct vector_info *vi = irq_data_get_irq_chip_data(d); u32 d0, d1; /* data is initialized by fixup_irq */ @@ -666,34 +666,34 @@ printk("\n"); * enables their IRQ. It can lead to "interesting" race conditions * in the driver initialization sequence. */ - DBG(KERN_DEBUG "enable_irq(%d): eoi(%p, 0x%x)\n", irq, + DBG(KERN_DEBUG "enable_irq(%d): eoi(%p, 0x%x)\n", d->irq, vi->eoi_addr, vi->eoi_data); iosapic_eoi(vi->eoi_addr, vi->eoi_data); } -static void iosapic_eoi_irq(unsigned int irq) +static void iosapic_eoi_irq(struct irq_data *d) { - struct vector_info *vi = get_irq_chip_data(irq); + struct vector_info *vi = irq_data_get_irq_chip_data(d); iosapic_eoi(vi->eoi_addr, vi->eoi_data); - cpu_eoi_irq(irq); + cpu_eoi_irq(d); } #ifdef CONFIG_SMP -static int iosapic_set_affinity_irq(unsigned int irq, - const struct cpumask *dest) +static int iosapic_set_affinity_irq(struct irq_data *d, + const struct cpumask *dest, bool force) { - struct vector_info *vi = get_irq_chip_data(irq); + struct vector_info *vi = irq_data_get_irq_chip_data(d); u32 d0, d1, dummy_d0; unsigned long flags; int dest_cpu; - dest_cpu = cpu_check_affinity(irq, dest); + dest_cpu = cpu_check_affinity(d, dest); if (dest_cpu < 0) return -1; - cpumask_copy(irq_desc[irq].affinity, cpumask_of(dest_cpu)); - vi->txn_addr = txn_affinity_addr(irq, dest_cpu); + cpumask_copy(d->affinity, cpumask_of(dest_cpu)); + vi->txn_addr = txn_affinity_addr(d->irq, dest_cpu); spin_lock_irqsave(&iosapic_lock, flags); /* d1 contains the destination CPU, so only want to set that @@ -708,13 +708,13 @@ static int iosapic_set_affinity_irq(unsigned int irq, #endif static struct irq_chip iosapic_interrupt_type = { - .name = "IO-SAPIC-level", - .unmask = iosapic_unmask_irq, - .mask = iosapic_mask_irq, - .ack = cpu_ack_irq, - .eoi = iosapic_eoi_irq, + .name = "IO-SAPIC-level", + .irq_unmask = iosapic_unmask_irq, + .irq_mask = iosapic_mask_irq, + .irq_ack = cpu_ack_irq, + .irq_eoi = iosapic_eoi_irq, #ifdef CONFIG_SMP - .set_affinity = iosapic_set_affinity_irq, + .irq_set_affinity = iosapic_set_affinity_irq, #endif }; diff --git a/drivers/parisc/superio.c b/drivers/parisc/superio.c index 28241532c0fd..a4d8ff66a639 100644 --- a/drivers/parisc/superio.c +++ b/drivers/parisc/superio.c @@ -286,8 +286,9 @@ superio_init(struct pci_dev *pcidev) } DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_NS, PCI_DEVICE_ID_NS_87560_LIO, superio_init); -static void superio_mask_irq(unsigned int irq) +static void superio_mask_irq(struct irq_data *d) { + unsigned int irq = d->irq; u8 r8; if ((irq < 1) || (irq == 2) || (irq > 7)) { @@ -303,8 +304,9 @@ static void superio_mask_irq(unsigned int irq) outb (r8,IC_PIC1+1); } -static void superio_unmask_irq(unsigned int irq) +static void superio_unmask_irq(struct irq_data *d) { + unsigned int irq = d->irq; u8 r8; if ((irq < 1) || (irq == 2) || (irq > 7)) { @@ -320,9 +322,9 @@ static void superio_unmask_irq(unsigned int irq) } static struct irq_chip superio_interrupt_type = { - .name = SUPERIO, - .unmask = superio_unmask_irq, - .mask = superio_mask_irq, + .name = SUPERIO, + .irq_unmask = superio_unmask_irq, + .irq_mask = superio_mask_irq, }; #ifdef DEBUG_SUPERIO_INIT diff --git a/drivers/pci/Makefile b/drivers/pci/Makefile index 98e6fdf34d30..77cf813ba264 100644 --- a/drivers/pci/Makefile +++ b/drivers/pci/Makefile @@ -42,6 +42,7 @@ obj-$(CONFIG_PCI_IOV) += iov.o obj-$(CONFIG_X86) += setup-bus.o obj-$(CONFIG_ALPHA) += setup-bus.o setup-irq.o obj-$(CONFIG_ARM) += setup-bus.o setup-irq.o +obj-$(CONFIG_UNICORE32) += setup-bus.o setup-irq.o obj-$(CONFIG_PARISC) += setup-bus.o obj-$(CONFIG_SUPERH) += setup-bus.o setup-irq.o obj-$(CONFIG_PPC) += setup-bus.o diff --git a/drivers/pci/pci-acpi.c b/drivers/pci/pci-acpi.c index 6fe0772e0e7d..7c3b18e78cee 100644 --- a/drivers/pci/pci-acpi.c +++ b/drivers/pci/pci-acpi.c @@ -293,19 +293,11 @@ static int acpi_dev_run_wake(struct device *phys_dev, bool enable) } if (enable) { - if (!dev->wakeup.run_wake_count++) { - acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0); - acpi_enable_gpe(dev->wakeup.gpe_device, - dev->wakeup.gpe_number); - } - } else if (dev->wakeup.run_wake_count > 0) { - if (!--dev->wakeup.run_wake_count) { - acpi_disable_gpe(dev->wakeup.gpe_device, - dev->wakeup.gpe_number); - acpi_disable_wakeup_device_power(dev); - } + acpi_enable_wakeup_device_power(dev, ACPI_STATE_S0); + acpi_enable_gpe(dev->wakeup.gpe_device, dev->wakeup.gpe_number); } else { - error = -EALREADY; + acpi_disable_gpe(dev->wakeup.gpe_device, dev->wakeup.gpe_number); + acpi_disable_wakeup_device_power(dev); } return error; diff --git a/drivers/pci/pci-sysfs.c b/drivers/pci/pci-sysfs.c index ea25e5bfcf23..c85438a367d5 100644 --- a/drivers/pci/pci-sysfs.c +++ b/drivers/pci/pci-sysfs.c @@ -1088,7 +1088,7 @@ static int pci_create_capabilities_sysfs(struct pci_dev *dev) attr->write = write_vpd_attr; retval = sysfs_create_bin_file(&dev->dev.kobj, attr); if (retval) { - kfree(dev->vpd->attr); + kfree(attr); return retval; } dev->vpd->attr = attr; diff --git a/drivers/pci/probe.c b/drivers/pci/probe.c index c84900da3c59..44cbbbaa499d 100644 --- a/drivers/pci/probe.c +++ b/drivers/pci/probe.c @@ -764,6 +764,8 @@ int __devinit pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, if (pci_find_bus(pci_domain_nr(bus), max+1)) goto out; child = pci_add_new_bus(bus, dev, ++max); + if (!child) + goto out; buses = (buses & 0xff000000) | ((unsigned int)(child->primary) << 0) | ((unsigned int)(child->secondary) << 8) @@ -777,7 +779,7 @@ int __devinit pci_scan_bridge(struct pci_bus *bus, struct pci_dev *dev, int max, buses &= ~0xff000000; buses |= CARDBUS_LATENCY_TIMER << 24; } - + /* * We need to blast all three values with a single write. */ diff --git a/drivers/pci/quirks.c b/drivers/pci/quirks.c index 53a786fd0d40..ff01bfb3cc29 100644 --- a/drivers/pci/quirks.c +++ b/drivers/pci/quirks.c @@ -2618,58 +2618,6 @@ DECLARE_PCI_FIXUP_FINAL(PCI_VENDOR_ID_ATI, 0x4375, #endif /* CONFIG_PCI_MSI */ -#ifdef CONFIG_PCI_IOV - -/* - * For Intel 82576 SR-IOV NIC, if BIOS doesn't allocate resources for the - * SR-IOV BARs, zero the Flash BAR and program the SR-IOV BARs to use the - * old Flash Memory Space. - */ -static void __devinit quirk_i82576_sriov(struct pci_dev *dev) -{ - int pos, flags; - u32 bar, start, size; - - if (PAGE_SIZE > 0x10000) - return; - - flags = pci_resource_flags(dev, 0); - if ((flags & PCI_BASE_ADDRESS_SPACE) != - PCI_BASE_ADDRESS_SPACE_MEMORY || - (flags & PCI_BASE_ADDRESS_MEM_TYPE_MASK) != - PCI_BASE_ADDRESS_MEM_TYPE_32) - return; - - pos = pci_find_ext_capability(dev, PCI_EXT_CAP_ID_SRIOV); - if (!pos) - return; - - pci_read_config_dword(dev, pos + PCI_SRIOV_BAR, &bar); - if (bar & PCI_BASE_ADDRESS_MEM_MASK) - return; - - start = pci_resource_start(dev, 1); - size = pci_resource_len(dev, 1); - if (!start || size != 0x400000 || start & (size - 1)) - return; - - pci_resource_flags(dev, 1) = 0; - pci_write_config_dword(dev, PCI_BASE_ADDRESS_1, 0); - pci_write_config_dword(dev, pos + PCI_SRIOV_BAR, start); - pci_write_config_dword(dev, pos + PCI_SRIOV_BAR + 12, start + size / 2); - - dev_info(&dev->dev, "use Flash Memory Space for SR-IOV BARs\n"); -} -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10c9, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10e6, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10e7, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x10e8, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x150a, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x150d, quirk_i82576_sriov); -DECLARE_PCI_FIXUP_HEADER(PCI_VENDOR_ID_INTEL, 0x1518, quirk_i82576_sriov); - -#endif /* CONFIG_PCI_IOV */ - /* Allow manual resource allocation for PCI hotplug bridges * via pci=hpmemsize=nnM and pci=hpiosize=nnM parameters. For * some PCI-PCI hotplug bridges, like PLX 6254 (former HINT HB6), diff --git a/drivers/pcmcia/pcmcia_resource.c b/drivers/pcmcia/pcmcia_resource.c index 0bdda5b3ed55..42fbf1a75576 100644 --- a/drivers/pcmcia/pcmcia_resource.c +++ b/drivers/pcmcia/pcmcia_resource.c @@ -518,6 +518,8 @@ int pcmcia_enable_device(struct pcmcia_device *p_dev) flags |= CONF_ENABLE_IOCARD; if (flags & CONF_ENABLE_IOCARD) s->socket.flags |= SS_IOCARD; + if (flags & CONF_ENABLE_ZVCARD) + s->socket.flags |= SS_ZVCARD | SS_IOCARD; if (flags & CONF_ENABLE_SPKR) { s->socket.flags |= SS_SPKR_ENA; status = CCSR_AUDIO_ENA; diff --git a/drivers/pcmcia/pxa2xx_base.c b/drivers/pcmcia/pxa2xx_base.c index 3755e7c8c715..2c540542b5af 100644 --- a/drivers/pcmcia/pxa2xx_base.c +++ b/drivers/pcmcia/pxa2xx_base.c @@ -215,7 +215,7 @@ pxa2xx_pcmcia_frequency_change(struct soc_pcmcia_socket *skt, } #endif -static void pxa2xx_configure_sockets(struct device *dev) +void pxa2xx_configure_sockets(struct device *dev) { struct pcmcia_low_level *ops = dev->platform_data; /* diff --git a/drivers/pcmcia/pxa2xx_base.h b/drivers/pcmcia/pxa2xx_base.h index bb62ea87b8f9..b609b45469ed 100644 --- a/drivers/pcmcia/pxa2xx_base.h +++ b/drivers/pcmcia/pxa2xx_base.h @@ -1,3 +1,4 @@ int pxa2xx_drv_pcmcia_add_one(struct soc_pcmcia_socket *skt); void pxa2xx_drv_pcmcia_ops(struct pcmcia_low_level *ops); +void pxa2xx_configure_sockets(struct device *dev); diff --git a/drivers/pcmcia/pxa2xx_lubbock.c b/drivers/pcmcia/pxa2xx_lubbock.c index b9f8c8fb42bd..25afe637c657 100644 --- a/drivers/pcmcia/pxa2xx_lubbock.c +++ b/drivers/pcmcia/pxa2xx_lubbock.c @@ -226,6 +226,7 @@ int pcmcia_lubbock_init(struct sa1111_dev *sadev) lubbock_set_misc_wr((1 << 15) | (1 << 14), 0); pxa2xx_drv_pcmcia_ops(&lubbock_pcmcia_ops); + pxa2xx_configure_sockets(&sadev->dev); ret = sa1111_pcmcia_add(sadev, &lubbock_pcmcia_ops, pxa2xx_drv_pcmcia_add_one); } diff --git a/drivers/platform/x86/Kconfig b/drivers/platform/x86/Kconfig index d163bc2e2b9e..222dfb737b11 100644 --- a/drivers/platform/x86/Kconfig +++ b/drivers/platform/x86/Kconfig @@ -138,6 +138,24 @@ config TC1100_WMI This is a driver for the WMI extensions (wireless and bluetooth power control) of the HP Compaq TC1100 tablet. +config HP_ACCEL + tristate "HP laptop accelerometer" + depends on INPUT && ACPI + select SENSORS_LIS3LV02D + select NEW_LEDS + select LEDS_CLASS + help + This driver provides support for the "Mobile Data Protection System 3D" + or "3D DriveGuard" feature of HP laptops. On such systems the driver + should load automatically (via ACPI alias). + + Support for a led indicating disk protection will be provided as + hp::hddprotect. For more information on the feature, refer to + Documentation/hwmon/lis3lv02d. + + To compile this driver as a module, choose M here: the module will + be called hp_accel. + config HP_WMI tristate "HP WMI extras" depends on ACPI_WMI @@ -227,7 +245,7 @@ config SONYPI_COMPAT config IDEAPAD_LAPTOP tristate "Lenovo IdeaPad Laptop Extras" depends on ACPI - depends on RFKILL + depends on RFKILL && INPUT select INPUT_SPARSEKMAP help This is a driver for the rfkill switches on Lenovo IdeaPad netbooks. diff --git a/drivers/platform/x86/Makefile b/drivers/platform/x86/Makefile index 4ec4ff8f9182..299aefb3e74c 100644 --- a/drivers/platform/x86/Makefile +++ b/drivers/platform/x86/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_DELL_LAPTOP) += dell-laptop.o obj-$(CONFIG_DELL_WMI) += dell-wmi.o obj-$(CONFIG_ACER_WMI) += acer-wmi.o obj-$(CONFIG_ACERHDF) += acerhdf.o +obj-$(CONFIG_HP_ACCEL) += hp_accel.o obj-$(CONFIG_HP_WMI) += hp-wmi.o obj-$(CONFIG_TC1100_WMI) += tc1100-wmi.o obj-$(CONFIG_SONY_LAPTOP) += sony-laptop.o diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c index c5c4b8c32eb8..38b34a73866a 100644 --- a/drivers/platform/x86/acer-wmi.c +++ b/drivers/platform/x86/acer-wmi.c @@ -84,7 +84,7 @@ MODULE_LICENSE("GPL"); */ #define AMW0_GUID1 "67C3371D-95A3-4C37-BB61-DD47B491DAAB" #define AMW0_GUID2 "431F16ED-0C2B-444C-B267-27DEB140CF9C" -#define WMID_GUID1 "6AF4F258-B401-42fd-BE91-3D4AC2D7C0D3" +#define WMID_GUID1 "6AF4F258-B401-42FD-BE91-3D4AC2D7C0D3" #define WMID_GUID2 "95764E09-FB56-4e83-B31A-37761F60994A" #define WMID_GUID3 "61EF69EA-865C-4BC3-A502-A0DEBA0CB531" @@ -1280,7 +1280,7 @@ static ssize_t set_bool_threeg(struct device *dev, return -EINVAL; return count; } -static DEVICE_ATTR(threeg, S_IWUGO | S_IRUGO | S_IWUSR, show_bool_threeg, +static DEVICE_ATTR(threeg, S_IRUGO | S_IWUSR, show_bool_threeg, set_bool_threeg); static ssize_t show_interface(struct device *dev, struct device_attribute *attr, diff --git a/drivers/platform/x86/asus_acpi.c b/drivers/platform/x86/asus_acpi.c index 4633fd8532cc..fe495939c307 100644 --- a/drivers/platform/x86/asus_acpi.c +++ b/drivers/platform/x86/asus_acpi.c @@ -1081,14 +1081,8 @@ static int asus_hotk_add_fs(struct acpi_device *device) struct proc_dir_entry *proc; mode_t mode; - /* - * If parameter uid or gid is not changed, keep the default setting for - * our proc entries (-rw-rw-rw-) else, it means we care about security, - * and then set to -rw-rw---- - */ - if ((asus_uid == 0) && (asus_gid == 0)) { - mode = S_IFREG | S_IRUGO | S_IWUGO; + mode = S_IFREG | S_IRUGO | S_IWUSR | S_IWGRP; } else { mode = S_IFREG | S_IRUSR | S_IRGRP | S_IWUSR | S_IWGRP; printk(KERN_WARNING " asus_uid and asus_gid parameters are " diff --git a/drivers/platform/x86/dell-laptop.c b/drivers/platform/x86/dell-laptop.c index 34657f96b5a5..ad24ef36f9f7 100644 --- a/drivers/platform/x86/dell-laptop.c +++ b/drivers/platform/x86/dell-laptop.c @@ -290,9 +290,12 @@ static int dell_rfkill_set(void *data, bool blocked) dell_send_request(buffer, 17, 11); /* If the hardware switch controls this radio, and the hardware - switch is disabled, don't allow changing the software state */ + switch is disabled, don't allow changing the software state. + If the hardware switch is reported as not supported, always + fire the SMI to toggle the killswitch. */ if ((hwswitch_state & BIT(hwswitch_bit)) && - !(buffer->output[1] & BIT(16))) { + !(buffer->output[1] & BIT(16)) && + (buffer->output[1] & BIT(0))) { ret = -EINVAL; goto out; } @@ -398,6 +401,23 @@ static const struct file_operations dell_debugfs_fops = { static void dell_update_rfkill(struct work_struct *ignored) { + int status; + + get_buffer(); + dell_send_request(buffer, 17, 11); + status = buffer->output[1]; + release_buffer(); + + /* if hardware rfkill is not supported, set it explicitly */ + if (!(status & BIT(0))) { + if (wifi_rfkill) + dell_rfkill_set((void *)1, !((status & BIT(17)) >> 17)); + if (bluetooth_rfkill) + dell_rfkill_set((void *)2, !((status & BIT(18)) >> 18)); + if (wwan_rfkill) + dell_rfkill_set((void *)3, !((status & BIT(19)) >> 19)); + } + if (wifi_rfkill) dell_rfkill_query(wifi_rfkill, (void *)1); if (bluetooth_rfkill) diff --git a/drivers/hwmon/hp_accel.c b/drivers/platform/x86/hp_accel.c index 3d21fa2b97cd..16b233fac8f7 100644 --- a/drivers/hwmon/hp_accel.c +++ b/drivers/platform/x86/hp_accel.c @@ -35,11 +35,11 @@ #include <linux/freezer.h> #include <linux/uaccess.h> #include <linux/leds.h> +#include <linux/atomic.h> #include <acpi/acpi_drivers.h> -#include <asm/atomic.h> -#include "lis3lv02d.h" +#include "../../misc/lis3lv02d/lis3lv02d.h" -#define DRIVER_NAME "lis3lv02d" +#define DRIVER_NAME "hp_accel" #define ACPI_MDPS_CLASS "accelerometer" /* Delayed LEDs infrastructure ------------------------------------ */ diff --git a/drivers/platform/x86/intel_pmic_gpio.c b/drivers/platform/x86/intel_pmic_gpio.c index 930e62762365..61433d492862 100644 --- a/drivers/platform/x86/intel_pmic_gpio.c +++ b/drivers/platform/x86/intel_pmic_gpio.c @@ -60,69 +60,20 @@ enum pmic_gpio_register { #define GPOSW_DOU 0x08 #define GPOSW_RDRV 0x30 +#define GPIO_UPDATE_TYPE 0x80000000 #define NUM_GPIO 24 -struct pmic_gpio_irq { - spinlock_t lock; - u32 trigger[NUM_GPIO]; - u32 dirty; - struct work_struct work; -}; - - struct pmic_gpio { + struct mutex buslock; struct gpio_chip chip; - struct pmic_gpio_irq irqtypes; void *gpiointr; int irq; unsigned irq_base; + unsigned int update_type; + u32 trigger_type; }; -static void pmic_program_irqtype(int gpio, int type) -{ - if (type & IRQ_TYPE_EDGE_RISING) - intel_scu_ipc_update_register(GPIO0 + gpio, 0x20, 0x20); - else - intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x20); - - if (type & IRQ_TYPE_EDGE_FALLING) - intel_scu_ipc_update_register(GPIO0 + gpio, 0x10, 0x10); - else - intel_scu_ipc_update_register(GPIO0 + gpio, 0x00, 0x10); -}; - -static void pmic_irqtype_work(struct work_struct *work) -{ - struct pmic_gpio_irq *t = - container_of(work, struct pmic_gpio_irq, work); - unsigned long flags; - int i; - u16 type; - - spin_lock_irqsave(&t->lock, flags); - /* As we drop the lock, we may need multiple scans if we race the - pmic_irq_type function */ - while (t->dirty) { - /* - * For each pin that has the dirty bit set send an IPC - * message to configure the hardware via the PMIC - */ - for (i = 0; i < NUM_GPIO; i++) { - if (!(t->dirty & (1 << i))) - continue; - t->dirty &= ~(1 << i); - /* We can't trust the array entry or dirty - once the lock is dropped */ - type = t->trigger[i]; - spin_unlock_irqrestore(&t->lock, flags); - pmic_program_irqtype(i, type); - spin_lock_irqsave(&t->lock, flags); - } - } - spin_unlock_irqrestore(&t->lock, flags); -} - static int pmic_gpio_direction_input(struct gpio_chip *chip, unsigned offset) { if (offset > 8) { @@ -190,25 +141,24 @@ static void pmic_gpio_set(struct gpio_chip *chip, unsigned offset, int value) 1 << (offset - 16)); } -static int pmic_irq_type(unsigned irq, unsigned type) +/* + * This is called from genirq with pg->buslock locked and + * irq_desc->lock held. We can not access the scu bus here, so we + * store the change and update in the bus_sync_unlock() function below + */ +static int pmic_irq_type(struct irq_data *data, unsigned type) { - struct pmic_gpio *pg = get_irq_chip_data(irq); - u32 gpio = irq - pg->irq_base; - unsigned long flags; + struct pmic_gpio *pg = irq_data_get_irq_chip_data(data); + u32 gpio = data->irq - pg->irq_base; if (gpio >= pg->chip.ngpio) return -EINVAL; - spin_lock_irqsave(&pg->irqtypes.lock, flags); - pg->irqtypes.trigger[gpio] = type; - pg->irqtypes.dirty |= (1 << gpio); - spin_unlock_irqrestore(&pg->irqtypes.lock, flags); - schedule_work(&pg->irqtypes.work); + pg->trigger_type = type; + pg->update_type = gpio | GPIO_UPDATE_TYPE; return 0; } - - static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned offset) { struct pmic_gpio *pg = container_of(chip, struct pmic_gpio, chip); @@ -217,38 +167,32 @@ static int pmic_gpio_to_irq(struct gpio_chip *chip, unsigned offset) } /* the gpiointr register is read-clear, so just do nothing. */ -static void pmic_irq_unmask(unsigned irq) -{ -}; +static void pmic_irq_unmask(struct irq_data *data) { } -static void pmic_irq_mask(unsigned irq) -{ -}; +static void pmic_irq_mask(struct irq_data *data) { } static struct irq_chip pmic_irqchip = { .name = "PMIC-GPIO", - .mask = pmic_irq_mask, - .unmask = pmic_irq_unmask, - .set_type = pmic_irq_type, + .irq_mask = pmic_irq_mask, + .irq_unmask = pmic_irq_unmask, + .irq_set_type = pmic_irq_type, }; -static void pmic_irq_handler(unsigned irq, struct irq_desc *desc) +static irqreturn_t pmic_irq_handler(int irq, void *data) { - struct pmic_gpio *pg = (struct pmic_gpio *)get_irq_data(irq); + struct pmic_gpio *pg = data; u8 intsts = *((u8 *)pg->gpiointr + 4); int gpio; + irqreturn_t ret = IRQ_NONE; for (gpio = 0; gpio < 8; gpio++) { if (intsts & (1 << gpio)) { pr_debug("pmic pin %d triggered\n", gpio); generic_handle_irq(pg->irq_base + gpio); + ret = IRQ_HANDLED; } } - - if (desc->chip->irq_eoi) - desc->chip->irq_eoi(irq_get_irq_data(irq)); - else - dev_warn(pg->chip.dev, "missing EOI handler for irq %d\n", irq); + return ret; } static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev) @@ -297,8 +241,7 @@ static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev) pg->chip.can_sleep = 1; pg->chip.dev = dev; - INIT_WORK(&pg->irqtypes.work, pmic_irqtype_work); - spin_lock_init(&pg->irqtypes.lock); + mutex_init(&pg->buslock); pg->chip.dev = dev; retval = gpiochip_add(&pg->chip); @@ -306,8 +249,13 @@ static int __devinit platform_pmic_gpio_probe(struct platform_device *pdev) printk(KERN_ERR "%s: Can not add pmic gpio chip.\n", __func__); goto err; } - set_irq_data(pg->irq, pg); - set_irq_chained_handler(pg->irq, pmic_irq_handler); + + retval = request_irq(pg->irq, pmic_irq_handler, 0, "pmic", pg); + if (retval) { + printk(KERN_WARNING "pmic: Interrupt request failed\n"); + goto err; + } + for (i = 0; i < 8; i++) { set_irq_chip_and_handler_name(i + pg->irq_base, &pmic_irqchip, handle_simple_irq, "demux"); diff --git a/drivers/platform/x86/tc1100-wmi.c b/drivers/platform/x86/tc1100-wmi.c index 1fe0f1feff71..865ef78d6f1a 100644 --- a/drivers/platform/x86/tc1100-wmi.c +++ b/drivers/platform/x86/tc1100-wmi.c @@ -162,7 +162,7 @@ set_bool_##value(struct device *dev, struct device_attribute *attr, \ return -EINVAL; \ return count; \ } \ -static DEVICE_ATTR(value, S_IWUGO | S_IRUGO | S_IWUSR, \ +static DEVICE_ATTR(value, S_IRUGO | S_IWUSR, \ show_bool_##value, set_bool_##value); show_set_bool(wireless, TC1100_INSTANCE_WIRELESS); diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c index dd599585c6a9..eb9922385ef8 100644 --- a/drivers/platform/x86/thinkpad_acpi.c +++ b/drivers/platform/x86/thinkpad_acpi.c @@ -2275,16 +2275,12 @@ static void tpacpi_input_send_key(const unsigned int scancode) if (keycode != KEY_RESERVED) { mutex_lock(&tpacpi_inputdev_send_mutex); + input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); input_report_key(tpacpi_inputdev, keycode, 1); - if (keycode == KEY_UNKNOWN) - input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, - scancode); input_sync(tpacpi_inputdev); + input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, scancode); input_report_key(tpacpi_inputdev, keycode, 0); - if (keycode == KEY_UNKNOWN) - input_event(tpacpi_inputdev, EV_MSC, MSC_SCAN, - scancode); input_sync(tpacpi_inputdev); mutex_unlock(&tpacpi_inputdev_send_mutex); diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c index cba1b43f7519..a4e8eb9fece6 100644 --- a/drivers/pps/kapi.c +++ b/drivers/pps/kapi.c @@ -168,7 +168,7 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, { unsigned long flags; int captured = 0; - struct pps_ktime ts_real; + struct pps_ktime ts_real = { .sec = 0, .nsec = 0, .flags = 0 }; /* check event type */ BUG_ON((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0); diff --git a/drivers/rapidio/rio-sysfs.c b/drivers/rapidio/rio-sysfs.c index 76b41853a877..1269fbd2deca 100644 --- a/drivers/rapidio/rio-sysfs.c +++ b/drivers/rapidio/rio-sysfs.c @@ -77,9 +77,9 @@ rio_read_config(struct file *filp, struct kobject *kobj, /* Several chips lock up trying to read undefined config space */ if (capable(CAP_SYS_ADMIN)) - size = 0x200000; + size = RIO_MAINT_SPACE_SZ; - if (off > size) + if (off >= size) return 0; if (off + count > size) { size -= off; @@ -147,10 +147,10 @@ rio_write_config(struct file *filp, struct kobject *kobj, loff_t init_off = off; u8 *data = (u8 *) buf; - if (off > 0x200000) + if (off >= RIO_MAINT_SPACE_SZ) return 0; - if (off + count > 0x200000) { - size = 0x200000 - off; + if (off + count > RIO_MAINT_SPACE_SZ) { + size = RIO_MAINT_SPACE_SZ - off; count = size; } @@ -200,7 +200,7 @@ static struct bin_attribute rio_config_attr = { .name = "config", .mode = S_IRUGO | S_IWUSR, }, - .size = 0x200000, + .size = RIO_MAINT_SPACE_SZ, .read = rio_read_config, .write = rio_write_config, }; diff --git a/drivers/regulator/mc13xxx-regulator-core.c b/drivers/regulator/mc13xxx-regulator-core.c index f53d31b950d4..2bb5de1f2421 100644 --- a/drivers/regulator/mc13xxx-regulator-core.c +++ b/drivers/regulator/mc13xxx-regulator-core.c @@ -174,7 +174,7 @@ static int mc13xxx_regulator_get_voltage(struct regulator_dev *rdev) dev_dbg(rdev_get_dev(rdev), "%s id: %d val: %d\n", __func__, id, val); - BUG_ON(val < 0 || val > mc13xxx_regulators[id].desc.n_voltages); + BUG_ON(val > mc13xxx_regulators[id].desc.n_voltages); return mc13xxx_regulators[id].voltages[val]; } diff --git a/drivers/regulator/wm831x-dcdc.c b/drivers/regulator/wm831x-dcdc.c index 8b0d2c4bde91..06df898842c0 100644 --- a/drivers/regulator/wm831x-dcdc.c +++ b/drivers/regulator/wm831x-dcdc.c @@ -120,6 +120,7 @@ static unsigned int wm831x_dcdc_get_mode(struct regulator_dev *rdev) return REGULATOR_MODE_IDLE; default: BUG(); + return -EINVAL; } } diff --git a/drivers/rtc/rtc-at91sam9.c b/drivers/rtc/rtc-at91sam9.c index c36749e4c926..5469c52cba3d 100644 --- a/drivers/rtc/rtc-at91sam9.c +++ b/drivers/rtc/rtc-at91sam9.c @@ -309,7 +309,7 @@ static const struct rtc_class_ops at91_rtc_ops = { .read_alarm = at91_rtc_readalarm, .set_alarm = at91_rtc_setalarm, .proc = at91_rtc_proc, - .alarm_irq_enabled = at91_rtc_alarm_irq_enable, + .alarm_irq_enable = at91_rtc_alarm_irq_enable, }; /* diff --git a/drivers/rtc/rtc-ds3232.c b/drivers/rtc/rtc-ds3232.c index 23a9ee19764c..950735415a7c 100644 --- a/drivers/rtc/rtc-ds3232.c +++ b/drivers/rtc/rtc-ds3232.c @@ -1,7 +1,7 @@ /* * RTC client/driver for the Maxim/Dallas DS3232 Real-Time Clock over I2C * - * Copyright (C) 2009-2010 Freescale Semiconductor. + * Copyright (C) 2009-2011 Freescale Semiconductor. * Author: Jack Lan <jack.lan@freescale.com> * * This program is free software; you can redistribute it and/or modify it @@ -141,9 +141,11 @@ static int ds3232_read_time(struct device *dev, struct rtc_time *time) time->tm_hour = bcd2bin(hour); } - time->tm_wday = bcd2bin(week); + /* Day of the week in linux range is 0~6 while 1~7 in RTC chip */ + time->tm_wday = bcd2bin(week) - 1; time->tm_mday = bcd2bin(day); - time->tm_mon = bcd2bin(month & 0x7F); + /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */ + time->tm_mon = bcd2bin(month & 0x7F) - 1; if (century) add_century = 100; @@ -162,9 +164,11 @@ static int ds3232_set_time(struct device *dev, struct rtc_time *time) buf[0] = bin2bcd(time->tm_sec); buf[1] = bin2bcd(time->tm_min); buf[2] = bin2bcd(time->tm_hour); - buf[3] = bin2bcd(time->tm_wday); /* Day of the week */ + /* Day of the week in linux range is 0~6 while 1~7 in RTC chip */ + buf[3] = bin2bcd(time->tm_wday + 1); buf[4] = bin2bcd(time->tm_mday); /* Date */ - buf[5] = bin2bcd(time->tm_mon); + /* linux tm_mon range:0~11, while month range is 1~12 in RTC chip */ + buf[5] = bin2bcd(time->tm_mon + 1); if (time->tm_year >= 100) { buf[5] |= 0x80; buf[6] = bin2bcd(time->tm_year - 100); diff --git a/drivers/rtc/rtc-pl030.c b/drivers/rtc/rtc-pl030.c index bbdb2f02798a..baff724cd40f 100644 --- a/drivers/rtc/rtc-pl030.c +++ b/drivers/rtc/rtc-pl030.c @@ -103,7 +103,7 @@ static const struct rtc_class_ops pl030_ops = { .set_alarm = pl030_set_alarm, }; -static int pl030_probe(struct amba_device *dev, struct amba_id *id) +static int pl030_probe(struct amba_device *dev, const struct amba_id *id) { struct pl030_rtc *rtc; int ret; diff --git a/drivers/rtc/rtc-pl031.c b/drivers/rtc/rtc-pl031.c index b7a6690e5b35..6568f4b37e19 100644 --- a/drivers/rtc/rtc-pl031.c +++ b/drivers/rtc/rtc-pl031.c @@ -358,7 +358,7 @@ static int pl031_remove(struct amba_device *adev) return 0; } -static int pl031_probe(struct amba_device *adev, struct amba_id *id) +static int pl031_probe(struct amba_device *adev, const struct amba_id *id) { int ret; struct pl031_local *ldata; diff --git a/drivers/s390/block/dasd_eckd.c b/drivers/s390/block/dasd_eckd.c index 318672d05563..a9fe23d5bd0f 100644 --- a/drivers/s390/block/dasd_eckd.c +++ b/drivers/s390/block/dasd_eckd.c @@ -72,7 +72,7 @@ static struct dasd_discipline dasd_eckd_discipline; static struct ccw_device_id dasd_eckd_ids[] = { { CCW_DEVICE_DEVTYPE (0x3990, 0, 0x3390, 0), .driver_info = 0x1}, { CCW_DEVICE_DEVTYPE (0x2105, 0, 0x3390, 0), .driver_info = 0x2}, - { CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3390, 0), .driver_info = 0x3}, + { CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3380, 0), .driver_info = 0x3}, { CCW_DEVICE_DEVTYPE (0x3990, 0, 0x3380, 0), .driver_info = 0x4}, { CCW_DEVICE_DEVTYPE (0x2105, 0, 0x3380, 0), .driver_info = 0x5}, { CCW_DEVICE_DEVTYPE (0x9343, 0, 0x9345, 0), .driver_info = 0x6}, diff --git a/drivers/s390/block/xpram.c b/drivers/s390/block/xpram.c index c881a14fa5dd..1f6a4d894e73 100644 --- a/drivers/s390/block/xpram.c +++ b/drivers/s390/block/xpram.c @@ -62,8 +62,8 @@ static int xpram_devs; /* * Parameter parsing functions. */ -static int __initdata devs = XPRAM_DEVS; -static char __initdata *sizes[XPRAM_MAX_DEVS]; +static int devs = XPRAM_DEVS; +static char *sizes[XPRAM_MAX_DEVS]; module_param(devs, int, 0); module_param_array(sizes, charp, NULL, 0); diff --git a/drivers/s390/cio/cio.c b/drivers/s390/cio/cio.c index 430f875006f2..f3147542e52e 100644 --- a/drivers/s390/cio/cio.c +++ b/drivers/s390/cio/cio.c @@ -630,11 +630,7 @@ void __irq_entry do_IRQ(struct pt_regs *regs) irb = (struct irb *)&S390_lowcore.irb; do { kstat_cpu(smp_processor_id()).irqs[IO_INTERRUPT]++; - /* - * Non I/O-subchannel thin interrupts are processed differently - */ - if (tpi_info->adapter_IO == 1 && - tpi_info->int_type == IO_INTERRUPT_TYPE) { + if (tpi_info->adapter_IO) { do_adapter_IO(tpi_info->isc); continue; } diff --git a/drivers/s390/cio/cio.h b/drivers/s390/cio/cio.h index bf7f80f5a330..7a9032d01fb8 100644 --- a/drivers/s390/cio/cio.h +++ b/drivers/s390/cio/cio.h @@ -105,8 +105,6 @@ struct subchannel { struct schib_config config; } __attribute__ ((aligned(8))); -#define IO_INTERRUPT_TYPE 0 /* I/O interrupt type */ - #define to_subchannel(n) container_of(n, struct subchannel, dev) extern int cio_validate_subchannel (struct subchannel *, struct subchannel_id); diff --git a/drivers/s390/scsi/zfcp_aux.c b/drivers/s390/scsi/zfcp_aux.c index 51c666fb67a4..645b0fcbb370 100644 --- a/drivers/s390/scsi/zfcp_aux.c +++ b/drivers/s390/scsi/zfcp_aux.c @@ -122,36 +122,21 @@ static int __init zfcp_module_init(void) { int retval = -ENOMEM; - zfcp_data.gpn_ft_cache = zfcp_cache_hw_align("zfcp_gpn", - sizeof(struct zfcp_fc_gpn_ft_req)); - if (!zfcp_data.gpn_ft_cache) - goto out; - - zfcp_data.qtcb_cache = zfcp_cache_hw_align("zfcp_qtcb", - sizeof(struct fsf_qtcb)); - if (!zfcp_data.qtcb_cache) + zfcp_fsf_qtcb_cache = zfcp_cache_hw_align("zfcp_fsf_qtcb", + sizeof(struct fsf_qtcb)); + if (!zfcp_fsf_qtcb_cache) goto out_qtcb_cache; - zfcp_data.sr_buffer_cache = zfcp_cache_hw_align("zfcp_sr", - sizeof(struct fsf_status_read_buffer)); - if (!zfcp_data.sr_buffer_cache) - goto out_sr_cache; + zfcp_fc_req_cache = zfcp_cache_hw_align("zfcp_fc_req", + sizeof(struct zfcp_fc_req)); + if (!zfcp_fc_req_cache) + goto out_fc_cache; - zfcp_data.gid_pn_cache = zfcp_cache_hw_align("zfcp_gid", - sizeof(struct zfcp_fc_gid_pn)); - if (!zfcp_data.gid_pn_cache) - goto out_gid_cache; - - zfcp_data.adisc_cache = zfcp_cache_hw_align("zfcp_adisc", - sizeof(struct zfcp_fc_els_adisc)); - if (!zfcp_data.adisc_cache) - goto out_adisc_cache; - - zfcp_data.scsi_transport_template = + zfcp_scsi_transport_template = fc_attach_transport(&zfcp_transport_functions); - if (!zfcp_data.scsi_transport_template) + if (!zfcp_scsi_transport_template) goto out_transport; - scsi_transport_reserve_device(zfcp_data.scsi_transport_template, + scsi_transport_reserve_device(zfcp_scsi_transport_template, sizeof(struct zfcp_scsi_dev)); @@ -175,18 +160,12 @@ static int __init zfcp_module_init(void) out_ccw_register: misc_deregister(&zfcp_cfdc_misc); out_misc: - fc_release_transport(zfcp_data.scsi_transport_template); + fc_release_transport(zfcp_scsi_transport_template); out_transport: - kmem_cache_destroy(zfcp_data.adisc_cache); -out_adisc_cache: - kmem_cache_destroy(zfcp_data.gid_pn_cache); -out_gid_cache: - kmem_cache_destroy(zfcp_data.sr_buffer_cache); -out_sr_cache: - kmem_cache_destroy(zfcp_data.qtcb_cache); + kmem_cache_destroy(zfcp_fc_req_cache); +out_fc_cache: + kmem_cache_destroy(zfcp_fsf_qtcb_cache); out_qtcb_cache: - kmem_cache_destroy(zfcp_data.gpn_ft_cache); -out: return retval; } @@ -196,12 +175,9 @@ static void __exit zfcp_module_exit(void) { ccw_driver_unregister(&zfcp_ccw_driver); misc_deregister(&zfcp_cfdc_misc); - fc_release_transport(zfcp_data.scsi_transport_template); - kmem_cache_destroy(zfcp_data.adisc_cache); - kmem_cache_destroy(zfcp_data.gid_pn_cache); - kmem_cache_destroy(zfcp_data.sr_buffer_cache); - kmem_cache_destroy(zfcp_data.qtcb_cache); - kmem_cache_destroy(zfcp_data.gpn_ft_cache); + fc_release_transport(zfcp_scsi_transport_template); + kmem_cache_destroy(zfcp_fc_req_cache); + kmem_cache_destroy(zfcp_fsf_qtcb_cache); } module_exit(zfcp_module_exit); @@ -260,18 +236,18 @@ static int zfcp_allocate_low_mem_buffers(struct zfcp_adapter *adapter) return -ENOMEM; adapter->pool.qtcb_pool = - mempool_create_slab_pool(4, zfcp_data.qtcb_cache); + mempool_create_slab_pool(4, zfcp_fsf_qtcb_cache); if (!adapter->pool.qtcb_pool) return -ENOMEM; - adapter->pool.status_read_data = - mempool_create_slab_pool(FSF_STATUS_READS_RECOM, - zfcp_data.sr_buffer_cache); - if (!adapter->pool.status_read_data) + BUILD_BUG_ON(sizeof(struct fsf_status_read_buffer) > PAGE_SIZE); + adapter->pool.sr_data = + mempool_create_page_pool(FSF_STATUS_READS_RECOM, 0); + if (!adapter->pool.sr_data) return -ENOMEM; adapter->pool.gid_pn = - mempool_create_slab_pool(1, zfcp_data.gid_pn_cache); + mempool_create_slab_pool(1, zfcp_fc_req_cache); if (!adapter->pool.gid_pn) return -ENOMEM; @@ -290,8 +266,8 @@ static void zfcp_free_low_mem_buffers(struct zfcp_adapter *adapter) mempool_destroy(adapter->pool.qtcb_pool); if (adapter->pool.status_read_req) mempool_destroy(adapter->pool.status_read_req); - if (adapter->pool.status_read_data) - mempool_destroy(adapter->pool.status_read_data); + if (adapter->pool.sr_data) + mempool_destroy(adapter->pool.sr_data); if (adapter->pool.gid_pn) mempool_destroy(adapter->pool.gid_pn); } @@ -386,6 +362,7 @@ struct zfcp_adapter *zfcp_adapter_enqueue(struct ccw_device *ccw_device) INIT_WORK(&adapter->stat_work, _zfcp_status_read_scheduler); INIT_WORK(&adapter->scan_work, zfcp_fc_scan_ports); + INIT_WORK(&adapter->ns_up_work, zfcp_fc_sym_name_update); if (zfcp_qdio_setup(adapter)) goto failed; @@ -437,7 +414,7 @@ struct zfcp_adapter *zfcp_adapter_enqueue(struct ccw_device *ccw_device) adapter->dma_parms.max_segment_size = ZFCP_QDIO_SBALE_LEN; adapter->ccw_device->dev.dma_parms = &adapter->dma_parms; - if (!zfcp_adapter_scsi_register(adapter)) + if (!zfcp_scsi_adapter_register(adapter)) return adapter; failed: @@ -451,10 +428,11 @@ void zfcp_adapter_unregister(struct zfcp_adapter *adapter) cancel_work_sync(&adapter->scan_work); cancel_work_sync(&adapter->stat_work); + cancel_work_sync(&adapter->ns_up_work); zfcp_destroy_adapter_work_queue(adapter); zfcp_fc_wka_ports_force_offline(adapter->gs); - zfcp_adapter_scsi_unregister(adapter); + zfcp_scsi_adapter_unregister(adapter); sysfs_remove_group(&cdev->dev.kobj, &zfcp_sysfs_adapter_attrs); zfcp_erp_thread_kill(adapter); diff --git a/drivers/s390/scsi/zfcp_def.h b/drivers/s390/scsi/zfcp_def.h index 9ae1d0a6f627..527ba48eea57 100644 --- a/drivers/s390/scsi/zfcp_def.h +++ b/drivers/s390/scsi/zfcp_def.h @@ -89,7 +89,6 @@ struct zfcp_reqlist; #define ZFCP_STATUS_LUN_READONLY 0x00000008 /* FSF request status (this does not have a common part) */ -#define ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT 0x00000002 #define ZFCP_STATUS_FSFREQ_ERROR 0x00000008 #define ZFCP_STATUS_FSFREQ_CLEANUP 0x00000010 #define ZFCP_STATUS_FSFREQ_ABORTSUCCEEDED 0x00000040 @@ -108,7 +107,7 @@ struct zfcp_adapter_mempool { mempool_t *scsi_req; mempool_t *scsi_abort; mempool_t *status_read_req; - mempool_t *status_read_data; + mempool_t *sr_data; mempool_t *gid_pn; mempool_t *qtcb_pool; }; @@ -190,6 +189,7 @@ struct zfcp_adapter { struct fsf_qtcb_bottom_port *stats_reset_data; unsigned long stats_reset; struct work_struct scan_work; + struct work_struct ns_up_work; struct service_level service_level; struct workqueue_struct *work_queue; struct device_dma_parameters dma_parms; @@ -314,15 +314,4 @@ struct zfcp_fsf_req { void (*handler)(struct zfcp_fsf_req *); }; -/* driver data */ -struct zfcp_data { - struct scsi_host_template scsi_host_template; - struct scsi_transport_template *scsi_transport_template; - struct kmem_cache *gpn_ft_cache; - struct kmem_cache *qtcb_cache; - struct kmem_cache *sr_buffer_cache; - struct kmem_cache *gid_pn_cache; - struct kmem_cache *adisc_cache; -}; - #endif /* ZFCP_DEF_H */ diff --git a/drivers/s390/scsi/zfcp_erp.c b/drivers/s390/scsi/zfcp_erp.c index e003e306f870..e1b4f800e226 100644 --- a/drivers/s390/scsi/zfcp_erp.c +++ b/drivers/s390/scsi/zfcp_erp.c @@ -732,7 +732,7 @@ static int zfcp_erp_adapter_strategy_open_fsf(struct zfcp_erp_action *act) if (zfcp_erp_adapter_strategy_open_fsf_xport(act) == ZFCP_ERP_FAILED) return ZFCP_ERP_FAILED; - if (mempool_resize(act->adapter->pool.status_read_data, + if (mempool_resize(act->adapter->pool.sr_data, act->adapter->stat_read_buf_num, GFP_KERNEL)) return ZFCP_ERP_FAILED; @@ -1231,8 +1231,10 @@ static void zfcp_erp_action_cleanup(struct zfcp_erp_action *act, int result) if (result == ZFCP_ERP_SUCCEEDED) { register_service_level(&adapter->service_level); queue_work(adapter->work_queue, &adapter->scan_work); + queue_work(adapter->work_queue, &adapter->ns_up_work); } else unregister_service_level(&adapter->service_level); + kref_put(&adapter->ref, zfcp_adapter_release); break; } diff --git a/drivers/s390/scsi/zfcp_ext.h b/drivers/s390/scsi/zfcp_ext.h index 6e325284fbe7..03627cfd81cd 100644 --- a/drivers/s390/scsi/zfcp_ext.h +++ b/drivers/s390/scsi/zfcp_ext.h @@ -80,6 +80,7 @@ extern void zfcp_erp_notify(struct zfcp_erp_action *, unsigned long); extern void zfcp_erp_timeout_handler(unsigned long); /* zfcp_fc.c */ +extern struct kmem_cache *zfcp_fc_req_cache; extern void zfcp_fc_enqueue_event(struct zfcp_adapter *, enum fc_host_event_code event_code, u32); extern void zfcp_fc_post_event(struct work_struct *); @@ -95,8 +96,10 @@ extern int zfcp_fc_gs_setup(struct zfcp_adapter *); extern void zfcp_fc_gs_destroy(struct zfcp_adapter *); extern int zfcp_fc_exec_bsg_job(struct fc_bsg_job *); extern int zfcp_fc_timeout_bsg_job(struct fc_bsg_job *); +extern void zfcp_fc_sym_name_update(struct work_struct *); /* zfcp_fsf.c */ +extern struct kmem_cache *zfcp_fsf_qtcb_cache; extern int zfcp_fsf_open_port(struct zfcp_erp_action *); extern int zfcp_fsf_open_wka_port(struct zfcp_fc_wka_port *); extern int zfcp_fsf_close_wka_port(struct zfcp_fc_wka_port *); @@ -139,9 +142,9 @@ extern struct zfcp_fsf_req *zfcp_fsf_get_req(struct zfcp_qdio *, struct qdio_buffer *); /* zfcp_scsi.c */ -extern struct zfcp_data zfcp_data; -extern int zfcp_adapter_scsi_register(struct zfcp_adapter *); -extern void zfcp_adapter_scsi_unregister(struct zfcp_adapter *); +extern struct scsi_transport_template *zfcp_scsi_transport_template; +extern int zfcp_scsi_adapter_register(struct zfcp_adapter *); +extern void zfcp_scsi_adapter_unregister(struct zfcp_adapter *); extern struct fc_function_template zfcp_transport_functions; extern void zfcp_scsi_rport_work(struct work_struct *); extern void zfcp_scsi_schedule_rport_register(struct zfcp_port *); diff --git a/drivers/s390/scsi/zfcp_fc.c b/drivers/s390/scsi/zfcp_fc.c index 30cf91a787a3..297e6b71ce9c 100644 --- a/drivers/s390/scsi/zfcp_fc.c +++ b/drivers/s390/scsi/zfcp_fc.c @@ -11,11 +11,14 @@ #include <linux/types.h> #include <linux/slab.h> +#include <linux/utsname.h> #include <scsi/fc/fc_els.h> #include <scsi/libfc.h> #include "zfcp_ext.h" #include "zfcp_fc.h" +struct kmem_cache *zfcp_fc_req_cache; + static u32 zfcp_fc_rscn_range_mask[] = { [ELS_ADDR_FMT_PORT] = 0xFFFFFF, [ELS_ADDR_FMT_AREA] = 0xFFFF00, @@ -260,24 +263,18 @@ void zfcp_fc_incoming_els(struct zfcp_fsf_req *fsf_req) zfcp_fc_incoming_rscn(fsf_req); } -static void zfcp_fc_ns_gid_pn_eval(void *data) +static void zfcp_fc_ns_gid_pn_eval(struct zfcp_fc_req *fc_req) { - struct zfcp_fc_gid_pn *gid_pn = data; - struct zfcp_fsf_ct_els *ct = &gid_pn->ct; - struct zfcp_fc_gid_pn_req *gid_pn_req = sg_virt(ct->req); - struct zfcp_fc_gid_pn_resp *gid_pn_resp = sg_virt(ct->resp); - struct zfcp_port *port = gid_pn->port; + struct zfcp_fsf_ct_els *ct_els = &fc_req->ct_els; + struct zfcp_fc_gid_pn_rsp *gid_pn_rsp = &fc_req->u.gid_pn.rsp; - if (ct->status) + if (ct_els->status) return; - if (gid_pn_resp->ct_hdr.ct_cmd != FC_FS_ACC) + if (gid_pn_rsp->ct_hdr.ct_cmd != FC_FS_ACC) return; - /* paranoia */ - if (gid_pn_req->gid_pn.fn_wwpn != port->wwpn) - return; /* looks like a valid d_id */ - port->d_id = ntoh24(gid_pn_resp->gid_pn.fp_fid); + ct_els->port->d_id = ntoh24(gid_pn_rsp->gid_pn.fp_fid); } static void zfcp_fc_complete(void *data) @@ -285,69 +282,73 @@ static void zfcp_fc_complete(void *data) complete(data); } +static void zfcp_fc_ct_ns_init(struct fc_ct_hdr *ct_hdr, u16 cmd, u16 mr_size) +{ + ct_hdr->ct_rev = FC_CT_REV; + ct_hdr->ct_fs_type = FC_FST_DIR; + ct_hdr->ct_fs_subtype = FC_NS_SUBTYPE; + ct_hdr->ct_cmd = cmd; + ct_hdr->ct_mr_size = mr_size / 4; +} + static int zfcp_fc_ns_gid_pn_request(struct zfcp_port *port, - struct zfcp_fc_gid_pn *gid_pn) + struct zfcp_fc_req *fc_req) { struct zfcp_adapter *adapter = port->adapter; DECLARE_COMPLETION_ONSTACK(completion); + struct zfcp_fc_gid_pn_req *gid_pn_req = &fc_req->u.gid_pn.req; + struct zfcp_fc_gid_pn_rsp *gid_pn_rsp = &fc_req->u.gid_pn.rsp; int ret; /* setup parameters for send generic command */ - gid_pn->port = port; - gid_pn->ct.handler = zfcp_fc_complete; - gid_pn->ct.handler_data = &completion; - gid_pn->ct.req = &gid_pn->sg_req; - gid_pn->ct.resp = &gid_pn->sg_resp; - sg_init_one(&gid_pn->sg_req, &gid_pn->gid_pn_req, - sizeof(struct zfcp_fc_gid_pn_req)); - sg_init_one(&gid_pn->sg_resp, &gid_pn->gid_pn_resp, - sizeof(struct zfcp_fc_gid_pn_resp)); - - /* setup nameserver request */ - gid_pn->gid_pn_req.ct_hdr.ct_rev = FC_CT_REV; - gid_pn->gid_pn_req.ct_hdr.ct_fs_type = FC_FST_DIR; - gid_pn->gid_pn_req.ct_hdr.ct_fs_subtype = FC_NS_SUBTYPE; - gid_pn->gid_pn_req.ct_hdr.ct_options = 0; - gid_pn->gid_pn_req.ct_hdr.ct_cmd = FC_NS_GID_PN; - gid_pn->gid_pn_req.ct_hdr.ct_mr_size = ZFCP_FC_CT_SIZE_PAGE / 4; - gid_pn->gid_pn_req.gid_pn.fn_wwpn = port->wwpn; - - ret = zfcp_fsf_send_ct(&adapter->gs->ds, &gid_pn->ct, + fc_req->ct_els.port = port; + fc_req->ct_els.handler = zfcp_fc_complete; + fc_req->ct_els.handler_data = &completion; + fc_req->ct_els.req = &fc_req->sg_req; + fc_req->ct_els.resp = &fc_req->sg_rsp; + sg_init_one(&fc_req->sg_req, gid_pn_req, sizeof(*gid_pn_req)); + sg_init_one(&fc_req->sg_rsp, gid_pn_rsp, sizeof(*gid_pn_rsp)); + + zfcp_fc_ct_ns_init(&gid_pn_req->ct_hdr, + FC_NS_GID_PN, ZFCP_FC_CT_SIZE_PAGE); + gid_pn_req->gid_pn.fn_wwpn = port->wwpn; + + ret = zfcp_fsf_send_ct(&adapter->gs->ds, &fc_req->ct_els, adapter->pool.gid_pn_req, ZFCP_FC_CTELS_TMO); if (!ret) { wait_for_completion(&completion); - zfcp_fc_ns_gid_pn_eval(gid_pn); + zfcp_fc_ns_gid_pn_eval(fc_req); } return ret; } /** - * zfcp_fc_ns_gid_pn_request - initiate GID_PN nameserver request + * zfcp_fc_ns_gid_pn - initiate GID_PN nameserver request * @port: port where GID_PN request is needed * return: -ENOMEM on error, 0 otherwise */ static int zfcp_fc_ns_gid_pn(struct zfcp_port *port) { int ret; - struct zfcp_fc_gid_pn *gid_pn; + struct zfcp_fc_req *fc_req; struct zfcp_adapter *adapter = port->adapter; - gid_pn = mempool_alloc(adapter->pool.gid_pn, GFP_ATOMIC); - if (!gid_pn) + fc_req = mempool_alloc(adapter->pool.gid_pn, GFP_ATOMIC); + if (!fc_req) return -ENOMEM; - memset(gid_pn, 0, sizeof(*gid_pn)); + memset(fc_req, 0, sizeof(*fc_req)); ret = zfcp_fc_wka_port_get(&adapter->gs->ds); if (ret) goto out; - ret = zfcp_fc_ns_gid_pn_request(port, gid_pn); + ret = zfcp_fc_ns_gid_pn_request(port, fc_req); zfcp_fc_wka_port_put(&adapter->gs->ds); out: - mempool_free(gid_pn, adapter->pool.gid_pn); + mempool_free(fc_req, adapter->pool.gid_pn); return ret; } @@ -419,11 +420,11 @@ void zfcp_fc_plogi_evaluate(struct zfcp_port *port, struct fc_els_flogi *plogi) static void zfcp_fc_adisc_handler(void *data) { - struct zfcp_fc_els_adisc *adisc = data; - struct zfcp_port *port = adisc->els.port; - struct fc_els_adisc *adisc_resp = &adisc->adisc_resp; + struct zfcp_fc_req *fc_req = data; + struct zfcp_port *port = fc_req->ct_els.port; + struct fc_els_adisc *adisc_resp = &fc_req->u.adisc.rsp; - if (adisc->els.status) { + if (fc_req->ct_els.status) { /* request rejected or timed out */ zfcp_erp_port_forced_reopen(port, ZFCP_STATUS_COMMON_ERP_FAILED, "fcadh_1"); @@ -445,42 +446,42 @@ static void zfcp_fc_adisc_handler(void *data) out: atomic_clear_mask(ZFCP_STATUS_PORT_LINK_TEST, &port->status); put_device(&port->dev); - kmem_cache_free(zfcp_data.adisc_cache, adisc); + kmem_cache_free(zfcp_fc_req_cache, fc_req); } static int zfcp_fc_adisc(struct zfcp_port *port) { - struct zfcp_fc_els_adisc *adisc; + struct zfcp_fc_req *fc_req; struct zfcp_adapter *adapter = port->adapter; + struct Scsi_Host *shost = adapter->scsi_host; int ret; - adisc = kmem_cache_zalloc(zfcp_data.adisc_cache, GFP_ATOMIC); - if (!adisc) + fc_req = kmem_cache_zalloc(zfcp_fc_req_cache, GFP_ATOMIC); + if (!fc_req) return -ENOMEM; - adisc->els.port = port; - adisc->els.req = &adisc->req; - adisc->els.resp = &adisc->resp; - sg_init_one(adisc->els.req, &adisc->adisc_req, + fc_req->ct_els.port = port; + fc_req->ct_els.req = &fc_req->sg_req; + fc_req->ct_els.resp = &fc_req->sg_rsp; + sg_init_one(&fc_req->sg_req, &fc_req->u.adisc.req, sizeof(struct fc_els_adisc)); - sg_init_one(adisc->els.resp, &adisc->adisc_resp, + sg_init_one(&fc_req->sg_rsp, &fc_req->u.adisc.rsp, sizeof(struct fc_els_adisc)); - adisc->els.handler = zfcp_fc_adisc_handler; - adisc->els.handler_data = adisc; + fc_req->ct_els.handler = zfcp_fc_adisc_handler; + fc_req->ct_els.handler_data = fc_req; /* acc. to FC-FS, hard_nport_id in ADISC should not be set for ports without FC-AL-2 capability, so we don't set it */ - adisc->adisc_req.adisc_wwpn = fc_host_port_name(adapter->scsi_host); - adisc->adisc_req.adisc_wwnn = fc_host_node_name(adapter->scsi_host); - adisc->adisc_req.adisc_cmd = ELS_ADISC; - hton24(adisc->adisc_req.adisc_port_id, - fc_host_port_id(adapter->scsi_host)); + fc_req->u.adisc.req.adisc_wwpn = fc_host_port_name(shost); + fc_req->u.adisc.req.adisc_wwnn = fc_host_node_name(shost); + fc_req->u.adisc.req.adisc_cmd = ELS_ADISC; + hton24(fc_req->u.adisc.req.adisc_port_id, fc_host_port_id(shost)); - ret = zfcp_fsf_send_els(adapter, port->d_id, &adisc->els, + ret = zfcp_fsf_send_els(adapter, port->d_id, &fc_req->ct_els, ZFCP_FC_CTELS_TMO); if (ret) - kmem_cache_free(zfcp_data.adisc_cache, adisc); + kmem_cache_free(zfcp_fc_req_cache, fc_req); return ret; } @@ -528,68 +529,42 @@ void zfcp_fc_test_link(struct zfcp_port *port) put_device(&port->dev); } -static void zfcp_free_sg_env(struct zfcp_fc_gpn_ft *gpn_ft, int buf_num) +static struct zfcp_fc_req *zfcp_alloc_sg_env(int buf_num) { - struct scatterlist *sg = &gpn_ft->sg_req; - - kmem_cache_free(zfcp_data.gpn_ft_cache, sg_virt(sg)); - zfcp_sg_free_table(gpn_ft->sg_resp, buf_num); - - kfree(gpn_ft); -} + struct zfcp_fc_req *fc_req; -static struct zfcp_fc_gpn_ft *zfcp_alloc_sg_env(int buf_num) -{ - struct zfcp_fc_gpn_ft *gpn_ft; - struct zfcp_fc_gpn_ft_req *req; - - gpn_ft = kzalloc(sizeof(*gpn_ft), GFP_KERNEL); - if (!gpn_ft) + fc_req = kmem_cache_zalloc(zfcp_fc_req_cache, GFP_KERNEL); + if (!fc_req) return NULL; - req = kmem_cache_zalloc(zfcp_data.gpn_ft_cache, GFP_KERNEL); - if (!req) { - kfree(gpn_ft); - gpn_ft = NULL; - goto out; + if (zfcp_sg_setup_table(&fc_req->sg_rsp, buf_num)) { + kmem_cache_free(zfcp_fc_req_cache, fc_req); + return NULL; } - sg_init_one(&gpn_ft->sg_req, req, sizeof(*req)); - if (zfcp_sg_setup_table(gpn_ft->sg_resp, buf_num)) { - zfcp_free_sg_env(gpn_ft, buf_num); - gpn_ft = NULL; - } -out: - return gpn_ft; -} + sg_init_one(&fc_req->sg_req, &fc_req->u.gpn_ft.req, + sizeof(struct zfcp_fc_gpn_ft_req)); + return fc_req; +} -static int zfcp_fc_send_gpn_ft(struct zfcp_fc_gpn_ft *gpn_ft, +static int zfcp_fc_send_gpn_ft(struct zfcp_fc_req *fc_req, struct zfcp_adapter *adapter, int max_bytes) { - struct zfcp_fsf_ct_els *ct = &gpn_ft->ct; - struct zfcp_fc_gpn_ft_req *req = sg_virt(&gpn_ft->sg_req); + struct zfcp_fsf_ct_els *ct_els = &fc_req->ct_els; + struct zfcp_fc_gpn_ft_req *req = &fc_req->u.gpn_ft.req; DECLARE_COMPLETION_ONSTACK(completion); int ret; - /* prepare CT IU for GPN_FT */ - req->ct_hdr.ct_rev = FC_CT_REV; - req->ct_hdr.ct_fs_type = FC_FST_DIR; - req->ct_hdr.ct_fs_subtype = FC_NS_SUBTYPE; - req->ct_hdr.ct_options = 0; - req->ct_hdr.ct_cmd = FC_NS_GPN_FT; - req->ct_hdr.ct_mr_size = max_bytes / 4; - req->gpn_ft.fn_domain_id_scope = 0; - req->gpn_ft.fn_area_id_scope = 0; + zfcp_fc_ct_ns_init(&req->ct_hdr, FC_NS_GPN_FT, max_bytes); req->gpn_ft.fn_fc4_type = FC_TYPE_FCP; - /* prepare zfcp_send_ct */ - ct->handler = zfcp_fc_complete; - ct->handler_data = &completion; - ct->req = &gpn_ft->sg_req; - ct->resp = gpn_ft->sg_resp; + ct_els->handler = zfcp_fc_complete; + ct_els->handler_data = &completion; + ct_els->req = &fc_req->sg_req; + ct_els->resp = &fc_req->sg_rsp; - ret = zfcp_fsf_send_ct(&adapter->gs->ds, ct, NULL, + ret = zfcp_fsf_send_ct(&adapter->gs->ds, ct_els, NULL, ZFCP_FC_CTELS_TMO); if (!ret) wait_for_completion(&completion); @@ -610,11 +585,11 @@ static void zfcp_fc_validate_port(struct zfcp_port *port, struct list_head *lh) list_move_tail(&port->list, lh); } -static int zfcp_fc_eval_gpn_ft(struct zfcp_fc_gpn_ft *gpn_ft, +static int zfcp_fc_eval_gpn_ft(struct zfcp_fc_req *fc_req, struct zfcp_adapter *adapter, int max_entries) { - struct zfcp_fsf_ct_els *ct = &gpn_ft->ct; - struct scatterlist *sg = gpn_ft->sg_resp; + struct zfcp_fsf_ct_els *ct_els = &fc_req->ct_els; + struct scatterlist *sg = &fc_req->sg_rsp; struct fc_ct_hdr *hdr = sg_virt(sg); struct fc_gpn_ft_resp *acc = sg_virt(sg); struct zfcp_port *port, *tmp; @@ -623,7 +598,7 @@ static int zfcp_fc_eval_gpn_ft(struct zfcp_fc_gpn_ft *gpn_ft, u32 d_id; int ret = 0, x, last = 0; - if (ct->status) + if (ct_els->status) return -EIO; if (hdr->ct_cmd != FC_FS_ACC) { @@ -687,7 +662,7 @@ void zfcp_fc_scan_ports(struct work_struct *work) struct zfcp_adapter *adapter = container_of(work, struct zfcp_adapter, scan_work); int ret, i; - struct zfcp_fc_gpn_ft *gpn_ft; + struct zfcp_fc_req *fc_req; int chain, max_entries, buf_num, max_bytes; chain = adapter->adapter_features & FSF_FEATURE_ELS_CT_CHAINED_SBALS; @@ -702,25 +677,145 @@ void zfcp_fc_scan_ports(struct work_struct *work) if (zfcp_fc_wka_port_get(&adapter->gs->ds)) return; - gpn_ft = zfcp_alloc_sg_env(buf_num); - if (!gpn_ft) + fc_req = zfcp_alloc_sg_env(buf_num); + if (!fc_req) goto out; for (i = 0; i < 3; i++) { - ret = zfcp_fc_send_gpn_ft(gpn_ft, adapter, max_bytes); + ret = zfcp_fc_send_gpn_ft(fc_req, adapter, max_bytes); if (!ret) { - ret = zfcp_fc_eval_gpn_ft(gpn_ft, adapter, max_entries); + ret = zfcp_fc_eval_gpn_ft(fc_req, adapter, max_entries); if (ret == -EAGAIN) ssleep(1); else break; } } - zfcp_free_sg_env(gpn_ft, buf_num); + zfcp_sg_free_table(&fc_req->sg_rsp, buf_num); + kmem_cache_free(zfcp_fc_req_cache, fc_req); out: zfcp_fc_wka_port_put(&adapter->gs->ds); } +static int zfcp_fc_gspn(struct zfcp_adapter *adapter, + struct zfcp_fc_req *fc_req) +{ + DECLARE_COMPLETION_ONSTACK(completion); + char devno[] = "DEVNO:"; + struct zfcp_fsf_ct_els *ct_els = &fc_req->ct_els; + struct zfcp_fc_gspn_req *gspn_req = &fc_req->u.gspn.req; + struct zfcp_fc_gspn_rsp *gspn_rsp = &fc_req->u.gspn.rsp; + int ret; + + zfcp_fc_ct_ns_init(&gspn_req->ct_hdr, FC_NS_GSPN_ID, + FC_SYMBOLIC_NAME_SIZE); + hton24(gspn_req->gspn.fp_fid, fc_host_port_id(adapter->scsi_host)); + + sg_init_one(&fc_req->sg_req, gspn_req, sizeof(*gspn_req)); + sg_init_one(&fc_req->sg_rsp, gspn_rsp, sizeof(*gspn_rsp)); + + ct_els->handler = zfcp_fc_complete; + ct_els->handler_data = &completion; + ct_els->req = &fc_req->sg_req; + ct_els->resp = &fc_req->sg_rsp; + + ret = zfcp_fsf_send_ct(&adapter->gs->ds, ct_els, NULL, + ZFCP_FC_CTELS_TMO); + if (ret) + return ret; + + wait_for_completion(&completion); + if (ct_els->status) + return ct_els->status; + + if (fc_host_port_type(adapter->scsi_host) == FC_PORTTYPE_NPIV && + !(strstr(gspn_rsp->gspn.fp_name, devno))) + snprintf(fc_host_symbolic_name(adapter->scsi_host), + FC_SYMBOLIC_NAME_SIZE, "%s%s %s NAME: %s", + gspn_rsp->gspn.fp_name, devno, + dev_name(&adapter->ccw_device->dev), + init_utsname()->nodename); + else + strlcpy(fc_host_symbolic_name(adapter->scsi_host), + gspn_rsp->gspn.fp_name, FC_SYMBOLIC_NAME_SIZE); + + return 0; +} + +static void zfcp_fc_rspn(struct zfcp_adapter *adapter, + struct zfcp_fc_req *fc_req) +{ + DECLARE_COMPLETION_ONSTACK(completion); + struct Scsi_Host *shost = adapter->scsi_host; + struct zfcp_fsf_ct_els *ct_els = &fc_req->ct_els; + struct zfcp_fc_rspn_req *rspn_req = &fc_req->u.rspn.req; + struct fc_ct_hdr *rspn_rsp = &fc_req->u.rspn.rsp; + int ret, len; + + zfcp_fc_ct_ns_init(&rspn_req->ct_hdr, FC_NS_RSPN_ID, + FC_SYMBOLIC_NAME_SIZE); + hton24(rspn_req->rspn.fr_fid.fp_fid, fc_host_port_id(shost)); + len = strlcpy(rspn_req->rspn.fr_name, fc_host_symbolic_name(shost), + FC_SYMBOLIC_NAME_SIZE); + rspn_req->rspn.fr_name_len = len; + + sg_init_one(&fc_req->sg_req, rspn_req, sizeof(*rspn_req)); + sg_init_one(&fc_req->sg_rsp, rspn_rsp, sizeof(*rspn_rsp)); + + ct_els->handler = zfcp_fc_complete; + ct_els->handler_data = &completion; + ct_els->req = &fc_req->sg_req; + ct_els->resp = &fc_req->sg_rsp; + + ret = zfcp_fsf_send_ct(&adapter->gs->ds, ct_els, NULL, + ZFCP_FC_CTELS_TMO); + if (!ret) + wait_for_completion(&completion); +} + +/** + * zfcp_fc_sym_name_update - Retrieve and update the symbolic port name + * @work: ns_up_work of the adapter where to update the symbolic port name + * + * Retrieve the current symbolic port name that may have been set by + * the hardware using the GSPN request and update the fc_host + * symbolic_name sysfs attribute. When running in NPIV mode (and hence + * the port name is unique for this system), update the symbolic port + * name to add Linux specific information and update the FC nameserver + * using the RSPN request. + */ +void zfcp_fc_sym_name_update(struct work_struct *work) +{ + struct zfcp_adapter *adapter = container_of(work, struct zfcp_adapter, + ns_up_work); + int ret; + struct zfcp_fc_req *fc_req; + + if (fc_host_port_type(adapter->scsi_host) != FC_PORTTYPE_NPORT && + fc_host_port_type(adapter->scsi_host) != FC_PORTTYPE_NPIV) + return; + + fc_req = kmem_cache_zalloc(zfcp_fc_req_cache, GFP_KERNEL); + if (!fc_req) + return; + + ret = zfcp_fc_wka_port_get(&adapter->gs->ds); + if (ret) + goto out_free; + + ret = zfcp_fc_gspn(adapter, fc_req); + if (ret || fc_host_port_type(adapter->scsi_host) != FC_PORTTYPE_NPIV) + goto out_ds_put; + + memset(fc_req, 0, sizeof(*fc_req)); + zfcp_fc_rspn(adapter, fc_req); + +out_ds_put: + zfcp_fc_wka_port_put(&adapter->gs->ds); +out_free: + kmem_cache_free(zfcp_fc_req_cache, fc_req); +} + static void zfcp_fc_ct_els_job_handler(void *data) { struct fc_bsg_job *job = data; diff --git a/drivers/s390/scsi/zfcp_fc.h b/drivers/s390/scsi/zfcp_fc.h index b464ae01086c..4561f3bf7300 100644 --- a/drivers/s390/scsi/zfcp_fc.h +++ b/drivers/s390/scsi/zfcp_fc.h @@ -64,33 +64,16 @@ struct zfcp_fc_gid_pn_req { } __packed; /** - * struct zfcp_fc_gid_pn_resp - container for ct header plus gid_pn response + * struct zfcp_fc_gid_pn_rsp - container for ct header plus gid_pn response * @ct_hdr: FC GS common transport header * @gid_pn: GID_PN response */ -struct zfcp_fc_gid_pn_resp { +struct zfcp_fc_gid_pn_rsp { struct fc_ct_hdr ct_hdr; struct fc_gid_pn_resp gid_pn; } __packed; /** - * struct zfcp_fc_gid_pn - everything required in zfcp for gid_pn request - * @ct: data passed to zfcp_fsf for issuing fsf request - * @sg_req: scatterlist entry for request data - * @sg_resp: scatterlist entry for response data - * @gid_pn_req: GID_PN request data - * @gid_pn_resp: GID_PN response data - */ -struct zfcp_fc_gid_pn { - struct zfcp_fsf_ct_els ct; - struct scatterlist sg_req; - struct scatterlist sg_resp; - struct zfcp_fc_gid_pn_req gid_pn_req; - struct zfcp_fc_gid_pn_resp gid_pn_resp; - struct zfcp_port *port; -}; - -/** * struct zfcp_fc_gpn_ft - container for ct header plus gpn_ft request * @ct_hdr: FC GS common transport header * @gpn_ft: GPN_FT request @@ -101,41 +84,72 @@ struct zfcp_fc_gpn_ft_req { } __packed; /** - * struct zfcp_fc_gpn_ft_resp - container for ct header plus gpn_ft response + * struct zfcp_fc_gspn_req - container for ct header plus GSPN_ID request * @ct_hdr: FC GS common transport header - * @gpn_ft: Array of gpn_ft response data to fill one memory page + * @gspn: GSPN_ID request */ -struct zfcp_fc_gpn_ft_resp { +struct zfcp_fc_gspn_req { struct fc_ct_hdr ct_hdr; - struct fc_gpn_ft_resp gpn_ft[ZFCP_FC_GPN_FT_ENT_PAGE]; + struct fc_gid_pn_resp gspn; } __packed; /** - * struct zfcp_fc_gpn_ft - zfcp data for gpn_ft request - * @ct: data passed to zfcp_fsf for issuing fsf request - * @sg_req: scatter list entry for gpn_ft request - * @sg_resp: scatter list entries for gpn_ft responses (per memory page) + * struct zfcp_fc_gspn_rsp - container for ct header plus GSPN_ID response + * @ct_hdr: FC GS common transport header + * @gspn: GSPN_ID response + * @name: The name string of the GSPN_ID response */ -struct zfcp_fc_gpn_ft { - struct zfcp_fsf_ct_els ct; - struct scatterlist sg_req; - struct scatterlist sg_resp[ZFCP_FC_GPN_FT_NUM_BUFS]; -}; +struct zfcp_fc_gspn_rsp { + struct fc_ct_hdr ct_hdr; + struct fc_gspn_resp gspn; + char name[FC_SYMBOLIC_NAME_SIZE]; +} __packed; /** - * struct zfcp_fc_els_adisc - everything required in zfcp for issuing ELS ADISC - * @els: data required for issuing els fsf command - * @req: scatterlist entry for ELS ADISC request - * @resp: scatterlist entry for ELS ADISC response - * @adisc_req: ELS ADISC request data - * @adisc_resp: ELS ADISC response data + * struct zfcp_fc_rspn_req - container for ct header plus RSPN_ID request + * @ct_hdr: FC GS common transport header + * @rspn: RSPN_ID request + * @name: The name string of the RSPN_ID request */ -struct zfcp_fc_els_adisc { - struct zfcp_fsf_ct_els els; - struct scatterlist req; - struct scatterlist resp; - struct fc_els_adisc adisc_req; - struct fc_els_adisc adisc_resp; +struct zfcp_fc_rspn_req { + struct fc_ct_hdr ct_hdr; + struct fc_ns_rspn rspn; + char name[FC_SYMBOLIC_NAME_SIZE]; +} __packed; + +/** + * struct zfcp_fc_req - Container for FC ELS and CT requests sent from zfcp + * @ct_els: data required for issuing fsf command + * @sg_req: scatterlist entry for request data + * @sg_rsp: scatterlist entry for response data + * @u: request specific data + */ +struct zfcp_fc_req { + struct zfcp_fsf_ct_els ct_els; + struct scatterlist sg_req; + struct scatterlist sg_rsp; + union { + struct { + struct fc_els_adisc req; + struct fc_els_adisc rsp; + } adisc; + struct { + struct zfcp_fc_gid_pn_req req; + struct zfcp_fc_gid_pn_rsp rsp; + } gid_pn; + struct { + struct scatterlist sg_rsp2[ZFCP_FC_GPN_FT_NUM_BUFS - 1]; + struct zfcp_fc_gpn_ft_req req; + } gpn_ft; + struct { + struct zfcp_fc_gspn_req req; + struct zfcp_fc_gspn_rsp rsp; + } gspn; + struct { + struct zfcp_fc_rspn_req req; + struct fc_ct_hdr rsp; + } rspn; + } u; }; /** @@ -192,14 +206,21 @@ struct zfcp_fc_wka_ports { * zfcp_fc_scsi_to_fcp - setup FCP command with data from scsi_cmnd * @fcp: fcp_cmnd to setup * @scsi: scsi_cmnd where to get LUN, task attributes/flags and CDB + * @tm: task management flags to setup task management command */ static inline -void zfcp_fc_scsi_to_fcp(struct fcp_cmnd *fcp, struct scsi_cmnd *scsi) +void zfcp_fc_scsi_to_fcp(struct fcp_cmnd *fcp, struct scsi_cmnd *scsi, + u8 tm_flags) { char tag[2]; int_to_scsilun(scsi->device->lun, (struct scsi_lun *) &fcp->fc_lun); + if (unlikely(tm_flags)) { + fcp->fc_tm_flags = tm_flags; + return; + } + if (scsi_populate_tag_msg(scsi, tag)) { switch (tag[0]) { case MSG_ORDERED_TAG: @@ -226,19 +247,6 @@ void zfcp_fc_scsi_to_fcp(struct fcp_cmnd *fcp, struct scsi_cmnd *scsi) } /** - * zfcp_fc_fcp_tm - setup FCP command as task management command - * @fcp: fcp_cmnd to setup - * @dev: scsi_device where to send the task management command - * @tm: task management flags to setup tm command - */ -static inline -void zfcp_fc_fcp_tm(struct fcp_cmnd *fcp, struct scsi_device *dev, u8 tm_flags) -{ - int_to_scsilun(dev->lun, (struct scsi_lun *) &fcp->fc_lun); - fcp->fc_tm_flags |= tm_flags; -} - -/** * zfcp_fc_evap_fcp_rsp - evaluate FCP RSP IU and update scsi_cmnd accordingly * @fcp_rsp: FCP RSP IU to evaluate * @scsi: SCSI command where to update status and sense buffer diff --git a/drivers/s390/scsi/zfcp_fsf.c b/drivers/s390/scsi/zfcp_fsf.c index 60ff9d172c79..a0e05ef65924 100644 --- a/drivers/s390/scsi/zfcp_fsf.c +++ b/drivers/s390/scsi/zfcp_fsf.c @@ -18,6 +18,8 @@ #include "zfcp_qdio.h" #include "zfcp_reqlist.h" +struct kmem_cache *zfcp_fsf_qtcb_cache; + static void zfcp_fsf_request_timeout_handler(unsigned long data) { struct zfcp_adapter *adapter = (struct zfcp_adapter *) data; @@ -83,7 +85,7 @@ void zfcp_fsf_req_free(struct zfcp_fsf_req *req) } if (likely(req->qtcb)) - kmem_cache_free(zfcp_data.qtcb_cache, req->qtcb); + kmem_cache_free(zfcp_fsf_qtcb_cache, req->qtcb); kfree(req); } @@ -212,7 +214,7 @@ static void zfcp_fsf_status_read_handler(struct zfcp_fsf_req *req) if (req->status & ZFCP_STATUS_FSFREQ_DISMISSED) { zfcp_dbf_hba_fsf_uss("fssrh_1", req); - mempool_free(sr_buf, adapter->pool.status_read_data); + mempool_free(virt_to_page(sr_buf), adapter->pool.sr_data); zfcp_fsf_req_free(req); return; } @@ -265,7 +267,7 @@ static void zfcp_fsf_status_read_handler(struct zfcp_fsf_req *req) break; } - mempool_free(sr_buf, adapter->pool.status_read_data); + mempool_free(virt_to_page(sr_buf), adapter->pool.sr_data); zfcp_fsf_req_free(req); atomic_inc(&adapter->stat_miss); @@ -628,7 +630,7 @@ static struct fsf_qtcb *zfcp_qtcb_alloc(mempool_t *pool) if (likely(pool)) qtcb = mempool_alloc(pool, GFP_ATOMIC); else - qtcb = kmem_cache_alloc(zfcp_data.qtcb_cache, GFP_ATOMIC); + qtcb = kmem_cache_alloc(zfcp_fsf_qtcb_cache, GFP_ATOMIC); if (unlikely(!qtcb)) return NULL; @@ -723,6 +725,7 @@ int zfcp_fsf_status_read(struct zfcp_qdio *qdio) struct zfcp_adapter *adapter = qdio->adapter; struct zfcp_fsf_req *req; struct fsf_status_read_buffer *sr_buf; + struct page *page; int retval = -EIO; spin_lock_irq(&qdio->req_q_lock); @@ -736,11 +739,12 @@ int zfcp_fsf_status_read(struct zfcp_qdio *qdio) goto out; } - sr_buf = mempool_alloc(adapter->pool.status_read_data, GFP_ATOMIC); - if (!sr_buf) { + page = mempool_alloc(adapter->pool.sr_data, GFP_ATOMIC); + if (!page) { retval = -ENOMEM; goto failed_buf; } + sr_buf = page_address(page); memset(sr_buf, 0, sizeof(*sr_buf)); req->data = sr_buf; @@ -755,7 +759,7 @@ int zfcp_fsf_status_read(struct zfcp_qdio *qdio) failed_req_send: req->data = NULL; - mempool_free(sr_buf, adapter->pool.status_read_data); + mempool_free(virt_to_page(sr_buf), adapter->pool.sr_data); failed_buf: zfcp_dbf_hba_fsf_uss("fssr__1", req); zfcp_fsf_req_free(req); @@ -1552,7 +1556,7 @@ int zfcp_fsf_open_wka_port(struct zfcp_fc_wka_port *wka_port) SBAL_FLAGS0_TYPE_READ, qdio->adapter->pool.erp_req); - if (unlikely(IS_ERR(req))) { + if (IS_ERR(req)) { retval = PTR_ERR(req); goto out; } @@ -1605,7 +1609,7 @@ int zfcp_fsf_close_wka_port(struct zfcp_fc_wka_port *wka_port) SBAL_FLAGS0_TYPE_READ, qdio->adapter->pool.erp_req); - if (unlikely(IS_ERR(req))) { + if (IS_ERR(req)) { retval = PTR_ERR(req); goto out; } @@ -2206,7 +2210,7 @@ int zfcp_fsf_fcp_cmnd(struct scsi_cmnd *scsi_cmnd) zfcp_fsf_set_data_dir(scsi_cmnd, &io->data_direction); fcp_cmnd = (struct fcp_cmnd *) &req->qtcb->bottom.io.fcp_cmnd; - zfcp_fc_scsi_to_fcp(fcp_cmnd, scsi_cmnd); + zfcp_fc_scsi_to_fcp(fcp_cmnd, scsi_cmnd, 0); if (scsi_prot_sg_count(scsi_cmnd)) { zfcp_qdio_set_data_div(qdio, &req->qdio_req, @@ -2284,7 +2288,6 @@ struct zfcp_fsf_req *zfcp_fsf_fcp_task_mgmt(struct scsi_cmnd *scmnd, goto out; } - req->status |= ZFCP_STATUS_FSFREQ_TASK_MANAGEMENT; req->data = scmnd; req->handler = zfcp_fsf_fcp_task_mgmt_handler; req->qtcb->header.lun_handle = zfcp_sdev->lun_handle; @@ -2296,7 +2299,7 @@ struct zfcp_fsf_req *zfcp_fsf_fcp_task_mgmt(struct scsi_cmnd *scmnd, zfcp_qdio_set_sbale_last(qdio, &req->qdio_req); fcp_cmnd = (struct fcp_cmnd *) &req->qtcb->bottom.io.fcp_cmnd; - zfcp_fc_fcp_tm(fcp_cmnd, scmnd->device, tm_flags); + zfcp_fc_scsi_to_fcp(fcp_cmnd, scmnd, tm_flags); zfcp_fsf_start_timer(req, ZFCP_SCSI_ER_TIMEOUT); if (!zfcp_fsf_req_send(req)) diff --git a/drivers/s390/scsi/zfcp_scsi.c b/drivers/s390/scsi/zfcp_scsi.c index ddb5800823a9..2a4991d6d4d5 100644 --- a/drivers/s390/scsi/zfcp_scsi.c +++ b/drivers/s390/scsi/zfcp_scsi.c @@ -292,7 +292,37 @@ static int zfcp_scsi_eh_host_reset_handler(struct scsi_cmnd *scpnt) return SUCCESS; } -int zfcp_adapter_scsi_register(struct zfcp_adapter *adapter) +struct scsi_transport_template *zfcp_scsi_transport_template; + +static struct scsi_host_template zfcp_scsi_host_template = { + .module = THIS_MODULE, + .name = "zfcp", + .queuecommand = zfcp_scsi_queuecommand, + .eh_abort_handler = zfcp_scsi_eh_abort_handler, + .eh_device_reset_handler = zfcp_scsi_eh_device_reset_handler, + .eh_target_reset_handler = zfcp_scsi_eh_target_reset_handler, + .eh_host_reset_handler = zfcp_scsi_eh_host_reset_handler, + .slave_alloc = zfcp_scsi_slave_alloc, + .slave_configure = zfcp_scsi_slave_configure, + .slave_destroy = zfcp_scsi_slave_destroy, + .change_queue_depth = zfcp_scsi_change_queue_depth, + .proc_name = "zfcp", + .can_queue = 4096, + .this_id = -1, + .sg_tablesize = ZFCP_QDIO_MAX_SBALES_PER_REQ, + .max_sectors = (ZFCP_QDIO_MAX_SBALES_PER_REQ * 8), + .dma_boundary = ZFCP_QDIO_SBALE_LEN - 1, + .cmd_per_lun = 1, + .use_clustering = 1, + .shost_attrs = zfcp_sysfs_shost_attrs, + .sdev_attrs = zfcp_sysfs_sdev_attrs, +}; + +/** + * zfcp_scsi_adapter_register - Register SCSI and FC host with SCSI midlayer + * @adapter: The zfcp adapter to register with the SCSI midlayer + */ +int zfcp_scsi_adapter_register(struct zfcp_adapter *adapter) { struct ccw_dev_id dev_id; @@ -301,7 +331,7 @@ int zfcp_adapter_scsi_register(struct zfcp_adapter *adapter) ccw_device_get_id(adapter->ccw_device, &dev_id); /* register adapter as SCSI host with mid layer of SCSI stack */ - adapter->scsi_host = scsi_host_alloc(&zfcp_data.scsi_host_template, + adapter->scsi_host = scsi_host_alloc(&zfcp_scsi_host_template, sizeof (struct zfcp_adapter *)); if (!adapter->scsi_host) { dev_err(&adapter->ccw_device->dev, @@ -316,7 +346,7 @@ int zfcp_adapter_scsi_register(struct zfcp_adapter *adapter) adapter->scsi_host->max_channel = 0; adapter->scsi_host->unique_id = dev_id.devno; adapter->scsi_host->max_cmd_len = 16; /* in struct fcp_cmnd */ - adapter->scsi_host->transportt = zfcp_data.scsi_transport_template; + adapter->scsi_host->transportt = zfcp_scsi_transport_template; adapter->scsi_host->hostdata[0] = (unsigned long) adapter; @@ -328,7 +358,11 @@ int zfcp_adapter_scsi_register(struct zfcp_adapter *adapter) return 0; } -void zfcp_adapter_scsi_unregister(struct zfcp_adapter *adapter) +/** + * zfcp_scsi_adapter_unregister - Unregister SCSI and FC host from SCSI midlayer + * @adapter: The zfcp adapter to unregister. + */ +void zfcp_scsi_adapter_unregister(struct zfcp_adapter *adapter) { struct Scsi_Host *shost; struct zfcp_port *port; @@ -346,8 +380,6 @@ void zfcp_adapter_scsi_unregister(struct zfcp_adapter *adapter) scsi_remove_host(shost); scsi_host_put(shost); adapter->scsi_host = NULL; - - return; } static struct fc_host_statistics* @@ -688,33 +720,8 @@ struct fc_function_template zfcp_transport_functions = { /* no functions registered for following dynamic attributes but directly set by LLDD */ .show_host_port_type = 1, + .show_host_symbolic_name = 1, .show_host_speed = 1, .show_host_port_id = 1, .dd_bsg_size = sizeof(struct zfcp_fsf_ct_els), }; - -struct zfcp_data zfcp_data = { - .scsi_host_template = { - .name = "zfcp", - .module = THIS_MODULE, - .proc_name = "zfcp", - .change_queue_depth = zfcp_scsi_change_queue_depth, - .slave_alloc = zfcp_scsi_slave_alloc, - .slave_configure = zfcp_scsi_slave_configure, - .slave_destroy = zfcp_scsi_slave_destroy, - .queuecommand = zfcp_scsi_queuecommand, - .eh_abort_handler = zfcp_scsi_eh_abort_handler, - .eh_device_reset_handler = zfcp_scsi_eh_device_reset_handler, - .eh_target_reset_handler = zfcp_scsi_eh_target_reset_handler, - .eh_host_reset_handler = zfcp_scsi_eh_host_reset_handler, - .can_queue = 4096, - .this_id = -1, - .sg_tablesize = ZFCP_QDIO_MAX_SBALES_PER_REQ, - .cmd_per_lun = 1, - .use_clustering = 1, - .sdev_attrs = zfcp_sysfs_sdev_attrs, - .max_sectors = (ZFCP_QDIO_MAX_SBALES_PER_REQ * 8), - .dma_boundary = ZFCP_QDIO_SBALE_LEN - 1, - .shost_attrs = zfcp_sysfs_shost_attrs, - }, -}; diff --git a/drivers/scsi/NCR5380.c b/drivers/scsi/NCR5380.c index 9a5629f94f95..e7cd2fcbe036 100644 --- a/drivers/scsi/NCR5380.c +++ b/drivers/scsi/NCR5380.c @@ -936,8 +936,7 @@ static void NCR5380_exit(struct Scsi_Host *instance) { struct NCR5380_hostdata *hostdata = (struct NCR5380_hostdata *) instance->hostdata; - cancel_delayed_work(&hostdata->coroutine); - flush_scheduled_work(); + cancel_delayed_work_sync(&hostdata->coroutine); } /** diff --git a/drivers/scsi/arcmsr/arcmsr_hba.c b/drivers/scsi/arcmsr/arcmsr_hba.c index 984bd527c6c9..da7b9887ec48 100644 --- a/drivers/scsi/arcmsr/arcmsr_hba.c +++ b/drivers/scsi/arcmsr/arcmsr_hba.c @@ -1020,7 +1020,7 @@ static void arcmsr_remove(struct pci_dev *pdev) int poll_count = 0; arcmsr_free_sysfs_attr(acb); scsi_remove_host(host); - flush_scheduled_work(); + flush_work_sync(&acb->arcmsr_do_message_isr_bh); del_timer_sync(&acb->eternal_timer); arcmsr_disable_outbound_ints(acb); arcmsr_stop_adapter_bgrb(acb); @@ -1066,7 +1066,7 @@ static void arcmsr_shutdown(struct pci_dev *pdev) (struct AdapterControlBlock *)host->hostdata; del_timer_sync(&acb->eternal_timer); arcmsr_disable_outbound_ints(acb); - flush_scheduled_work(); + flush_work_sync(&acb->arcmsr_do_message_isr_bh); arcmsr_stop_adapter_bgrb(acb); arcmsr_flush_adapter_cache(acb); } diff --git a/drivers/scsi/be2iscsi/be_iscsi.c b/drivers/scsi/be2iscsi/be_iscsi.c index eaaa8813067d..868cc5590145 100644 --- a/drivers/scsi/be2iscsi/be_iscsi.c +++ b/drivers/scsi/be2iscsi/be_iscsi.c @@ -210,28 +210,20 @@ int beiscsi_conn_bind(struct iscsi_cls_session *cls_session, } /** - * beiscsi_conn_get_param - get the iscsi parameter - * @cls_conn: pointer to iscsi cls conn + * beiscsi_ep_get_param - get the iscsi parameter + * @ep: pointer to iscsi ep * @param: parameter type identifier * @buf: buffer pointer * * returns iscsi parameter */ -int beiscsi_conn_get_param(struct iscsi_cls_conn *cls_conn, +int beiscsi_ep_get_param(struct iscsi_endpoint *ep, enum iscsi_param param, char *buf) { - struct beiscsi_endpoint *beiscsi_ep; - struct iscsi_conn *conn = cls_conn->dd_data; - struct beiscsi_conn *beiscsi_conn = conn->dd_data; + struct beiscsi_endpoint *beiscsi_ep = ep->dd_data; int len = 0; SE_DEBUG(DBG_LVL_8, "In beiscsi_conn_get_param, param= %d\n", param); - beiscsi_ep = beiscsi_conn->ep; - if (!beiscsi_ep) { - SE_DEBUG(DBG_LVL_1, - "In beiscsi_conn_get_param , no beiscsi_ep\n"); - return -ENODEV; - } switch (param) { case ISCSI_PARAM_CONN_PORT: @@ -244,7 +236,7 @@ int beiscsi_conn_get_param(struct iscsi_cls_conn *cls_conn, len = sprintf(buf, "%pI6\n", &beiscsi_ep->dst6_addr); break; default: - return iscsi_conn_get_param(cls_conn, param, buf); + return -ENOSYS; } return len; } diff --git a/drivers/scsi/be2iscsi/be_iscsi.h b/drivers/scsi/be2iscsi/be_iscsi.h index 8950a702b9f4..9c532797c29e 100644 --- a/drivers/scsi/be2iscsi/be_iscsi.h +++ b/drivers/scsi/be2iscsi/be_iscsi.h @@ -48,8 +48,8 @@ int beiscsi_conn_bind(struct iscsi_cls_session *cls_session, struct iscsi_cls_conn *cls_conn, uint64_t transport_fd, int is_leading); -int beiscsi_conn_get_param(struct iscsi_cls_conn *cls_conn, - enum iscsi_param param, char *buf); +int beiscsi_ep_get_param(struct iscsi_endpoint *ep, enum iscsi_param param, + char *buf); int beiscsi_get_host_param(struct Scsi_Host *shost, enum iscsi_host_param param, char *buf); diff --git a/drivers/scsi/be2iscsi/be_main.c b/drivers/scsi/be2iscsi/be_main.c index 79cefbe31367..bd50145ab71b 100644 --- a/drivers/scsi/be2iscsi/be_main.c +++ b/drivers/scsi/be2iscsi/be_main.c @@ -4384,7 +4384,7 @@ struct iscsi_transport beiscsi_iscsi_transport = { .bind_conn = beiscsi_conn_bind, .destroy_conn = iscsi_conn_teardown, .set_param = beiscsi_set_param, - .get_conn_param = beiscsi_conn_get_param, + .get_conn_param = iscsi_conn_get_param, .get_session_param = iscsi_session_get_param, .get_host_param = beiscsi_get_host_param, .start_conn = beiscsi_conn_start, @@ -4395,6 +4395,7 @@ struct iscsi_transport beiscsi_iscsi_transport = { .alloc_pdu = beiscsi_alloc_pdu, .parse_pdu_itt = beiscsi_parse_pdu, .get_stats = beiscsi_conn_get_stats, + .get_ep_param = beiscsi_ep_get_param, .ep_connect = beiscsi_ep_connect, .ep_poll = beiscsi_ep_poll, .ep_disconnect = beiscsi_ep_disconnect, diff --git a/drivers/scsi/bnx2i/bnx2i.h b/drivers/scsi/bnx2i/bnx2i.h index e1ca5fe7e6bb..cfd59023227b 100644 --- a/drivers/scsi/bnx2i/bnx2i.h +++ b/drivers/scsi/bnx2i/bnx2i.h @@ -360,7 +360,7 @@ struct bnx2i_hba { #define ADAPTER_STATE_LINK_DOWN 2 #define ADAPTER_STATE_INIT_FAILED 31 unsigned int mtu_supported; - #define BNX2I_MAX_MTU_SUPPORTED 1500 + #define BNX2I_MAX_MTU_SUPPORTED 9000 struct Scsi_Host *shost; @@ -751,6 +751,8 @@ extern int bnx2i_send_iscsi_login(struct bnx2i_conn *conn, struct iscsi_task *mtask); extern int bnx2i_send_iscsi_tmf(struct bnx2i_conn *conn, struct iscsi_task *mtask); +extern int bnx2i_send_iscsi_text(struct bnx2i_conn *conn, + struct iscsi_task *mtask); extern int bnx2i_send_iscsi_scsicmd(struct bnx2i_conn *conn, struct bnx2i_cmd *cmnd); extern int bnx2i_send_iscsi_nopout(struct bnx2i_conn *conn, diff --git a/drivers/scsi/bnx2i/bnx2i_hwi.c b/drivers/scsi/bnx2i/bnx2i_hwi.c index 96505e3ab986..1da34c019b8a 100644 --- a/drivers/scsi/bnx2i/bnx2i_hwi.c +++ b/drivers/scsi/bnx2i/bnx2i_hwi.c @@ -445,6 +445,56 @@ int bnx2i_send_iscsi_tmf(struct bnx2i_conn *bnx2i_conn, } /** + * bnx2i_send_iscsi_text - post iSCSI text WQE to hardware + * @conn: iscsi connection + * @mtask: driver command structure which is requesting + * a WQE to sent to chip for further processing + * + * prepare and post an iSCSI Text request WQE to CNIC firmware + */ +int bnx2i_send_iscsi_text(struct bnx2i_conn *bnx2i_conn, + struct iscsi_task *mtask) +{ + struct bnx2i_cmd *bnx2i_cmd; + struct bnx2i_text_request *text_wqe; + struct iscsi_text *text_hdr; + u32 dword; + + bnx2i_cmd = (struct bnx2i_cmd *)mtask->dd_data; + text_hdr = (struct iscsi_text *)mtask->hdr; + text_wqe = (struct bnx2i_text_request *) bnx2i_conn->ep->qp.sq_prod_qe; + + memset(text_wqe, 0, sizeof(struct bnx2i_text_request)); + + text_wqe->op_code = text_hdr->opcode; + text_wqe->op_attr = text_hdr->flags; + text_wqe->data_length = ntoh24(text_hdr->dlength); + text_wqe->itt = mtask->itt | + (ISCSI_TASK_TYPE_MPATH << ISCSI_TEXT_REQUEST_TYPE_SHIFT); + text_wqe->ttt = be32_to_cpu(text_hdr->ttt); + + text_wqe->cmd_sn = be32_to_cpu(text_hdr->cmdsn); + + text_wqe->resp_bd_list_addr_lo = (u32) bnx2i_conn->gen_pdu.resp_bd_dma; + text_wqe->resp_bd_list_addr_hi = + (u32) ((u64) bnx2i_conn->gen_pdu.resp_bd_dma >> 32); + + dword = ((1 << ISCSI_TEXT_REQUEST_NUM_RESP_BDS_SHIFT) | + (bnx2i_conn->gen_pdu.resp_buf_size << + ISCSI_TEXT_REQUEST_RESP_BUFFER_LENGTH_SHIFT)); + text_wqe->resp_buffer = dword; + text_wqe->bd_list_addr_lo = (u32) bnx2i_conn->gen_pdu.req_bd_dma; + text_wqe->bd_list_addr_hi = + (u32) ((u64) bnx2i_conn->gen_pdu.req_bd_dma >> 32); + text_wqe->num_bds = 1; + text_wqe->cq_index = 0; /* CQ# used for completion, 5771x only */ + + bnx2i_ring_dbell_update_sq_params(bnx2i_conn, 1); + return 0; +} + + +/** * bnx2i_send_iscsi_scsicmd - post iSCSI scsicmd request WQE to hardware * @conn: iscsi connection * @cmd: driver command structure which is requesting @@ -490,15 +540,18 @@ int bnx2i_send_iscsi_nopout(struct bnx2i_conn *bnx2i_conn, bnx2i_cmd = (struct bnx2i_cmd *)task->dd_data; nopout_hdr = (struct iscsi_nopout *)task->hdr; nopout_wqe = (struct bnx2i_nop_out_request *)ep->qp.sq_prod_qe; + + memset(nopout_wqe, 0x00, sizeof(struct bnx2i_nop_out_request)); + nopout_wqe->op_code = nopout_hdr->opcode; nopout_wqe->op_attr = ISCSI_FLAG_CMD_FINAL; memcpy(nopout_wqe->lun, nopout_hdr->lun, 8); if (test_bit(BNX2I_NX2_DEV_57710, &ep->hba->cnic_dev_type)) { - u32 tmp = nopout_hdr->lun[0]; + u32 tmp = nopout_wqe->lun[0]; /* 57710 requires LUN field to be swapped */ - nopout_hdr->lun[0] = nopout_hdr->lun[1]; - nopout_hdr->lun[1] = tmp; + nopout_wqe->lun[0] = nopout_wqe->lun[1]; + nopout_wqe->lun[1] = tmp; } nopout_wqe->itt = ((u16)task->itt | @@ -1425,6 +1478,68 @@ done: return 0; } + +/** + * bnx2i_process_text_resp - this function handles iscsi text response + * @session: iscsi session pointer + * @bnx2i_conn: iscsi connection pointer + * @cqe: pointer to newly DMA'ed CQE entry for processing + * + * process iSCSI Text Response CQE& complete it to open-iscsi user daemon + */ +static int bnx2i_process_text_resp(struct iscsi_session *session, + struct bnx2i_conn *bnx2i_conn, + struct cqe *cqe) +{ + struct iscsi_conn *conn = bnx2i_conn->cls_conn->dd_data; + struct iscsi_task *task; + struct bnx2i_text_response *text; + struct iscsi_text_rsp *resp_hdr; + int pld_len; + int pad_len; + + text = (struct bnx2i_text_response *) cqe; + spin_lock(&session->lock); + task = iscsi_itt_to_task(conn, text->itt & ISCSI_LOGIN_RESPONSE_INDEX); + if (!task) + goto done; + + resp_hdr = (struct iscsi_text_rsp *)&bnx2i_conn->gen_pdu.resp_hdr; + memset(resp_hdr, 0, sizeof(struct iscsi_hdr)); + resp_hdr->opcode = text->op_code; + resp_hdr->flags = text->response_flags; + resp_hdr->hlength = 0; + + hton24(resp_hdr->dlength, text->data_length); + resp_hdr->itt = task->hdr->itt; + resp_hdr->ttt = cpu_to_be32(text->ttt); + resp_hdr->statsn = task->hdr->exp_statsn; + resp_hdr->exp_cmdsn = cpu_to_be32(text->exp_cmd_sn); + resp_hdr->max_cmdsn = cpu_to_be32(text->max_cmd_sn); + pld_len = text->data_length; + bnx2i_conn->gen_pdu.resp_wr_ptr = bnx2i_conn->gen_pdu.resp_buf + + pld_len; + pad_len = 0; + if (pld_len & 0x3) + pad_len = 4 - (pld_len % 4); + + if (pad_len) { + int i = 0; + for (i = 0; i < pad_len; i++) { + bnx2i_conn->gen_pdu.resp_wr_ptr[0] = 0; + bnx2i_conn->gen_pdu.resp_wr_ptr++; + } + } + __iscsi_complete_pdu(conn, (struct iscsi_hdr *)resp_hdr, + bnx2i_conn->gen_pdu.resp_buf, + bnx2i_conn->gen_pdu.resp_wr_ptr - + bnx2i_conn->gen_pdu.resp_buf); +done: + spin_unlock(&session->lock); + return 0; +} + + /** * bnx2i_process_tmf_resp - this function handles iscsi TMF response * @session: iscsi session pointer @@ -1766,6 +1881,10 @@ static void bnx2i_process_new_cqes(struct bnx2i_conn *bnx2i_conn) bnx2i_process_tmf_resp(session, bnx2i_conn, qp->cq_cons_qe); break; + case ISCSI_OP_TEXT_RSP: + bnx2i_process_text_resp(session, bnx2i_conn, + qp->cq_cons_qe); + break; case ISCSI_OP_LOGOUT_RSP: bnx2i_process_logout_resp(session, bnx2i_conn, qp->cq_cons_qe); diff --git a/drivers/scsi/bnx2i/bnx2i_init.c b/drivers/scsi/bnx2i/bnx2i_init.c index 72a7b2d4a439..1d24a2819736 100644 --- a/drivers/scsi/bnx2i/bnx2i_init.c +++ b/drivers/scsi/bnx2i/bnx2i_init.c @@ -18,8 +18,8 @@ static struct list_head adapter_list = LIST_HEAD_INIT(adapter_list); static u32 adapter_count; #define DRV_MODULE_NAME "bnx2i" -#define DRV_MODULE_VERSION "2.6.2.2" -#define DRV_MODULE_RELDATE "Nov 23, 2010" +#define DRV_MODULE_VERSION "2.6.2.3" +#define DRV_MODULE_RELDATE "Dec 31, 2010" static char version[] __devinitdata = "Broadcom NetXtreme II iSCSI Driver " DRV_MODULE_NAME \ @@ -29,7 +29,7 @@ static char version[] __devinitdata = MODULE_AUTHOR("Anil Veerabhadrappa <anilgv@broadcom.com> and " "Eddie Wai <eddie.wai@broadcom.com>"); -MODULE_DESCRIPTION("Broadcom NetXtreme II BCM5706/5708/5709/57710/57711" +MODULE_DESCRIPTION("Broadcom NetXtreme II BCM5706/5708/5709/57710/57711/57712" " iSCSI Driver"); MODULE_LICENSE("GPL"); MODULE_VERSION(DRV_MODULE_VERSION); @@ -88,9 +88,11 @@ void bnx2i_identify_device(struct bnx2i_hba *hba) (hba->pci_did == PCI_DEVICE_ID_NX2_5709S)) { set_bit(BNX2I_NX2_DEV_5709, &hba->cnic_dev_type); hba->mail_queue_access = BNX2I_MQ_BIN_MODE; - } else if (hba->pci_did == PCI_DEVICE_ID_NX2_57710 || - hba->pci_did == PCI_DEVICE_ID_NX2_57711 || - hba->pci_did == PCI_DEVICE_ID_NX2_57711E) + } else if (hba->pci_did == PCI_DEVICE_ID_NX2_57710 || + hba->pci_did == PCI_DEVICE_ID_NX2_57711 || + hba->pci_did == PCI_DEVICE_ID_NX2_57711E || + hba->pci_did == PCI_DEVICE_ID_NX2_57712 || + hba->pci_did == PCI_DEVICE_ID_NX2_57712E) set_bit(BNX2I_NX2_DEV_57710, &hba->cnic_dev_type); else printk(KERN_ALERT "bnx2i: unknown device, 0x%x\n", @@ -161,6 +163,21 @@ void bnx2i_start(void *handle) struct bnx2i_hba *hba = handle; int i = HZ; + if (!hba->cnic->max_iscsi_conn) { + printk(KERN_ALERT "bnx2i: dev %s does not support " + "iSCSI\n", hba->netdev->name); + + if (test_bit(BNX2I_CNIC_REGISTERED, &hba->reg_with_cnic)) { + mutex_lock(&bnx2i_dev_lock); + list_del_init(&hba->link); + adapter_count--; + hba->cnic->unregister_device(hba->cnic, CNIC_ULP_ISCSI); + clear_bit(BNX2I_CNIC_REGISTERED, &hba->reg_with_cnic); + mutex_unlock(&bnx2i_dev_lock); + bnx2i_free_hba(hba); + } + return; + } bnx2i_send_fw_iscsi_init_msg(hba); while (!test_bit(ADAPTER_STATE_UP, &hba->adapter_state) && i--) msleep(BNX2I_INIT_POLL_TIME); diff --git a/drivers/scsi/bnx2i/bnx2i_iscsi.c b/drivers/scsi/bnx2i/bnx2i_iscsi.c index f0dce26593eb..1809f9ccc4ce 100644 --- a/drivers/scsi/bnx2i/bnx2i_iscsi.c +++ b/drivers/scsi/bnx2i/bnx2i_iscsi.c @@ -1092,6 +1092,9 @@ static int bnx2i_iscsi_send_generic_request(struct iscsi_task *task) case ISCSI_OP_SCSI_TMFUNC: rc = bnx2i_send_iscsi_tmf(bnx2i_conn, task); break; + case ISCSI_OP_TEXT: + rc = bnx2i_send_iscsi_text(bnx2i_conn, task); + break; default: iscsi_conn_printk(KERN_ALERT, bnx2i_conn->cls_conn->dd_data, "send_gen: unsupported op 0x%x\n", @@ -1455,42 +1458,40 @@ static void bnx2i_conn_destroy(struct iscsi_cls_conn *cls_conn) /** - * bnx2i_conn_get_param - return iscsi connection parameter to caller - * @cls_conn: pointer to iscsi cls conn + * bnx2i_ep_get_param - return iscsi ep parameter to caller + * @ep: pointer to iscsi endpoint * @param: parameter type identifier * @buf: buffer pointer * - * returns iSCSI connection parameters + * returns iSCSI ep parameters */ -static int bnx2i_conn_get_param(struct iscsi_cls_conn *cls_conn, - enum iscsi_param param, char *buf) +static int bnx2i_ep_get_param(struct iscsi_endpoint *ep, + enum iscsi_param param, char *buf) { - struct iscsi_conn *conn = cls_conn->dd_data; - struct bnx2i_conn *bnx2i_conn = conn->dd_data; - int len = 0; + struct bnx2i_endpoint *bnx2i_ep = ep->dd_data; + struct bnx2i_hba *hba = bnx2i_ep->hba; + int len = -ENOTCONN; - if (!(bnx2i_conn && bnx2i_conn->ep && bnx2i_conn->ep->hba)) - goto out; + if (!hba) + return -ENOTCONN; switch (param) { case ISCSI_PARAM_CONN_PORT: - mutex_lock(&bnx2i_conn->ep->hba->net_dev_lock); - if (bnx2i_conn->ep->cm_sk) - len = sprintf(buf, "%hu\n", - bnx2i_conn->ep->cm_sk->dst_port); - mutex_unlock(&bnx2i_conn->ep->hba->net_dev_lock); + mutex_lock(&hba->net_dev_lock); + if (bnx2i_ep->cm_sk) + len = sprintf(buf, "%hu\n", bnx2i_ep->cm_sk->dst_port); + mutex_unlock(&hba->net_dev_lock); break; case ISCSI_PARAM_CONN_ADDRESS: - mutex_lock(&bnx2i_conn->ep->hba->net_dev_lock); - if (bnx2i_conn->ep->cm_sk) - len = sprintf(buf, "%pI4\n", - &bnx2i_conn->ep->cm_sk->dst_ip); - mutex_unlock(&bnx2i_conn->ep->hba->net_dev_lock); + mutex_lock(&hba->net_dev_lock); + if (bnx2i_ep->cm_sk) + len = sprintf(buf, "%pI4\n", &bnx2i_ep->cm_sk->dst_ip); + mutex_unlock(&hba->net_dev_lock); break; default: - return iscsi_conn_get_param(cls_conn, param, buf); + return -ENOSYS; } -out: + return len; } @@ -1935,13 +1936,13 @@ static int bnx2i_ep_tcp_conn_active(struct bnx2i_endpoint *bnx2i_ep) cnic_dev_10g = 1; switch (bnx2i_ep->state) { - case EP_STATE_CONNECT_FAILED: case EP_STATE_CLEANUP_FAILED: case EP_STATE_OFLD_FAILED: case EP_STATE_DISCONN_TIMEDOUT: ret = 0; break; case EP_STATE_CONNECT_START: + case EP_STATE_CONNECT_FAILED: case EP_STATE_CONNECT_COMPL: case EP_STATE_ULP_UPDATE_START: case EP_STATE_ULP_UPDATE_COMPL: @@ -2167,7 +2168,8 @@ struct iscsi_transport bnx2i_iscsi_transport = { .name = "bnx2i", .caps = CAP_RECOVERY_L0 | CAP_HDRDGST | CAP_MULTI_R2T | CAP_DATADGST | - CAP_DATA_PATH_OFFLOAD, + CAP_DATA_PATH_OFFLOAD | + CAP_TEXT_NEGO, .param_mask = ISCSI_MAX_RECV_DLENGTH | ISCSI_MAX_XMIT_DLENGTH | ISCSI_HDRDGST_EN | @@ -2200,7 +2202,7 @@ struct iscsi_transport bnx2i_iscsi_transport = { .bind_conn = bnx2i_conn_bind, .destroy_conn = bnx2i_conn_destroy, .set_param = iscsi_set_param, - .get_conn_param = bnx2i_conn_get_param, + .get_conn_param = iscsi_conn_get_param, .get_session_param = iscsi_session_get_param, .get_host_param = bnx2i_host_get_param, .start_conn = bnx2i_conn_start, @@ -2209,6 +2211,7 @@ struct iscsi_transport bnx2i_iscsi_transport = { .xmit_task = bnx2i_task_xmit, .get_stats = bnx2i_conn_get_stats, /* TCP connect - disconnect - option-2 interface calls */ + .get_ep_param = bnx2i_ep_get_param, .ep_connect = bnx2i_ep_connect, .ep_poll = bnx2i_ep_poll, .ep_disconnect = bnx2i_ep_disconnect, diff --git a/drivers/scsi/cxgbi/cxgb3i/cxgb3i.c b/drivers/scsi/cxgbi/cxgb3i/cxgb3i.c index a129a170b47b..fc2cdb62f53b 100644 --- a/drivers/scsi/cxgbi/cxgb3i/cxgb3i.c +++ b/drivers/scsi/cxgbi/cxgb3i/cxgb3i.c @@ -105,7 +105,7 @@ static struct iscsi_transport cxgb3i_iscsi_transport = { /* owner and name should be set already */ .caps = CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST | CAP_DATADGST | CAP_DIGEST_OFFLOAD | - CAP_PADDING_OFFLOAD, + CAP_PADDING_OFFLOAD | CAP_TEXT_NEGO, .param_mask = ISCSI_MAX_RECV_DLENGTH | ISCSI_MAX_XMIT_DLENGTH | ISCSI_HDRDGST_EN | ISCSI_DATADGST_EN | ISCSI_INITIAL_R2T_EN | ISCSI_MAX_R2T | @@ -137,7 +137,7 @@ static struct iscsi_transport cxgb3i_iscsi_transport = { .destroy_conn = iscsi_tcp_conn_teardown, .start_conn = iscsi_conn_start, .stop_conn = iscsi_conn_stop, - .get_conn_param = cxgbi_get_conn_param, + .get_conn_param = iscsi_conn_get_param, .set_param = cxgbi_set_conn_param, .get_stats = cxgbi_get_conn_stats, /* pdu xmit req from user space */ @@ -152,6 +152,7 @@ static struct iscsi_transport cxgb3i_iscsi_transport = { .xmit_pdu = cxgbi_conn_xmit_pdu, .parse_pdu_itt = cxgbi_parse_pdu_itt, /* TCP connect/disconnect */ + .get_ep_param = cxgbi_get_ep_param, .ep_connect = cxgbi_ep_connect, .ep_poll = cxgbi_ep_poll, .ep_disconnect = cxgbi_ep_disconnect, @@ -1108,10 +1109,11 @@ static int ddp_set_map(struct cxgbi_sock *csk, struct cxgbi_pagepod_hdr *hdr, csk, idx, npods, gl); for (i = 0; i < npods; i++, idx++, pm_addr += PPOD_SIZE) { - struct sk_buff *skb = ddp->gl_skb[idx]; + struct sk_buff *skb = alloc_wr(sizeof(struct ulp_mem_io) + + PPOD_SIZE, 0, GFP_ATOMIC); - /* hold on to the skb until we clear the ddp mapping */ - skb_get(skb); + if (!skb) + return -ENOMEM; ulp_mem_io_set_hdr(skb, pm_addr); cxgbi_ddp_ppod_set((struct cxgbi_pagepod *)(skb->head + @@ -1136,56 +1138,20 @@ static void ddp_clear_map(struct cxgbi_hba *chba, unsigned int tag, cdev, idx, npods, tag); for (i = 0; i < npods; i++, idx++, pm_addr += PPOD_SIZE) { - struct sk_buff *skb = ddp->gl_skb[idx]; + struct sk_buff *skb = alloc_wr(sizeof(struct ulp_mem_io) + + PPOD_SIZE, 0, GFP_ATOMIC); if (!skb) { - pr_err("tag 0x%x, 0x%x, %d/%u, skb NULL.\n", + pr_err("tag 0x%x, 0x%x, %d/%u, skb OOM.\n", tag, idx, i, npods); continue; } - ddp->gl_skb[idx] = NULL; - memset(skb->head + sizeof(struct ulp_mem_io), 0, PPOD_SIZE); ulp_mem_io_set_hdr(skb, pm_addr); skb->priority = CPL_PRIORITY_CONTROL; cxgb3_ofld_send(cdev->lldev, skb); } } -static void ddp_free_gl_skb(struct cxgbi_ddp_info *ddp, int idx, int cnt) -{ - int i; - - log_debug(1 << CXGBI_DBG_DDP, - "ddp 0x%p, idx %d, cnt %d.\n", ddp, idx, cnt); - - for (i = 0; i < cnt; i++, idx++) - if (ddp->gl_skb[idx]) { - kfree_skb(ddp->gl_skb[idx]); - ddp->gl_skb[idx] = NULL; - } -} - -static int ddp_alloc_gl_skb(struct cxgbi_ddp_info *ddp, int idx, - int cnt, gfp_t gfp) -{ - int i; - - log_debug(1 << CXGBI_DBG_DDP, - "ddp 0x%p, idx %d, cnt %d.\n", ddp, idx, cnt); - - for (i = 0; i < cnt; i++) { - struct sk_buff *skb = alloc_wr(sizeof(struct ulp_mem_io) + - PPOD_SIZE, 0, gfp); - if (skb) - ddp->gl_skb[idx + i] = skb; - else { - ddp_free_gl_skb(ddp, idx, i); - return -ENOMEM; - } - } - return 0; -} - static int ddp_setup_conn_pgidx(struct cxgbi_sock *csk, unsigned int tid, int pg_idx, bool reply) { @@ -1316,8 +1282,6 @@ static int cxgb3i_ddp_init(struct cxgbi_device *cdev) } tdev->ulp_iscsi = ddp; - cdev->csk_ddp_free_gl_skb = ddp_free_gl_skb; - cdev->csk_ddp_alloc_gl_skb = ddp_alloc_gl_skb; cdev->csk_ddp_setup_digest = ddp_setup_conn_digest; cdev->csk_ddp_setup_pgidx = ddp_setup_conn_pgidx; cdev->csk_ddp_set = ddp_set_map; diff --git a/drivers/scsi/cxgbi/cxgb3i/cxgb3i.h b/drivers/scsi/cxgbi/cxgb3i/cxgb3i.h index 5f5e3394b594..20593fd69d8f 100644 --- a/drivers/scsi/cxgbi/cxgb3i/cxgb3i.h +++ b/drivers/scsi/cxgbi/cxgb3i/cxgb3i.h @@ -24,10 +24,21 @@ extern cxgb3_cpl_handler_func cxgb3i_cpl_handlers[NUM_CPL_CMDS]; -#define cxgb3i_get_private_ipv4addr(ndev) \ - (((struct port_info *)(netdev_priv(ndev)))->iscsi_ipv4addr) -#define cxgb3i_set_private_ipv4addr(ndev, addr) \ - (((struct port_info *)(netdev_priv(ndev)))->iscsi_ipv4addr) = addr +static inline unsigned int cxgb3i_get_private_ipv4addr(struct net_device *ndev) +{ + return ((struct port_info *)(netdev_priv(ndev)))->iscsi_ipv4addr; +} + +static inline void cxgb3i_set_private_ipv4addr(struct net_device *ndev, + unsigned int addr) +{ + struct port_info *pi = (struct port_info *)netdev_priv(ndev); + + pi->iscsic.flags = addr ? 1 : 0; + pi->iscsi_ipv4addr = addr; + if (addr) + memcpy(pi->iscsic.mac_addr, ndev->dev_addr, ETH_ALEN); +} struct cpl_iscsi_hdr_norss { union opcode_tid ot; diff --git a/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c b/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c index 8c04fada710b..f3a4cd7cf782 100644 --- a/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c +++ b/drivers/scsi/cxgbi/cxgb4i/cxgb4i.c @@ -106,7 +106,7 @@ static struct iscsi_transport cxgb4i_iscsi_transport = { .name = DRV_MODULE_NAME, .caps = CAP_RECOVERY_L0 | CAP_MULTI_R2T | CAP_HDRDGST | CAP_DATADGST | CAP_DIGEST_OFFLOAD | - CAP_PADDING_OFFLOAD, + CAP_PADDING_OFFLOAD | CAP_TEXT_NEGO, .param_mask = ISCSI_MAX_RECV_DLENGTH | ISCSI_MAX_XMIT_DLENGTH | ISCSI_HDRDGST_EN | ISCSI_DATADGST_EN | ISCSI_INITIAL_R2T_EN | ISCSI_MAX_R2T | @@ -138,7 +138,7 @@ static struct iscsi_transport cxgb4i_iscsi_transport = { .destroy_conn = iscsi_tcp_conn_teardown, .start_conn = iscsi_conn_start, .stop_conn = iscsi_conn_stop, - .get_conn_param = cxgbi_get_conn_param, + .get_conn_param = iscsi_conn_get_param, .set_param = cxgbi_set_conn_param, .get_stats = cxgbi_get_conn_stats, /* pdu xmit req from user space */ @@ -153,6 +153,7 @@ static struct iscsi_transport cxgb4i_iscsi_transport = { .xmit_pdu = cxgbi_conn_xmit_pdu, .parse_pdu_itt = cxgbi_parse_pdu_itt, /* TCP connect/disconnect */ + .get_ep_param = cxgbi_get_ep_param, .ep_connect = cxgbi_ep_connect, .ep_poll = cxgbi_ep_poll, .ep_disconnect = cxgbi_ep_disconnect, @@ -1425,8 +1426,6 @@ static int cxgb4i_ddp_init(struct cxgbi_device *cdev) cxgbi_ddp_page_size_factor(pgsz_factor); cxgb4_iscsi_init(lldi->ports[0], tagmask, pgsz_factor); - cdev->csk_ddp_free_gl_skb = NULL; - cdev->csk_ddp_alloc_gl_skb = NULL; cdev->csk_ddp_setup_digest = ddp_setup_conn_digest; cdev->csk_ddp_setup_pgidx = ddp_setup_conn_pgidx; cdev->csk_ddp_set = ddp_set_map; diff --git a/drivers/scsi/cxgbi/libcxgbi.c b/drivers/scsi/cxgbi/libcxgbi.c index d2ad3d676724..fedf1be278ff 100644 --- a/drivers/scsi/cxgbi/libcxgbi.c +++ b/drivers/scsi/cxgbi/libcxgbi.c @@ -543,6 +543,7 @@ static struct cxgbi_sock *cxgbi_check_route(struct sockaddr *dst_addr) csk->dst = dst; csk->daddr.sin_addr.s_addr = daddr->sin_addr.s_addr; csk->daddr.sin_port = daddr->sin_port; + csk->daddr.sin_family = daddr->sin_family; csk->saddr.sin_addr.s_addr = rt->rt_src; return csk; @@ -1277,12 +1278,6 @@ static int ddp_tag_reserve(struct cxgbi_sock *csk, unsigned int tid, return idx; } - if (cdev->csk_ddp_alloc_gl_skb) { - err = cdev->csk_ddp_alloc_gl_skb(ddp, idx, npods, gfp); - if (err < 0) - goto unmark_entries; - } - tag = cxgbi_ddp_tag_base(tformat, sw_tag); tag |= idx << PPOD_IDX_SHIFT; @@ -1293,11 +1288,8 @@ static int ddp_tag_reserve(struct cxgbi_sock *csk, unsigned int tid, hdr.page_offset = htonl(gl->offset); err = cdev->csk_ddp_set(csk, &hdr, idx, npods, gl); - if (err < 0) { - if (cdev->csk_ddp_free_gl_skb) - cdev->csk_ddp_free_gl_skb(ddp, idx, npods); + if (err < 0) goto unmark_entries; - } ddp->idx_last = idx; log_debug(1 << CXGBI_DBG_DDP, @@ -1363,8 +1355,6 @@ static void ddp_destroy(struct kref *kref) >> PPOD_PAGES_SHIFT; pr_info("cdev 0x%p, ddp %d + %d.\n", cdev, i, npods); kfree(gl); - if (cdev->csk_ddp_free_gl_skb) - cdev->csk_ddp_free_gl_skb(ddp, i, npods); i += npods; } else i++; @@ -1407,8 +1397,6 @@ int cxgbi_ddp_init(struct cxgbi_device *cdev, return -ENOMEM; } ddp->gl_map = (struct cxgbi_gather_list **)(ddp + 1); - ddp->gl_skb = (struct sk_buff **)(((char *)ddp->gl_map) + - ppmax * sizeof(struct cxgbi_gather_list *)); cdev->ddp = ddp; spin_lock_init(&ddp->map_lock); @@ -1908,13 +1896,16 @@ EXPORT_SYMBOL_GPL(cxgbi_conn_alloc_pdu); static inline void tx_skb_setmode(struct sk_buff *skb, int hcrc, int dcrc) { - u8 submode = 0; + if (hcrc || dcrc) { + u8 submode = 0; - if (hcrc) - submode |= 1; - if (dcrc) - submode |= 2; - cxgbi_skcb_ulp_mode(skb) = (ULP2_MODE_ISCSI << 4) | submode; + if (hcrc) + submode |= 1; + if (dcrc) + submode |= 2; + cxgbi_skcb_ulp_mode(skb) = (ULP2_MODE_ISCSI << 4) | submode; + } else + cxgbi_skcb_ulp_mode(skb) = 0; } int cxgbi_conn_init_pdu(struct iscsi_task *task, unsigned int offset, @@ -2210,32 +2201,34 @@ int cxgbi_set_conn_param(struct iscsi_cls_conn *cls_conn, } EXPORT_SYMBOL_GPL(cxgbi_set_conn_param); -int cxgbi_get_conn_param(struct iscsi_cls_conn *cls_conn, - enum iscsi_param param, char *buf) +int cxgbi_get_ep_param(struct iscsi_endpoint *ep, enum iscsi_param param, + char *buf) { - struct iscsi_conn *iconn = cls_conn->dd_data; + struct cxgbi_endpoint *cep = ep->dd_data; + struct cxgbi_sock *csk; int len; log_debug(1 << CXGBI_DBG_ISCSI, - "cls_conn 0x%p, param %d.\n", cls_conn, param); + "cls_conn 0x%p, param %d.\n", ep, param); switch (param) { case ISCSI_PARAM_CONN_PORT: - spin_lock_bh(&iconn->session->lock); - len = sprintf(buf, "%hu\n", iconn->portal_port); - spin_unlock_bh(&iconn->session->lock); - break; case ISCSI_PARAM_CONN_ADDRESS: - spin_lock_bh(&iconn->session->lock); - len = sprintf(buf, "%s\n", iconn->portal_address); - spin_unlock_bh(&iconn->session->lock); - break; + if (!cep) + return -ENOTCONN; + + csk = cep->csk; + if (!csk) + return -ENOTCONN; + + return iscsi_conn_get_addr_param((struct sockaddr_storage *) + &csk->daddr, param, buf); default: - return iscsi_conn_get_param(cls_conn, param, buf); + return -ENOSYS; } return len; } -EXPORT_SYMBOL_GPL(cxgbi_get_conn_param); +EXPORT_SYMBOL_GPL(cxgbi_get_ep_param); struct iscsi_cls_conn * cxgbi_create_conn(struct iscsi_cls_session *cls_session, u32 cid) @@ -2302,11 +2295,6 @@ int cxgbi_bind_conn(struct iscsi_cls_session *cls_session, cxgbi_conn_max_xmit_dlength(conn); cxgbi_conn_max_recv_dlength(conn); - spin_lock_bh(&conn->session->lock); - sprintf(conn->portal_address, "%pI4", &csk->daddr.sin_addr.s_addr); - conn->portal_port = ntohs(csk->daddr.sin_port); - spin_unlock_bh(&conn->session->lock); - log_debug(1 << CXGBI_DBG_ISCSI, "cls 0x%p,0x%p, ep 0x%p, cconn 0x%p, csk 0x%p.\n", cls_session, cls_conn, ep, cconn, csk); diff --git a/drivers/scsi/cxgbi/libcxgbi.h b/drivers/scsi/cxgbi/libcxgbi.h index c57d59db000c..0a20fd5f7102 100644 --- a/drivers/scsi/cxgbi/libcxgbi.h +++ b/drivers/scsi/cxgbi/libcxgbi.h @@ -131,7 +131,6 @@ struct cxgbi_ddp_info { unsigned int rsvd_tag_mask; spinlock_t map_lock; struct cxgbi_gather_list **gl_map; - struct sk_buff **gl_skb; }; #define DDP_PGIDX_MAX 4 @@ -536,8 +535,6 @@ struct cxgbi_device { struct cxgbi_ddp_info *ddp; void (*dev_ddp_cleanup)(struct cxgbi_device *); - void (*csk_ddp_free_gl_skb)(struct cxgbi_ddp_info *, int, int); - int (*csk_ddp_alloc_gl_skb)(struct cxgbi_ddp_info *, int, int, gfp_t); int (*csk_ddp_set)(struct cxgbi_sock *, struct cxgbi_pagepod_hdr *, unsigned int, unsigned int, struct cxgbi_gather_list *); @@ -715,7 +712,7 @@ void cxgbi_cleanup_task(struct iscsi_task *task); void cxgbi_get_conn_stats(struct iscsi_cls_conn *, struct iscsi_stats *); int cxgbi_set_conn_param(struct iscsi_cls_conn *, enum iscsi_param, char *, int); -int cxgbi_get_conn_param(struct iscsi_cls_conn *, enum iscsi_param, char *); +int cxgbi_get_ep_param(struct iscsi_endpoint *ep, enum iscsi_param, char *); struct iscsi_cls_conn *cxgbi_create_conn(struct iscsi_cls_session *, u32); int cxgbi_bind_conn(struct iscsi_cls_session *, struct iscsi_cls_conn *, u64, int); diff --git a/drivers/scsi/device_handler/scsi_dh.c b/drivers/scsi/device_handler/scsi_dh.c index b837c5b3c8f9..564e6ecd17c2 100644 --- a/drivers/scsi/device_handler/scsi_dh.c +++ b/drivers/scsi/device_handler/scsi_dh.c @@ -25,16 +25,9 @@ #include <scsi/scsi_dh.h> #include "../scsi_priv.h" -struct scsi_dh_devinfo_list { - struct list_head node; - char vendor[9]; - char model[17]; - struct scsi_device_handler *handler; -}; - static DEFINE_SPINLOCK(list_lock); static LIST_HEAD(scsi_dh_list); -static LIST_HEAD(scsi_dh_dev_list); +static int scsi_dh_list_idx = 1; static struct scsi_device_handler *get_device_handler(const char *name) { @@ -51,40 +44,18 @@ static struct scsi_device_handler *get_device_handler(const char *name) return found; } - -static struct scsi_device_handler * -scsi_dh_cache_lookup(struct scsi_device *sdev) +static struct scsi_device_handler *get_device_handler_by_idx(int idx) { - struct scsi_dh_devinfo_list *tmp; - struct scsi_device_handler *found_dh = NULL; + struct scsi_device_handler *tmp, *found = NULL; spin_lock(&list_lock); - list_for_each_entry(tmp, &scsi_dh_dev_list, node) { - if (!strncmp(sdev->vendor, tmp->vendor, strlen(tmp->vendor)) && - !strncmp(sdev->model, tmp->model, strlen(tmp->model))) { - found_dh = tmp->handler; + list_for_each_entry(tmp, &scsi_dh_list, list) { + if (tmp->idx == idx) { + found = tmp; break; } } spin_unlock(&list_lock); - - return found_dh; -} - -static int scsi_dh_handler_lookup(struct scsi_device_handler *scsi_dh, - struct scsi_device *sdev) -{ - int i, found = 0; - - for(i = 0; scsi_dh->devlist[i].vendor; i++) { - if (!strncmp(sdev->vendor, scsi_dh->devlist[i].vendor, - strlen(scsi_dh->devlist[i].vendor)) && - !strncmp(sdev->model, scsi_dh->devlist[i].model, - strlen(scsi_dh->devlist[i].model))) { - found = 1; - break; - } - } return found; } @@ -102,41 +73,14 @@ device_handler_match(struct scsi_device_handler *scsi_dh, struct scsi_device *sdev) { struct scsi_device_handler *found_dh = NULL; - struct scsi_dh_devinfo_list *tmp; + int idx; - found_dh = scsi_dh_cache_lookup(sdev); - if (found_dh) - return found_dh; + idx = scsi_get_device_flags_keyed(sdev, sdev->vendor, sdev->model, + SCSI_DEVINFO_DH); + found_dh = get_device_handler_by_idx(idx); - if (scsi_dh) { - if (scsi_dh_handler_lookup(scsi_dh, sdev)) - found_dh = scsi_dh; - } else { - struct scsi_device_handler *tmp_dh; - - spin_lock(&list_lock); - list_for_each_entry(tmp_dh, &scsi_dh_list, list) { - if (scsi_dh_handler_lookup(tmp_dh, sdev)) - found_dh = tmp_dh; - } - spin_unlock(&list_lock); - } - - if (found_dh) { /* If device is found, add it to the cache */ - tmp = kmalloc(sizeof(*tmp), GFP_KERNEL); - if (tmp) { - strncpy(tmp->vendor, sdev->vendor, 8); - strncpy(tmp->model, sdev->model, 16); - tmp->vendor[8] = '\0'; - tmp->model[16] = '\0'; - tmp->handler = found_dh; - spin_lock(&list_lock); - list_add(&tmp->node, &scsi_dh_dev_list); - spin_unlock(&list_lock); - } else { - found_dh = NULL; - } - } + if (scsi_dh && found_dh != scsi_dh) + found_dh = NULL; return found_dh; } @@ -373,12 +317,25 @@ static int scsi_dh_notifier_remove(struct device *dev, void *data) */ int scsi_register_device_handler(struct scsi_device_handler *scsi_dh) { + int i; + if (get_device_handler(scsi_dh->name)) return -EBUSY; spin_lock(&list_lock); + scsi_dh->idx = scsi_dh_list_idx++; list_add(&scsi_dh->list, &scsi_dh_list); spin_unlock(&list_lock); + + for (i = 0; scsi_dh->devlist[i].vendor; i++) { + scsi_dev_info_list_add_keyed(0, + scsi_dh->devlist[i].vendor, + scsi_dh->devlist[i].model, + NULL, + scsi_dh->idx, + SCSI_DEVINFO_DH); + } + bus_for_each_dev(&scsi_bus_type, NULL, scsi_dh, scsi_dh_notifier_add); printk(KERN_INFO "%s: device handler registered\n", scsi_dh->name); @@ -395,7 +352,7 @@ EXPORT_SYMBOL_GPL(scsi_register_device_handler); */ int scsi_unregister_device_handler(struct scsi_device_handler *scsi_dh) { - struct scsi_dh_devinfo_list *tmp, *pos; + int i; if (!get_device_handler(scsi_dh->name)) return -ENODEV; @@ -403,14 +360,14 @@ int scsi_unregister_device_handler(struct scsi_device_handler *scsi_dh) bus_for_each_dev(&scsi_bus_type, NULL, scsi_dh, scsi_dh_notifier_remove); + for (i = 0; scsi_dh->devlist[i].vendor; i++) { + scsi_dev_info_list_del_keyed(scsi_dh->devlist[i].vendor, + scsi_dh->devlist[i].model, + SCSI_DEVINFO_DH); + } + spin_lock(&list_lock); list_del(&scsi_dh->list); - list_for_each_entry_safe(pos, tmp, &scsi_dh_dev_list, node) { - if (pos->handler == scsi_dh) { - list_del(&pos->node); - kfree(pos); - } - } spin_unlock(&list_lock); printk(KERN_INFO "%s: device handler unregistered\n", scsi_dh->name); @@ -576,6 +533,10 @@ static int __init scsi_dh_init(void) { int r; + r = scsi_dev_info_add_list(SCSI_DEVINFO_DH, "SCSI Device Handler"); + if (r) + return r; + r = bus_register_notifier(&scsi_bus_type, &scsi_dh_nb); if (!r) @@ -590,6 +551,7 @@ static void __exit scsi_dh_exit(void) bus_for_each_dev(&scsi_bus_type, NULL, NULL, scsi_dh_sysfs_attr_remove); bus_unregister_notifier(&scsi_bus_type, &scsi_dh_nb); + scsi_dev_info_remove_list(SCSI_DEVINFO_DH); } module_init(scsi_dh_init); diff --git a/drivers/scsi/device_handler/scsi_dh_alua.c b/drivers/scsi/device_handler/scsi_dh_alua.c index 6b729324b8d3..7cae0bc85390 100644 --- a/drivers/scsi/device_handler/scsi_dh_alua.c +++ b/drivers/scsi/device_handler/scsi_dh_alua.c @@ -253,13 +253,15 @@ static void stpg_endio(struct request *req, int error) { struct alua_dh_data *h = req->end_io_data; struct scsi_sense_hdr sense_hdr; - unsigned err = SCSI_DH_IO; + unsigned err = SCSI_DH_OK; if (error || host_byte(req->errors) != DID_OK || - msg_byte(req->errors) != COMMAND_COMPLETE) + msg_byte(req->errors) != COMMAND_COMPLETE) { + err = SCSI_DH_IO; goto done; + } - if (err == SCSI_DH_IO && h->senselen > 0) { + if (h->senselen > 0) { err = scsi_normalize_sense(h->sense, SCSI_SENSE_BUFFERSIZE, &sense_hdr); if (!err) { @@ -285,7 +287,8 @@ static void stpg_endio(struct request *req, int error) print_alua_state(h->state)); } done: - blk_put_request(req); + req->end_io_data = NULL; + __blk_put_request(req->q, req); if (h->callback_fn) { h->callback_fn(h->callback_data, err); h->callback_fn = h->callback_data = NULL; @@ -303,7 +306,6 @@ done: static unsigned submit_stpg(struct alua_dh_data *h) { struct request *rq; - int err = SCSI_DH_RES_TEMP_UNAVAIL; int stpg_len = 8; struct scsi_device *sdev = h->sdev; @@ -332,7 +334,7 @@ static unsigned submit_stpg(struct alua_dh_data *h) rq->end_io_data = h; blk_execute_rq_nowait(rq->q, NULL, rq, 1, stpg_endio); - return err; + return SCSI_DH_OK; } /* @@ -730,7 +732,9 @@ static const struct scsi_dh_devlist alua_dev_list[] = { {"Pillar", "Axiom" }, {"Intel", "Multi-Flex"}, {"NETAPP", "LUN"}, + {"NETAPP", "LUN C-Mode"}, {"AIX", "NVDISK"}, + {"Promise", "VTrak"}, {NULL, NULL} }; @@ -759,7 +763,7 @@ static int alua_bus_attach(struct scsi_device *sdev) unsigned long flags; int err = SCSI_DH_OK; - scsi_dh_data = kzalloc(sizeof(struct scsi_device_handler *) + scsi_dh_data = kzalloc(sizeof(*scsi_dh_data) + sizeof(*h) , GFP_KERNEL); if (!scsi_dh_data) { sdev_printk(KERN_ERR, sdev, "%s: Attach failed\n", diff --git a/drivers/scsi/device_handler/scsi_dh_emc.c b/drivers/scsi/device_handler/scsi_dh_emc.c index 6faf472f7537..48441f6908a4 100644 --- a/drivers/scsi/device_handler/scsi_dh_emc.c +++ b/drivers/scsi/device_handler/scsi_dh_emc.c @@ -650,7 +650,7 @@ static int clariion_bus_attach(struct scsi_device *sdev) unsigned long flags; int err; - scsi_dh_data = kzalloc(sizeof(struct scsi_device_handler *) + scsi_dh_data = kzalloc(sizeof(*scsi_dh_data) + sizeof(*h) , GFP_KERNEL); if (!scsi_dh_data) { sdev_printk(KERN_ERR, sdev, "%s: Attach failed\n", diff --git a/drivers/scsi/device_handler/scsi_dh_hp_sw.c b/drivers/scsi/device_handler/scsi_dh_hp_sw.c index e3916641e627..b479f1eef968 100644 --- a/drivers/scsi/device_handler/scsi_dh_hp_sw.c +++ b/drivers/scsi/device_handler/scsi_dh_hp_sw.c @@ -225,7 +225,8 @@ static void start_stop_endio(struct request *req, int error) } } done: - blk_put_request(req); + req->end_io_data = NULL; + __blk_put_request(req->q, req); if (h->callback_fn) { h->callback_fn(h->callback_data, err); h->callback_fn = h->callback_data = NULL; @@ -338,8 +339,8 @@ static int hp_sw_bus_attach(struct scsi_device *sdev) unsigned long flags; int ret; - scsi_dh_data = kzalloc(sizeof(struct scsi_device_handler *) - + sizeof(struct hp_sw_dh_data) , GFP_KERNEL); + scsi_dh_data = kzalloc(sizeof(*scsi_dh_data) + + sizeof(*h) , GFP_KERNEL); if (!scsi_dh_data) { sdev_printk(KERN_ERR, sdev, "%s: Attach Failed\n", HP_SW_NAME); diff --git a/drivers/scsi/device_handler/scsi_dh_rdac.c b/drivers/scsi/device_handler/scsi_dh_rdac.c index 5be3ae15cb71..f468b05cfaac 100644 --- a/drivers/scsi/device_handler/scsi_dh_rdac.c +++ b/drivers/scsi/device_handler/scsi_dh_rdac.c @@ -281,11 +281,13 @@ static struct request *get_rdac_req(struct scsi_device *sdev, } static struct request *rdac_failover_get(struct scsi_device *sdev, - struct rdac_dh_data *h) + struct rdac_dh_data *h, struct list_head *list) { struct request *rq; struct rdac_mode_common *common; unsigned data_size; + struct rdac_queue_data *qdata; + u8 *lun_table; if (h->ctlr->use_ms10) { struct rdac_pg_expanded *rdac_pg; @@ -298,6 +300,7 @@ static struct request *rdac_failover_get(struct scsi_device *sdev, rdac_pg->subpage_code = 0x1; rdac_pg->page_len[0] = 0x01; rdac_pg->page_len[1] = 0x28; + lun_table = rdac_pg->lun_table; } else { struct rdac_pg_legacy *rdac_pg; @@ -307,11 +310,16 @@ static struct request *rdac_failover_get(struct scsi_device *sdev, common = &rdac_pg->common; rdac_pg->page_code = RDAC_PAGE_CODE_REDUNDANT_CONTROLLER; rdac_pg->page_len = 0x68; + lun_table = rdac_pg->lun_table; } common->rdac_mode[1] = RDAC_MODE_TRANSFER_SPECIFIED_LUNS; common->quiescence_timeout = RDAC_QUIESCENCE_TIME; common->rdac_options = RDAC_FORCED_QUIESENCE; + list_for_each_entry(qdata, list, entry) { + lun_table[qdata->h->lun] = 0x81; + } + /* get request for block layer packet command */ rq = get_rdac_req(sdev, &h->ctlr->mode_select, data_size, WRITE); if (!rq) @@ -565,7 +573,6 @@ static void send_mode_select(struct work_struct *work) int err, retry_cnt = RDAC_RETRY_COUNT; struct rdac_queue_data *tmp, *qdata; LIST_HEAD(list); - u8 *lun_table; spin_lock(&ctlr->ms_lock); list_splice_init(&ctlr->ms_head, &list); @@ -573,21 +580,12 @@ static void send_mode_select(struct work_struct *work) ctlr->ms_sdev = NULL; spin_unlock(&ctlr->ms_lock); - if (ctlr->use_ms10) - lun_table = ctlr->mode_select.expanded.lun_table; - else - lun_table = ctlr->mode_select.legacy.lun_table; - retry: err = SCSI_DH_RES_TEMP_UNAVAIL; - rq = rdac_failover_get(sdev, h); + rq = rdac_failover_get(sdev, h, &list); if (!rq) goto done; - list_for_each_entry(qdata, &list, entry) { - lun_table[qdata->h->lun] = 0x81; - } - RDAC_LOG(RDAC_LOG_FAILOVER, sdev, "array %s, ctlr %d, " "%s MODE_SELECT command", (char *) h->ctlr->array_name, h->ctlr->index, @@ -800,7 +798,7 @@ static int rdac_bus_attach(struct scsi_device *sdev) int err; char array_name[ARRAY_LABEL_LEN]; - scsi_dh_data = kzalloc(sizeof(struct scsi_device_handler *) + scsi_dh_data = kzalloc(sizeof(*scsi_dh_data) + sizeof(*h) , GFP_KERNEL); if (!scsi_dh_data) { sdev_printk(KERN_ERR, sdev, "%s: Attach failed\n", @@ -906,4 +904,5 @@ module_exit(rdac_exit); MODULE_DESCRIPTION("Multipath LSI/Engenio RDAC driver"); MODULE_AUTHOR("Mike Christie, Chandra Seetharaman"); +MODULE_VERSION("01.00.0000.0000"); MODULE_LICENSE("GPL"); diff --git a/drivers/scsi/fcoe/Makefile b/drivers/scsi/fcoe/Makefile index 950f27615c76..f6d37d0271f7 100644 --- a/drivers/scsi/fcoe/Makefile +++ b/drivers/scsi/fcoe/Makefile @@ -1,2 +1,4 @@ obj-$(CONFIG_FCOE) += fcoe.o obj-$(CONFIG_LIBFCOE) += libfcoe.o + +libfcoe-objs := fcoe_ctlr.o fcoe_transport.o diff --git a/drivers/scsi/fcoe/fcoe.c b/drivers/scsi/fcoe/fcoe.c index 9f9600b67001..495456fe4520 100644 --- a/drivers/scsi/fcoe/fcoe.c +++ b/drivers/scsi/fcoe/fcoe.c @@ -31,6 +31,7 @@ #include <linux/fs.h> #include <linux/sysfs.h> #include <linux/ctype.h> +#include <linux/workqueue.h> #include <scsi/scsi_tcq.h> #include <scsi/scsicam.h> #include <scsi/scsi_transport.h> @@ -58,6 +59,8 @@ MODULE_PARM_DESC(ddp_min, "Minimum I/O size in bytes for " \ DEFINE_MUTEX(fcoe_config_mutex); +static struct workqueue_struct *fcoe_wq; + /* fcoe_percpu_clean completion. Waiter protected by fcoe_create_mutex */ static DECLARE_COMPLETION(fcoe_flush_completion); @@ -72,7 +75,6 @@ static int fcoe_xmit(struct fc_lport *, struct fc_frame *); static int fcoe_rcv(struct sk_buff *, struct net_device *, struct packet_type *, struct net_device *); static int fcoe_percpu_receive_thread(void *); -static void fcoe_clean_pending_queue(struct fc_lport *); static void fcoe_percpu_clean(struct fc_lport *); static int fcoe_link_speed_update(struct fc_lport *); static int fcoe_link_ok(struct fc_lport *); @@ -80,7 +82,6 @@ static int fcoe_link_ok(struct fc_lport *); static struct fc_lport *fcoe_hostlist_lookup(const struct net_device *); static int fcoe_hostlist_add(const struct fc_lport *); -static void fcoe_check_wait_queue(struct fc_lport *, struct sk_buff *); static int fcoe_device_notification(struct notifier_block *, ulong, void *); static void fcoe_dev_setup(void); static void fcoe_dev_cleanup(void); @@ -101,10 +102,11 @@ static int fcoe_ddp_done(struct fc_lport *, u16); static int fcoe_cpu_callback(struct notifier_block *, unsigned long, void *); -static int fcoe_create(const char *, struct kernel_param *); -static int fcoe_destroy(const char *, struct kernel_param *); -static int fcoe_enable(const char *, struct kernel_param *); -static int fcoe_disable(const char *, struct kernel_param *); +static bool fcoe_match(struct net_device *netdev); +static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode); +static int fcoe_destroy(struct net_device *netdev); +static int fcoe_enable(struct net_device *netdev); +static int fcoe_disable(struct net_device *netdev); static struct fc_seq *fcoe_elsct_send(struct fc_lport *, u32 did, struct fc_frame *, @@ -117,24 +119,6 @@ static void fcoe_recv_frame(struct sk_buff *skb); static void fcoe_get_lesb(struct fc_lport *, struct fc_els_lesb *); -module_param_call(create, fcoe_create, NULL, (void *)FIP_MODE_FABRIC, S_IWUSR); -__MODULE_PARM_TYPE(create, "string"); -MODULE_PARM_DESC(create, " Creates fcoe instance on a ethernet interface"); -module_param_call(create_vn2vn, fcoe_create, NULL, - (void *)FIP_MODE_VN2VN, S_IWUSR); -__MODULE_PARM_TYPE(create_vn2vn, "string"); -MODULE_PARM_DESC(create_vn2vn, " Creates a VN_node to VN_node FCoE instance " - "on an Ethernet interface"); -module_param_call(destroy, fcoe_destroy, NULL, NULL, S_IWUSR); -__MODULE_PARM_TYPE(destroy, "string"); -MODULE_PARM_DESC(destroy, " Destroys fcoe instance on a ethernet interface"); -module_param_call(enable, fcoe_enable, NULL, NULL, S_IWUSR); -__MODULE_PARM_TYPE(enable, "string"); -MODULE_PARM_DESC(enable, " Enables fcoe on a ethernet interface."); -module_param_call(disable, fcoe_disable, NULL, NULL, S_IWUSR); -__MODULE_PARM_TYPE(disable, "string"); -MODULE_PARM_DESC(disable, " Disables fcoe on a ethernet interface."); - /* notification function for packets from net device */ static struct notifier_block fcoe_notifier = { .notifier_call = fcoe_device_notification, @@ -145,8 +129,8 @@ static struct notifier_block fcoe_cpu_notifier = { .notifier_call = fcoe_cpu_callback, }; -static struct scsi_transport_template *fcoe_transport_template; -static struct scsi_transport_template *fcoe_vport_transport_template; +static struct scsi_transport_template *fcoe_nport_scsi_transport; +static struct scsi_transport_template *fcoe_vport_scsi_transport; static int fcoe_vport_destroy(struct fc_vport *); static int fcoe_vport_create(struct fc_vport *, bool disabled); @@ -163,7 +147,7 @@ static struct libfc_function_template fcoe_libfc_fcn_templ = { .lport_set_port_id = fcoe_set_port_id, }; -struct fc_function_template fcoe_transport_function = { +struct fc_function_template fcoe_nport_fc_functions = { .show_host_node_name = 1, .show_host_port_name = 1, .show_host_supported_classes = 1, @@ -203,7 +187,7 @@ struct fc_function_template fcoe_transport_function = { .bsg_request = fc_lport_bsg_request, }; -struct fc_function_template fcoe_vport_transport_function = { +struct fc_function_template fcoe_vport_fc_functions = { .show_host_node_name = 1, .show_host_port_name = 1, .show_host_supported_classes = 1, @@ -356,10 +340,18 @@ static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev, struct fcoe_interface *fcoe; int err; + if (!try_module_get(THIS_MODULE)) { + FCOE_NETDEV_DBG(netdev, + "Could not get a reference to the module\n"); + fcoe = ERR_PTR(-EBUSY); + goto out; + } + fcoe = kzalloc(sizeof(*fcoe), GFP_KERNEL); if (!fcoe) { FCOE_NETDEV_DBG(netdev, "Could not allocate fcoe structure\n"); - return NULL; + fcoe = ERR_PTR(-ENOMEM); + goto out_nomod; } dev_hold(netdev); @@ -378,9 +370,15 @@ static struct fcoe_interface *fcoe_interface_create(struct net_device *netdev, fcoe_ctlr_destroy(&fcoe->ctlr); kfree(fcoe); dev_put(netdev); - return NULL; + fcoe = ERR_PTR(err); + goto out_nomod; } + goto out; + +out_nomod: + module_put(THIS_MODULE); +out: return fcoe; } @@ -442,6 +440,7 @@ static void fcoe_interface_release(struct kref *kref) fcoe_ctlr_destroy(&fcoe->ctlr); kfree(fcoe); dev_put(netdev); + module_put(THIS_MODULE); } /** @@ -505,7 +504,7 @@ static void fcoe_fip_send(struct fcoe_ctlr *fip, struct sk_buff *skb) static void fcoe_update_src_mac(struct fc_lport *lport, u8 *addr) { struct fcoe_port *port = lport_priv(lport); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; rtnl_lock(); if (!is_zero_ether_addr(port->data_src_addr)) @@ -561,17 +560,6 @@ static int fcoe_lport_config(struct fc_lport *lport) } /** - * fcoe_queue_timer() - The fcoe queue timer - * @lport: The local port - * - * Calls fcoe_check_wait_queue on timeout - */ -static void fcoe_queue_timer(ulong lport) -{ - fcoe_check_wait_queue((struct fc_lport *)lport, NULL); -} - -/** * fcoe_get_wwn() - Get the world wide name from LLD if it supports it * @netdev: the associated net device * @wwn: the output WWN @@ -650,7 +638,7 @@ static int fcoe_netdev_config(struct fc_lport *lport, struct net_device *netdev) /* Setup lport private data to point to fcoe softc */ port = lport_priv(lport); - fcoe = port->fcoe; + fcoe = port->priv; /* * Determine max frame size based on underlying device and optional @@ -708,9 +696,9 @@ static int fcoe_shost_config(struct fc_lport *lport, struct device *dev) lport->host->max_cmd_len = FCOE_MAX_CMD_LEN; if (lport->vport) - lport->host->transportt = fcoe_vport_transport_template; + lport->host->transportt = fcoe_vport_scsi_transport; else - lport->host->transportt = fcoe_transport_template; + lport->host->transportt = fcoe_nport_scsi_transport; /* add the new host to the SCSI-ml */ rc = scsi_add_host(lport->host, dev); @@ -760,7 +748,7 @@ bool fcoe_oem_match(struct fc_frame *fp) static inline int fcoe_em_config(struct fc_lport *lport) { struct fcoe_port *port = lport_priv(lport); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; struct fcoe_interface *oldfcoe = NULL; struct net_device *old_real_dev, *cur_real_dev; u16 min_xid = FCOE_MIN_XID; @@ -844,7 +832,7 @@ skip_oem: static void fcoe_if_destroy(struct fc_lport *lport) { struct fcoe_port *port = lport_priv(lport); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; struct net_device *netdev = fcoe->netdev; FCOE_NETDEV_DBG(netdev, "Destroying interface\n"); @@ -886,7 +874,6 @@ static void fcoe_if_destroy(struct fc_lport *lport) /* Release the Scsi_Host */ scsi_host_put(lport->host); - module_put(THIS_MODULE); } /** @@ -966,7 +953,9 @@ static struct fc_lport *fcoe_if_create(struct fcoe_interface *fcoe, } port = lport_priv(lport); port->lport = lport; - port->fcoe = fcoe; + port->priv = fcoe; + port->max_queue_depth = FCOE_MAX_QUEUE_DEPTH; + port->min_queue_depth = FCOE_MIN_QUEUE_DEPTH; INIT_WORK(&port->destroy_work, fcoe_destroy_work); /* configure a fc_lport including the exchange manager */ @@ -1050,11 +1039,12 @@ out: static int __init fcoe_if_init(void) { /* attach to scsi transport */ - fcoe_transport_template = fc_attach_transport(&fcoe_transport_function); - fcoe_vport_transport_template = - fc_attach_transport(&fcoe_vport_transport_function); + fcoe_nport_scsi_transport = + fc_attach_transport(&fcoe_nport_fc_functions); + fcoe_vport_scsi_transport = + fc_attach_transport(&fcoe_vport_fc_functions); - if (!fcoe_transport_template) { + if (!fcoe_nport_scsi_transport) { printk(KERN_ERR "fcoe: Failed to attach to the FC transport\n"); return -ENODEV; } @@ -1071,10 +1061,10 @@ static int __init fcoe_if_init(void) */ int __exit fcoe_if_exit(void) { - fc_release_transport(fcoe_transport_template); - fc_release_transport(fcoe_vport_transport_template); - fcoe_transport_template = NULL; - fcoe_vport_transport_template = NULL; + fc_release_transport(fcoe_nport_scsi_transport); + fc_release_transport(fcoe_vport_scsi_transport); + fcoe_nport_scsi_transport = NULL; + fcoe_vport_scsi_transport = NULL; return 0; } @@ -1361,108 +1351,22 @@ err2: } /** - * fcoe_start_io() - Start FCoE I/O - * @skb: The packet to be transmitted - * - * This routine is called from the net device to start transmitting - * FCoE packets. - * - * Returns: 0 for success - */ -static inline int fcoe_start_io(struct sk_buff *skb) -{ - struct sk_buff *nskb; - int rc; - - nskb = skb_clone(skb, GFP_ATOMIC); - rc = dev_queue_xmit(nskb); - if (rc != 0) - return rc; - kfree_skb(skb); - return 0; -} - -/** - * fcoe_get_paged_crc_eof() - Allocate a page to be used for the trailer CRC + * fcoe_alloc_paged_crc_eof() - Allocate a page to be used for the trailer CRC * @skb: The packet to be transmitted * @tlen: The total length of the trailer * - * This routine allocates a page for frame trailers. The page is re-used if - * there is enough room left on it for the current trailer. If there isn't - * enough buffer left a new page is allocated for the trailer. Reference to - * the page from this function as well as the skbs using the page fragments - * ensure that the page is freed at the appropriate time. - * * Returns: 0 for success */ -static int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen) +static int fcoe_alloc_paged_crc_eof(struct sk_buff *skb, int tlen) { struct fcoe_percpu_s *fps; - struct page *page; + int rc; fps = &get_cpu_var(fcoe_percpu); - page = fps->crc_eof_page; - if (!page) { - page = alloc_page(GFP_ATOMIC); - if (!page) { - put_cpu_var(fcoe_percpu); - return -ENOMEM; - } - fps->crc_eof_page = page; - fps->crc_eof_offset = 0; - } - - get_page(page); - skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, - fps->crc_eof_offset, tlen); - skb->len += tlen; - skb->data_len += tlen; - skb->truesize += tlen; - fps->crc_eof_offset += sizeof(struct fcoe_crc_eof); - - if (fps->crc_eof_offset >= PAGE_SIZE) { - fps->crc_eof_page = NULL; - fps->crc_eof_offset = 0; - put_page(page); - } + rc = fcoe_get_paged_crc_eof(skb, tlen, fps); put_cpu_var(fcoe_percpu); - return 0; -} -/** - * fcoe_fc_crc() - Calculates the CRC for a given frame - * @fp: The frame to be checksumed - * - * This uses crc32() routine to calculate the CRC for a frame - * - * Return: The 32 bit CRC value - */ -u32 fcoe_fc_crc(struct fc_frame *fp) -{ - struct sk_buff *skb = fp_skb(fp); - struct skb_frag_struct *frag; - unsigned char *data; - unsigned long off, len, clen; - u32 crc; - unsigned i; - - crc = crc32(~0, skb->data, skb_headlen(skb)); - - for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { - frag = &skb_shinfo(skb)->frags[i]; - off = frag->page_offset; - len = frag->size; - while (len > 0) { - clen = min(len, PAGE_SIZE - (off & ~PAGE_MASK)); - data = kmap_atomic(frag->page + (off >> PAGE_SHIFT), - KM_SKB_DATA_SOFTIRQ); - crc = crc32(crc, data + (off & ~PAGE_MASK), clen); - kunmap_atomic(data, KM_SKB_DATA_SOFTIRQ); - off += clen; - len -= clen; - } - } - return crc; + return rc; } /** @@ -1485,7 +1389,7 @@ int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp) unsigned int tlen; /* trailer length */ unsigned int elen; /* eth header, may include vlan */ struct fcoe_port *port = lport_priv(lport); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; u8 sof, eof; struct fcoe_hdr *hp; @@ -1526,7 +1430,7 @@ int fcoe_xmit(struct fc_lport *lport, struct fc_frame *fp) /* copy port crc and eof to the skb buff */ if (skb_is_nonlinear(skb)) { skb_frag_t *frag; - if (fcoe_get_paged_crc_eof(skb, tlen)) { + if (fcoe_alloc_paged_crc_eof(skb, tlen)) { kfree_skb(skb); return -ENOMEM; } @@ -1606,6 +1510,56 @@ static void fcoe_percpu_flush_done(struct sk_buff *skb) } /** + * fcoe_filter_frames() - filter out bad fcoe frames, i.e. bad CRC + * @lport: The local port the frame was received on + * @fp: The received frame + * + * Return: 0 on passing filtering checks + */ +static inline int fcoe_filter_frames(struct fc_lport *lport, + struct fc_frame *fp) +{ + struct fcoe_interface *fcoe; + struct fc_frame_header *fh; + struct sk_buff *skb = (struct sk_buff *)fp; + struct fcoe_dev_stats *stats; + + /* + * We only check CRC if no offload is available and if it is + * it's solicited data, in which case, the FCP layer would + * check it during the copy. + */ + if (lport->crc_offload && skb->ip_summed == CHECKSUM_UNNECESSARY) + fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; + else + fr_flags(fp) |= FCPHF_CRC_UNCHECKED; + + fh = (struct fc_frame_header *) skb_transport_header(skb); + fh = fc_frame_header_get(fp); + if (fh->fh_r_ctl == FC_RCTL_DD_SOL_DATA && fh->fh_type == FC_TYPE_FCP) + return 0; + + fcoe = ((struct fcoe_port *)lport_priv(lport))->priv; + if (is_fip_mode(&fcoe->ctlr) && fc_frame_payload_op(fp) == ELS_LOGO && + ntoh24(fh->fh_s_id) == FC_FID_FLOGI) { + FCOE_DBG("fcoe: dropping FCoE lport LOGO in fip mode\n"); + return -EINVAL; + } + + if (!fr_flags(fp) & FCPHF_CRC_UNCHECKED || + le32_to_cpu(fr_crc(fp)) == ~crc32(~0, skb->data, skb->len)) { + fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; + return 0; + } + + stats = per_cpu_ptr(lport->dev_stats, get_cpu()); + stats->InvalidCRCCount++; + if (stats->InvalidCRCCount < 5) + printk(KERN_WARNING "fcoe: dropping frame with CRC error\n"); + return -EINVAL; +} + +/** * fcoe_recv_frame() - process a single received frame * @skb: frame to process */ @@ -1615,7 +1569,6 @@ static void fcoe_recv_frame(struct sk_buff *skb) struct fc_lport *lport; struct fcoe_rcv_info *fr; struct fcoe_dev_stats *stats; - struct fc_frame_header *fh; struct fcoe_crc_eof crc_eof; struct fc_frame *fp; struct fcoe_port *port; @@ -1646,7 +1599,6 @@ static void fcoe_recv_frame(struct sk_buff *skb) * was done in fcoe_rcv already. */ hp = (struct fcoe_hdr *) skb_network_header(skb); - fh = (struct fc_frame_header *) skb_transport_header(skb); stats = per_cpu_ptr(lport->dev_stats, get_cpu()); if (unlikely(FC_FCOE_DECAPS_VER(hp) != FC_FCOE_VER)) { @@ -1679,35 +1631,11 @@ static void fcoe_recv_frame(struct sk_buff *skb) if (pskb_trim(skb, fr_len)) goto drop; - /* - * We only check CRC if no offload is available and if it is - * it's solicited data, in which case, the FCP layer would - * check it during the copy. - */ - if (lport->crc_offload && - skb->ip_summed == CHECKSUM_UNNECESSARY) - fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; - else - fr_flags(fp) |= FCPHF_CRC_UNCHECKED; - - fh = fc_frame_header_get(fp); - if ((fh->fh_r_ctl != FC_RCTL_DD_SOL_DATA || - fh->fh_type != FC_TYPE_FCP) && - (fr_flags(fp) & FCPHF_CRC_UNCHECKED)) { - if (le32_to_cpu(fr_crc(fp)) != - ~crc32(~0, skb->data, fr_len)) { - if (stats->InvalidCRCCount < 5) - printk(KERN_WARNING "fcoe: dropping " - "frame with CRC error\n"); - stats->InvalidCRCCount++; - goto drop; - } - fr_flags(fp) &= ~FCPHF_CRC_UNCHECKED; + if (!fcoe_filter_frames(lport, fp)) { + put_cpu(); + fc_exch_recv(lport, fp); + return; } - put_cpu(); - fc_exch_recv(lport, fp); - return; - drop: stats->ErrorFrames++; put_cpu(); @@ -1746,64 +1674,6 @@ int fcoe_percpu_receive_thread(void *arg) } /** - * fcoe_check_wait_queue() - Attempt to clear the transmit backlog - * @lport: The local port whose backlog is to be cleared - * - * This empties the wait_queue, dequeues the head of the wait_queue queue - * and calls fcoe_start_io() for each packet. If all skb have been - * transmitted it returns the qlen. If an error occurs it restores - * wait_queue (to try again later) and returns -1. - * - * The wait_queue is used when the skb transmit fails. The failed skb - * will go in the wait_queue which will be emptied by the timer function or - * by the next skb transmit. - */ -static void fcoe_check_wait_queue(struct fc_lport *lport, struct sk_buff *skb) -{ - struct fcoe_port *port = lport_priv(lport); - int rc; - - spin_lock_bh(&port->fcoe_pending_queue.lock); - - if (skb) - __skb_queue_tail(&port->fcoe_pending_queue, skb); - - if (port->fcoe_pending_queue_active) - goto out; - port->fcoe_pending_queue_active = 1; - - while (port->fcoe_pending_queue.qlen) { - /* keep qlen > 0 until fcoe_start_io succeeds */ - port->fcoe_pending_queue.qlen++; - skb = __skb_dequeue(&port->fcoe_pending_queue); - - spin_unlock_bh(&port->fcoe_pending_queue.lock); - rc = fcoe_start_io(skb); - spin_lock_bh(&port->fcoe_pending_queue.lock); - - if (rc) { - __skb_queue_head(&port->fcoe_pending_queue, skb); - /* undo temporary increment above */ - port->fcoe_pending_queue.qlen--; - break; - } - /* undo temporary increment above */ - port->fcoe_pending_queue.qlen--; - } - - if (port->fcoe_pending_queue.qlen < FCOE_LOW_QUEUE_DEPTH) - lport->qfull = 0; - if (port->fcoe_pending_queue.qlen && !timer_pending(&port->timer)) - mod_timer(&port->timer, jiffies + 2); - port->fcoe_pending_queue_active = 0; -out: - if (port->fcoe_pending_queue.qlen > FCOE_MAX_QUEUE_DEPTH) - lport->qfull = 1; - spin_unlock_bh(&port->fcoe_pending_queue.lock); - return; -} - -/** * fcoe_dev_setup() - Setup the link change notification interface */ static void fcoe_dev_setup(void) @@ -1874,7 +1744,7 @@ static int fcoe_device_notification(struct notifier_block *notifier, list_del(&fcoe->list); port = lport_priv(fcoe->ctlr.lp); fcoe_interface_cleanup(fcoe); - schedule_work(&port->destroy_work); + queue_work(fcoe_wq, &port->destroy_work); goto out; break; case NETDEV_FEAT_CHANGE: @@ -1900,39 +1770,16 @@ out: } /** - * fcoe_if_to_netdev() - Parse a name buffer to get a net device - * @buffer: The name of the net device - * - * Returns: NULL or a ptr to net_device - */ -static struct net_device *fcoe_if_to_netdev(const char *buffer) -{ - char *cp; - char ifname[IFNAMSIZ + 2]; - - if (buffer) { - strlcpy(ifname, buffer, IFNAMSIZ); - cp = ifname + strlen(ifname); - while (--cp >= ifname && *cp == '\n') - *cp = '\0'; - return dev_get_by_name(&init_net, ifname); - } - return NULL; -} - -/** * fcoe_disable() - Disables a FCoE interface - * @buffer: The name of the Ethernet interface to be disabled - * @kp: The associated kernel parameter + * @netdev : The net_device object the Ethernet interface to create on * - * Called from sysfs. + * Called from fcoe transport. * * Returns: 0 for success */ -static int fcoe_disable(const char *buffer, struct kernel_param *kp) +static int fcoe_disable(struct net_device *netdev) { struct fcoe_interface *fcoe; - struct net_device *netdev; int rc = 0; mutex_lock(&fcoe_config_mutex); @@ -1948,16 +1795,9 @@ static int fcoe_disable(const char *buffer, struct kernel_param *kp) } #endif - netdev = fcoe_if_to_netdev(buffer); - if (!netdev) { - rc = -ENODEV; - goto out_nodev; - } - if (!rtnl_trylock()) { - dev_put(netdev); mutex_unlock(&fcoe_config_mutex); - return restart_syscall(); + return -ERESTARTSYS; } fcoe = fcoe_hostlist_lookup_port(netdev); @@ -1969,7 +1809,6 @@ static int fcoe_disable(const char *buffer, struct kernel_param *kp) } else rc = -ENODEV; - dev_put(netdev); out_nodev: mutex_unlock(&fcoe_config_mutex); return rc; @@ -1977,17 +1816,15 @@ out_nodev: /** * fcoe_enable() - Enables a FCoE interface - * @buffer: The name of the Ethernet interface to be enabled - * @kp: The associated kernel parameter + * @netdev : The net_device object the Ethernet interface to create on * - * Called from sysfs. + * Called from fcoe transport. * * Returns: 0 for success */ -static int fcoe_enable(const char *buffer, struct kernel_param *kp) +static int fcoe_enable(struct net_device *netdev) { struct fcoe_interface *fcoe; - struct net_device *netdev; int rc = 0; mutex_lock(&fcoe_config_mutex); @@ -2002,17 +1839,9 @@ static int fcoe_enable(const char *buffer, struct kernel_param *kp) goto out_nodev; } #endif - - netdev = fcoe_if_to_netdev(buffer); - if (!netdev) { - rc = -ENODEV; - goto out_nodev; - } - if (!rtnl_trylock()) { - dev_put(netdev); mutex_unlock(&fcoe_config_mutex); - return restart_syscall(); + return -ERESTARTSYS; } fcoe = fcoe_hostlist_lookup_port(netdev); @@ -2023,7 +1852,6 @@ static int fcoe_enable(const char *buffer, struct kernel_param *kp) else if (!fcoe_link_ok(fcoe->ctlr.lp)) fcoe_ctlr_link_up(&fcoe->ctlr); - dev_put(netdev); out_nodev: mutex_unlock(&fcoe_config_mutex); return rc; @@ -2031,17 +1859,15 @@ out_nodev: /** * fcoe_destroy() - Destroy a FCoE interface - * @buffer: The name of the Ethernet interface to be destroyed - * @kp: The associated kernel parameter + * @netdev : The net_device object the Ethernet interface to create on * - * Called from sysfs. + * Called from fcoe transport * * Returns: 0 for success */ -static int fcoe_destroy(const char *buffer, struct kernel_param *kp) +static int fcoe_destroy(struct net_device *netdev) { struct fcoe_interface *fcoe; - struct net_device *netdev; int rc = 0; mutex_lock(&fcoe_config_mutex); @@ -2056,32 +1882,21 @@ static int fcoe_destroy(const char *buffer, struct kernel_param *kp) goto out_nodev; } #endif - - netdev = fcoe_if_to_netdev(buffer); - if (!netdev) { - rc = -ENODEV; - goto out_nodev; - } - if (!rtnl_trylock()) { - dev_put(netdev); mutex_unlock(&fcoe_config_mutex); - return restart_syscall(); + return -ERESTARTSYS; } fcoe = fcoe_hostlist_lookup_port(netdev); if (!fcoe) { rtnl_unlock(); rc = -ENODEV; - goto out_putdev; + goto out_nodev; } fcoe_interface_cleanup(fcoe); list_del(&fcoe->list); /* RTNL mutex is dropped by fcoe_if_destroy */ fcoe_if_destroy(fcoe->ctlr.lp); - -out_putdev: - dev_put(netdev); out_nodev: mutex_unlock(&fcoe_config_mutex); return rc; @@ -2104,27 +1919,39 @@ static void fcoe_destroy_work(struct work_struct *work) } /** + * fcoe_match() - Check if the FCoE is supported on the given netdevice + * @netdev : The net_device object the Ethernet interface to create on + * + * Called from fcoe transport. + * + * Returns: always returns true as this is the default FCoE transport, + * i.e., support all netdevs. + */ +static bool fcoe_match(struct net_device *netdev) +{ + return true; +} + +/** * fcoe_create() - Create a fcoe interface - * @buffer: The name of the Ethernet interface to create on - * @kp: The associated kernel param + * @netdev : The net_device object the Ethernet interface to create on + * @fip_mode: The FIP mode for this creation * - * Called from sysfs. + * Called from fcoe transport * * Returns: 0 for success */ -static int fcoe_create(const char *buffer, struct kernel_param *kp) +static int fcoe_create(struct net_device *netdev, enum fip_state fip_mode) { - enum fip_state fip_mode = (enum fip_state)(long)kp->arg; int rc; struct fcoe_interface *fcoe; struct fc_lport *lport; - struct net_device *netdev; mutex_lock(&fcoe_config_mutex); if (!rtnl_trylock()) { mutex_unlock(&fcoe_config_mutex); - return restart_syscall(); + return -ERESTARTSYS; } #ifdef CONFIG_FCOE_MODULE @@ -2135,31 +1962,20 @@ static int fcoe_create(const char *buffer, struct kernel_param *kp) */ if (THIS_MODULE->state != MODULE_STATE_LIVE) { rc = -ENODEV; - goto out_nomod; - } -#endif - - if (!try_module_get(THIS_MODULE)) { - rc = -EINVAL; - goto out_nomod; - } - - netdev = fcoe_if_to_netdev(buffer); - if (!netdev) { - rc = -ENODEV; goto out_nodev; } +#endif /* look for existing lport */ if (fcoe_hostlist_lookup(netdev)) { rc = -EEXIST; - goto out_putdev; + goto out_nodev; } fcoe = fcoe_interface_create(netdev, fip_mode); - if (!fcoe) { - rc = -ENOMEM; - goto out_putdev; + if (IS_ERR(fcoe)) { + rc = PTR_ERR(fcoe); + goto out_nodev; } lport = fcoe_if_create(fcoe, &netdev->dev, 0); @@ -2188,18 +2004,13 @@ static int fcoe_create(const char *buffer, struct kernel_param *kp) * should be holding a reference taken in fcoe_if_create(). */ fcoe_interface_put(fcoe); - dev_put(netdev); rtnl_unlock(); mutex_unlock(&fcoe_config_mutex); return 0; out_free: fcoe_interface_put(fcoe); -out_putdev: - dev_put(netdev); out_nodev: - module_put(THIS_MODULE); -out_nomod: rtnl_unlock(); mutex_unlock(&fcoe_config_mutex); return rc; @@ -2214,8 +2025,7 @@ out_nomod: */ int fcoe_link_speed_update(struct fc_lport *lport) { - struct fcoe_port *port = lport_priv(lport); - struct net_device *netdev = port->fcoe->netdev; + struct net_device *netdev = fcoe_netdev(lport); struct ethtool_cmd ecmd = { ETHTOOL_GSET }; if (!dev_ethtool_get_settings(netdev, &ecmd)) { @@ -2246,8 +2056,7 @@ int fcoe_link_speed_update(struct fc_lport *lport) */ int fcoe_link_ok(struct fc_lport *lport) { - struct fcoe_port *port = lport_priv(lport); - struct net_device *netdev = port->fcoe->netdev; + struct net_device *netdev = fcoe_netdev(lport); if (netif_oper_up(netdev)) return 0; @@ -2311,24 +2120,6 @@ void fcoe_percpu_clean(struct fc_lport *lport) } /** - * fcoe_clean_pending_queue() - Dequeue a skb and free it - * @lport: The local port to dequeue a skb on - */ -void fcoe_clean_pending_queue(struct fc_lport *lport) -{ - struct fcoe_port *port = lport_priv(lport); - struct sk_buff *skb; - - spin_lock_bh(&port->fcoe_pending_queue.lock); - while ((skb = __skb_dequeue(&port->fcoe_pending_queue)) != NULL) { - spin_unlock_bh(&port->fcoe_pending_queue.lock); - kfree_skb(skb); - spin_lock_bh(&port->fcoe_pending_queue.lock); - } - spin_unlock_bh(&port->fcoe_pending_queue.lock); -} - -/** * fcoe_reset() - Reset a local port * @shost: The SCSI host associated with the local port to be reset * @@ -2395,12 +2186,24 @@ static int fcoe_hostlist_add(const struct fc_lport *lport) fcoe = fcoe_hostlist_lookup_port(fcoe_netdev(lport)); if (!fcoe) { port = lport_priv(lport); - fcoe = port->fcoe; + fcoe = port->priv; list_add_tail(&fcoe->list, &fcoe_hostlist); } return 0; } + +static struct fcoe_transport fcoe_sw_transport = { + .name = {FCOE_TRANSPORT_DEFAULT}, + .attached = false, + .list = LIST_HEAD_INIT(fcoe_sw_transport.list), + .match = fcoe_match, + .create = fcoe_create, + .destroy = fcoe_destroy, + .enable = fcoe_enable, + .disable = fcoe_disable, +}; + /** * fcoe_init() - Initialize fcoe.ko * @@ -2412,6 +2215,18 @@ static int __init fcoe_init(void) unsigned int cpu; int rc = 0; + fcoe_wq = alloc_workqueue("fcoe", 0, 0); + if (!fcoe_wq) + return -ENOMEM; + + /* register as a fcoe transport */ + rc = fcoe_transport_attach(&fcoe_sw_transport); + if (rc) { + printk(KERN_ERR "failed to register an fcoe transport, check " + "if libfcoe is loaded\n"); + return rc; + } + mutex_lock(&fcoe_config_mutex); for_each_possible_cpu(cpu) { @@ -2442,6 +2257,7 @@ out_free: fcoe_percpu_thread_destroy(cpu); } mutex_unlock(&fcoe_config_mutex); + destroy_workqueue(fcoe_wq); return rc; } module_init(fcoe_init); @@ -2467,7 +2283,7 @@ static void __exit fcoe_exit(void) list_del(&fcoe->list); port = lport_priv(fcoe->ctlr.lp); fcoe_interface_cleanup(fcoe); - schedule_work(&port->destroy_work); + queue_work(fcoe_wq, &port->destroy_work); } rtnl_unlock(); @@ -2478,16 +2294,21 @@ static void __exit fcoe_exit(void) mutex_unlock(&fcoe_config_mutex); - /* flush any asyncronous interface destroys, - * this should happen after the netdev notifier is unregistered */ - flush_scheduled_work(); - /* That will flush out all the N_Ports on the hostlist, but now we - * may have NPIV VN_Ports scheduled for destruction */ - flush_scheduled_work(); + /* + * destroy_work's may be chained but destroy_workqueue() + * can take care of them. Just kill the fcoe_wq. + */ + destroy_workqueue(fcoe_wq); - /* detach from scsi transport - * must happen after all destroys are done, therefor after the flush */ + /* + * Detaching from the scsi transport must happen after all + * destroys are done on the fcoe_wq. destroy_workqueue will + * enusre the fcoe_wq is flushed. + */ fcoe_if_exit(); + + /* detach from fcoe transport */ + fcoe_transport_detach(&fcoe_sw_transport); } module_exit(fcoe_exit); @@ -2559,7 +2380,7 @@ static struct fc_seq *fcoe_elsct_send(struct fc_lport *lport, u32 did, void *arg, u32 timeout) { struct fcoe_port *port = lport_priv(lport); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; struct fcoe_ctlr *fip = &fcoe->ctlr; struct fc_frame_header *fh = fc_frame_header_get(fp); @@ -2592,7 +2413,7 @@ static int fcoe_vport_create(struct fc_vport *vport, bool disabled) struct Scsi_Host *shost = vport_to_shost(vport); struct fc_lport *n_port = shost_priv(shost); struct fcoe_port *port = lport_priv(n_port); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; struct net_device *netdev = fcoe->netdev; struct fc_lport *vn_port; @@ -2632,7 +2453,7 @@ static int fcoe_vport_destroy(struct fc_vport *vport) mutex_lock(&n_port->lp_mutex); list_del(&vn_port->list); mutex_unlock(&n_port->lp_mutex); - schedule_work(&port->destroy_work); + queue_work(fcoe_wq, &port->destroy_work); return 0; } @@ -2736,7 +2557,7 @@ static void fcoe_set_port_id(struct fc_lport *lport, u32 port_id, struct fc_frame *fp) { struct fcoe_port *port = lport_priv(lport); - struct fcoe_interface *fcoe = port->fcoe; + struct fcoe_interface *fcoe = port->priv; if (fp && fc_frame_payload_op(fp) == ELS_FLOGI) fcoe_ctlr_recv_flogi(&fcoe->ctlr, lport, fp); diff --git a/drivers/scsi/fcoe/fcoe.h b/drivers/scsi/fcoe/fcoe.h index c69b2c56c2d1..d775128398e9 100644 --- a/drivers/scsi/fcoe/fcoe.h +++ b/drivers/scsi/fcoe/fcoe.h @@ -24,7 +24,7 @@ #include <linux/kthread.h> #define FCOE_MAX_QUEUE_DEPTH 256 -#define FCOE_LOW_QUEUE_DEPTH 32 +#define FCOE_MIN_QUEUE_DEPTH 32 #define FCOE_WORD_TO_BYTE 4 @@ -71,21 +71,6 @@ do { \ netdev->name, ##args);) /** - * struct fcoe_percpu_s - The per-CPU context for FCoE receive threads - * @thread: The thread context - * @fcoe_rx_list: The queue of pending packets to process - * @page: The memory page for calculating frame trailer CRCs - * @crc_eof_offset: The offset into the CRC page pointing to available - * memory for a new trailer - */ -struct fcoe_percpu_s { - struct task_struct *thread; - struct sk_buff_head fcoe_rx_list; - struct page *crc_eof_page; - int crc_eof_offset; -}; - -/** * struct fcoe_interface - A FCoE interface * @list: Handle for a list of FCoE interfaces * @netdev: The associated net device @@ -108,30 +93,6 @@ struct fcoe_interface { struct kref kref; }; -/** - * struct fcoe_port - The FCoE private structure - * @fcoe: The associated fcoe interface - * @lport: The associated local port - * @fcoe_pending_queue: The pending Rx queue of skbs - * @fcoe_pending_queue_active: Indicates if the pending queue is active - * @timer: The queue timer - * @destroy_work: Handle for work context - * (to prevent RTNL deadlocks) - * @data_srt_addr: Source address for data - * - * An instance of this structure is to be allocated along with the - * Scsi_Host and libfc fc_lport structures. - */ -struct fcoe_port { - struct fcoe_interface *fcoe; - struct fc_lport *lport; - struct sk_buff_head fcoe_pending_queue; - u8 fcoe_pending_queue_active; - struct timer_list timer; - struct work_struct destroy_work; - u8 data_src_addr[ETH_ALEN]; -}; - #define fcoe_from_ctlr(fip) container_of(fip, struct fcoe_interface, ctlr) /** @@ -140,7 +101,8 @@ struct fcoe_port { */ static inline struct net_device *fcoe_netdev(const struct fc_lport *lport) { - return ((struct fcoe_port *)lport_priv(lport))->fcoe->netdev; + return ((struct fcoe_interface *) + ((struct fcoe_port *)lport_priv(lport))->priv)->netdev; } #endif /* _FCOE_H_ */ diff --git a/drivers/scsi/fcoe/libfcoe.c b/drivers/scsi/fcoe/fcoe_ctlr.c index 625c6be25396..c93f007e702f 100644 --- a/drivers/scsi/fcoe/libfcoe.c +++ b/drivers/scsi/fcoe/fcoe_ctlr.c @@ -44,9 +44,7 @@ #include <scsi/libfc.h> #include <scsi/libfcoe.h> -MODULE_AUTHOR("Open-FCoE.org"); -MODULE_DESCRIPTION("FIP discovery protocol support for FCoE HBAs"); -MODULE_LICENSE("GPL v2"); +#include "libfcoe.h" #define FCOE_CTLR_MIN_FKA 500 /* min keep alive (mS) */ #define FCOE_CTLR_DEF_FKA FIP_DEF_FKA /* default keep alive (mS) */ @@ -66,31 +64,7 @@ static u8 fcoe_all_enode[ETH_ALEN] = FIP_ALL_ENODE_MACS; static u8 fcoe_all_vn2vn[ETH_ALEN] = FIP_ALL_VN2VN_MACS; static u8 fcoe_all_p2p[ETH_ALEN] = FIP_ALL_P2P_MACS; -unsigned int libfcoe_debug_logging; -module_param_named(debug_logging, libfcoe_debug_logging, int, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels"); - -#define LIBFCOE_LOGGING 0x01 /* General logging, not categorized */ -#define LIBFCOE_FIP_LOGGING 0x02 /* FIP logging */ - -#define LIBFCOE_CHECK_LOGGING(LEVEL, CMD) \ -do { \ - if (unlikely(libfcoe_debug_logging & LEVEL)) \ - do { \ - CMD; \ - } while (0); \ -} while (0) - -#define LIBFCOE_DBG(fmt, args...) \ - LIBFCOE_CHECK_LOGGING(LIBFCOE_LOGGING, \ - printk(KERN_INFO "libfcoe: " fmt, ##args);) - -#define LIBFCOE_FIP_DBG(fip, fmt, args...) \ - LIBFCOE_CHECK_LOGGING(LIBFCOE_FIP_LOGGING, \ - printk(KERN_INFO "host%d: fip: " fmt, \ - (fip)->lp->host->host_no, ##args);) - -static const char *fcoe_ctlr_states[] = { +static const char * const fcoe_ctlr_states[] = { [FIP_ST_DISABLED] = "DISABLED", [FIP_ST_LINK_WAIT] = "LINK_WAIT", [FIP_ST_AUTO] = "AUTO", @@ -308,8 +282,8 @@ static void fcoe_ctlr_solicit(struct fcoe_ctlr *fip, struct fcoe_fcf *fcf) struct fip_mac_desc mac; struct fip_wwn_desc wwnn; struct fip_size_desc size; - } __attribute__((packed)) desc; - } __attribute__((packed)) *sol; + } __packed desc; + } __packed * sol; u32 fcoe_size; skb = dev_alloc_skb(sizeof(*sol)); @@ -456,7 +430,7 @@ static void fcoe_ctlr_send_keep_alive(struct fcoe_ctlr *fip, struct ethhdr eth; struct fip_header fip; struct fip_mac_desc mac; - } __attribute__((packed)) *kal; + } __packed * kal; struct fip_vn_desc *vn; u32 len; struct fc_lport *lp; @@ -527,7 +501,7 @@ static int fcoe_ctlr_encaps(struct fcoe_ctlr *fip, struct fc_lport *lport, struct ethhdr eth; struct fip_header fip; struct fip_encaps encaps; - } __attribute__((packed)) *cap; + } __packed * cap; struct fc_frame_header *fh; struct fip_mac_desc *mac; struct fcoe_fcf *fcf; @@ -1819,7 +1793,7 @@ static void fcoe_ctlr_vn_send(struct fcoe_ctlr *fip, struct fip_mac_desc mac; struct fip_wwn_desc wwnn; struct fip_vn_desc vn; - } __attribute__((packed)) *frame; + } __packed * frame; struct fip_fc4_feat *ff; struct fip_size_desc *size; u32 fcp_feat; diff --git a/drivers/scsi/fcoe/fcoe_transport.c b/drivers/scsi/fcoe/fcoe_transport.c new file mode 100644 index 000000000000..745eb9a22d64 --- /dev/null +++ b/drivers/scsi/fcoe/fcoe_transport.c @@ -0,0 +1,726 @@ +/* + * Copyright(c) 2008 - 2011 Intel Corporation. All rights reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope 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., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + * + * Maintained at www.Open-FCoE.org + */ + +#include <linux/types.h> +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/list.h> +#include <linux/netdevice.h> +#include <linux/errno.h> +#include <linux/crc32.h> +#include <scsi/libfcoe.h> + +#include "libfcoe.h" + +MODULE_AUTHOR("Open-FCoE.org"); +MODULE_DESCRIPTION("FIP discovery protocol and FCoE transport for FCoE HBAs"); +MODULE_LICENSE("GPL v2"); + +static int fcoe_transport_create(const char *, struct kernel_param *); +static int fcoe_transport_destroy(const char *, struct kernel_param *); +static int fcoe_transport_show(char *buffer, const struct kernel_param *kp); +static struct fcoe_transport *fcoe_transport_lookup(struct net_device *device); +static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *device); +static int fcoe_transport_enable(const char *, struct kernel_param *); +static int fcoe_transport_disable(const char *, struct kernel_param *); + +static LIST_HEAD(fcoe_transports); +static LIST_HEAD(fcoe_netdevs); +static DEFINE_MUTEX(ft_mutex); + +unsigned int libfcoe_debug_logging; +module_param_named(debug_logging, libfcoe_debug_logging, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels"); + +module_param_call(show, NULL, fcoe_transport_show, NULL, S_IRUSR); +__MODULE_PARM_TYPE(show, "string"); +MODULE_PARM_DESC(show, " Show attached FCoE transports"); + +module_param_call(create, fcoe_transport_create, NULL, + (void *)FIP_MODE_FABRIC, S_IWUSR); +__MODULE_PARM_TYPE(create, "string"); +MODULE_PARM_DESC(create, " Creates fcoe instance on a ethernet interface"); + +module_param_call(create_vn2vn, fcoe_transport_create, NULL, + (void *)FIP_MODE_VN2VN, S_IWUSR); +__MODULE_PARM_TYPE(create_vn2vn, "string"); +MODULE_PARM_DESC(create_vn2vn, " Creates a VN_node to VN_node FCoE instance " + "on an Ethernet interface"); + +module_param_call(destroy, fcoe_transport_destroy, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(destroy, "string"); +MODULE_PARM_DESC(destroy, " Destroys fcoe instance on a ethernet interface"); + +module_param_call(enable, fcoe_transport_enable, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(enable, "string"); +MODULE_PARM_DESC(enable, " Enables fcoe on a ethernet interface."); + +module_param_call(disable, fcoe_transport_disable, NULL, NULL, S_IWUSR); +__MODULE_PARM_TYPE(disable, "string"); +MODULE_PARM_DESC(disable, " Disables fcoe on a ethernet interface."); + +/** + * fcoe_fc_crc() - Calculates the CRC for a given frame + * @fp: The frame to be checksumed + * + * This uses crc32() routine to calculate the CRC for a frame + * + * Return: The 32 bit CRC value + */ +u32 fcoe_fc_crc(struct fc_frame *fp) +{ + struct sk_buff *skb = fp_skb(fp); + struct skb_frag_struct *frag; + unsigned char *data; + unsigned long off, len, clen; + u32 crc; + unsigned i; + + crc = crc32(~0, skb->data, skb_headlen(skb)); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + frag = &skb_shinfo(skb)->frags[i]; + off = frag->page_offset; + len = frag->size; + while (len > 0) { + clen = min(len, PAGE_SIZE - (off & ~PAGE_MASK)); + data = kmap_atomic(frag->page + (off >> PAGE_SHIFT), + KM_SKB_DATA_SOFTIRQ); + crc = crc32(crc, data + (off & ~PAGE_MASK), clen); + kunmap_atomic(data, KM_SKB_DATA_SOFTIRQ); + off += clen; + len -= clen; + } + } + return crc; +} +EXPORT_SYMBOL_GPL(fcoe_fc_crc); + +/** + * fcoe_start_io() - Start FCoE I/O + * @skb: The packet to be transmitted + * + * This routine is called from the net device to start transmitting + * FCoE packets. + * + * Returns: 0 for success + */ +int fcoe_start_io(struct sk_buff *skb) +{ + struct sk_buff *nskb; + int rc; + + nskb = skb_clone(skb, GFP_ATOMIC); + if (!nskb) + return -ENOMEM; + rc = dev_queue_xmit(nskb); + if (rc != 0) + return rc; + kfree_skb(skb); + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_start_io); + + +/** + * fcoe_clean_pending_queue() - Dequeue a skb and free it + * @lport: The local port to dequeue a skb on + */ +void fcoe_clean_pending_queue(struct fc_lport *lport) +{ + struct fcoe_port *port = lport_priv(lport); + struct sk_buff *skb; + + spin_lock_bh(&port->fcoe_pending_queue.lock); + while ((skb = __skb_dequeue(&port->fcoe_pending_queue)) != NULL) { + spin_unlock_bh(&port->fcoe_pending_queue.lock); + kfree_skb(skb); + spin_lock_bh(&port->fcoe_pending_queue.lock); + } + spin_unlock_bh(&port->fcoe_pending_queue.lock); +} +EXPORT_SYMBOL_GPL(fcoe_clean_pending_queue); + +/** + * fcoe_check_wait_queue() - Attempt to clear the transmit backlog + * @lport: The local port whose backlog is to be cleared + * + * This empties the wait_queue, dequeues the head of the wait_queue queue + * and calls fcoe_start_io() for each packet. If all skb have been + * transmitted it returns the qlen. If an error occurs it restores + * wait_queue (to try again later) and returns -1. + * + * The wait_queue is used when the skb transmit fails. The failed skb + * will go in the wait_queue which will be emptied by the timer function or + * by the next skb transmit. + */ +void fcoe_check_wait_queue(struct fc_lport *lport, struct sk_buff *skb) +{ + struct fcoe_port *port = lport_priv(lport); + int rc; + + spin_lock_bh(&port->fcoe_pending_queue.lock); + + if (skb) + __skb_queue_tail(&port->fcoe_pending_queue, skb); + + if (port->fcoe_pending_queue_active) + goto out; + port->fcoe_pending_queue_active = 1; + + while (port->fcoe_pending_queue.qlen) { + /* keep qlen > 0 until fcoe_start_io succeeds */ + port->fcoe_pending_queue.qlen++; + skb = __skb_dequeue(&port->fcoe_pending_queue); + + spin_unlock_bh(&port->fcoe_pending_queue.lock); + rc = fcoe_start_io(skb); + spin_lock_bh(&port->fcoe_pending_queue.lock); + + if (rc) { + __skb_queue_head(&port->fcoe_pending_queue, skb); + /* undo temporary increment above */ + port->fcoe_pending_queue.qlen--; + break; + } + /* undo temporary increment above */ + port->fcoe_pending_queue.qlen--; + } + + if (port->fcoe_pending_queue.qlen < port->min_queue_depth) + lport->qfull = 0; + if (port->fcoe_pending_queue.qlen && !timer_pending(&port->timer)) + mod_timer(&port->timer, jiffies + 2); + port->fcoe_pending_queue_active = 0; +out: + if (port->fcoe_pending_queue.qlen > port->max_queue_depth) + lport->qfull = 1; + spin_unlock_bh(&port->fcoe_pending_queue.lock); +} +EXPORT_SYMBOL_GPL(fcoe_check_wait_queue); + +/** + * fcoe_queue_timer() - The fcoe queue timer + * @lport: The local port + * + * Calls fcoe_check_wait_queue on timeout + */ +void fcoe_queue_timer(ulong lport) +{ + fcoe_check_wait_queue((struct fc_lport *)lport, NULL); +} +EXPORT_SYMBOL_GPL(fcoe_queue_timer); + +/** + * fcoe_get_paged_crc_eof() - Allocate a page to be used for the trailer CRC + * @skb: The packet to be transmitted + * @tlen: The total length of the trailer + * @fps: The fcoe context + * + * This routine allocates a page for frame trailers. The page is re-used if + * there is enough room left on it for the current trailer. If there isn't + * enough buffer left a new page is allocated for the trailer. Reference to + * the page from this function as well as the skbs using the page fragments + * ensure that the page is freed at the appropriate time. + * + * Returns: 0 for success + */ +int fcoe_get_paged_crc_eof(struct sk_buff *skb, int tlen, + struct fcoe_percpu_s *fps) +{ + struct page *page; + + page = fps->crc_eof_page; + if (!page) { + page = alloc_page(GFP_ATOMIC); + if (!page) + return -ENOMEM; + + fps->crc_eof_page = page; + fps->crc_eof_offset = 0; + } + + get_page(page); + skb_fill_page_desc(skb, skb_shinfo(skb)->nr_frags, page, + fps->crc_eof_offset, tlen); + skb->len += tlen; + skb->data_len += tlen; + skb->truesize += tlen; + fps->crc_eof_offset += sizeof(struct fcoe_crc_eof); + + if (fps->crc_eof_offset >= PAGE_SIZE) { + fps->crc_eof_page = NULL; + fps->crc_eof_offset = 0; + put_page(page); + } + + return 0; +} +EXPORT_SYMBOL_GPL(fcoe_get_paged_crc_eof); + +/** + * fcoe_transport_lookup - find an fcoe transport that matches a netdev + * @netdev: The netdev to look for from all attached transports + * + * Returns : ptr to the fcoe transport that supports this netdev or NULL + * if not found. + * + * The ft_mutex should be held when this is called + */ +static struct fcoe_transport *fcoe_transport_lookup(struct net_device *netdev) +{ + struct fcoe_transport *ft = NULL; + + list_for_each_entry(ft, &fcoe_transports, list) + if (ft->match && ft->match(netdev)) + return ft; + return NULL; +} + +/** + * fcoe_transport_attach - Attaches an FCoE transport + * @ft: The fcoe transport to be attached + * + * Returns : 0 for success + */ +int fcoe_transport_attach(struct fcoe_transport *ft) +{ + int rc = 0; + + mutex_lock(&ft_mutex); + if (ft->attached) { + LIBFCOE_TRANSPORT_DBG("transport %s already attached\n", + ft->name); + rc = -EEXIST; + goto out_attach; + } + + /* Add default transport to the tail */ + if (strcmp(ft->name, FCOE_TRANSPORT_DEFAULT)) + list_add(&ft->list, &fcoe_transports); + else + list_add_tail(&ft->list, &fcoe_transports); + + ft->attached = true; + LIBFCOE_TRANSPORT_DBG("attaching transport %s\n", ft->name); + +out_attach: + mutex_unlock(&ft_mutex); + return rc; +} +EXPORT_SYMBOL(fcoe_transport_attach); + +/** + * fcoe_transport_attach - Detaches an FCoE transport + * @ft: The fcoe transport to be attached + * + * Returns : 0 for success + */ +int fcoe_transport_detach(struct fcoe_transport *ft) +{ + int rc = 0; + + mutex_lock(&ft_mutex); + if (!ft->attached) { + LIBFCOE_TRANSPORT_DBG("transport %s already detached\n", + ft->name); + rc = -ENODEV; + goto out_attach; + } + + list_del(&ft->list); + ft->attached = false; + LIBFCOE_TRANSPORT_DBG("detaching transport %s\n", ft->name); + +out_attach: + mutex_unlock(&ft_mutex); + return rc; + +} +EXPORT_SYMBOL(fcoe_transport_detach); + +static int fcoe_transport_show(char *buffer, const struct kernel_param *kp) +{ + int i, j; + struct fcoe_transport *ft = NULL; + + i = j = sprintf(buffer, "Attached FCoE transports:"); + mutex_lock(&ft_mutex); + list_for_each_entry(ft, &fcoe_transports, list) { + i += snprintf(&buffer[i], IFNAMSIZ, "%s ", ft->name); + if (i >= PAGE_SIZE) + break; + } + mutex_unlock(&ft_mutex); + if (i == j) + i += snprintf(&buffer[i], IFNAMSIZ, "none"); + return i; +} + +static int __init fcoe_transport_init(void) +{ + return 0; +} + +static int __exit fcoe_transport_exit(void) +{ + struct fcoe_transport *ft; + + mutex_lock(&ft_mutex); + list_for_each_entry(ft, &fcoe_transports, list) + printk(KERN_ERR "FCoE transport %s is still attached!\n", + ft->name); + mutex_unlock(&ft_mutex); + return 0; +} + + +static int fcoe_add_netdev_mapping(struct net_device *netdev, + struct fcoe_transport *ft) +{ + struct fcoe_netdev_mapping *nm; + + nm = kmalloc(sizeof(*nm), GFP_KERNEL); + if (!nm) { + printk(KERN_ERR "Unable to allocate netdev_mapping"); + return -ENOMEM; + } + + nm->netdev = netdev; + nm->ft = ft; + + list_add(&nm->list, &fcoe_netdevs); + return 0; +} + + +static void fcoe_del_netdev_mapping(struct net_device *netdev) +{ + struct fcoe_netdev_mapping *nm = NULL, *tmp; + + list_for_each_entry_safe(nm, tmp, &fcoe_netdevs, list) { + if (nm->netdev == netdev) { + list_del(&nm->list); + kfree(nm); + return; + } + } +} + + +/** + * fcoe_netdev_map_lookup - find the fcoe transport that matches the netdev on which + * it was created + * + * Returns : ptr to the fcoe transport that supports this netdev or NULL + * if not found. + * + * The ft_mutex should be held when this is called + */ +static struct fcoe_transport *fcoe_netdev_map_lookup(struct net_device *netdev) +{ + struct fcoe_transport *ft = NULL; + struct fcoe_netdev_mapping *nm; + + list_for_each_entry(nm, &fcoe_netdevs, list) { + if (netdev == nm->netdev) { + ft = nm->ft; + return ft; + } + } + + return NULL; +} + +/** + * fcoe_if_to_netdev() - Parse a name buffer to get a net device + * @buffer: The name of the net device + * + * Returns: NULL or a ptr to net_device + */ +static struct net_device *fcoe_if_to_netdev(const char *buffer) +{ + char *cp; + char ifname[IFNAMSIZ + 2]; + + if (buffer) { + strlcpy(ifname, buffer, IFNAMSIZ); + cp = ifname + strlen(ifname); + while (--cp >= ifname && *cp == '\n') + *cp = '\0'; + return dev_get_by_name(&init_net, ifname); + } + return NULL; +} + +/** + * fcoe_transport_create() - Create a fcoe interface + * @buffer: The name of the Ethernet interface to create on + * @kp: The associated kernel param + * + * Called from sysfs. This holds the ft_mutex while calling the + * registered fcoe transport's create function. + * + * Returns: 0 for success + */ +static int fcoe_transport_create(const char *buffer, struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + enum fip_state fip_mode = (enum fip_state)(long)kp->arg; + + if (!mutex_trylock(&ft_mutex)) + return restart_syscall(); + +#ifdef CONFIG_LIBFCOE_MODULE + /* + * Make sure the module has been initialized, and is not about to be + * removed. Module parameter sysfs files are writable before the + * module_init function is called and after module_exit. + */ + if (THIS_MODULE->state != MODULE_STATE_LIVE) + goto out_nodev; +#endif + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) { + LIBFCOE_TRANSPORT_DBG("Invalid device %s.\n", buffer); + goto out_nodev; + } + + ft = fcoe_netdev_map_lookup(netdev); + if (ft) { + LIBFCOE_TRANSPORT_DBG("transport %s already has existing " + "FCoE instance on %s.\n", + ft->name, netdev->name); + rc = -EEXIST; + goto out_putdev; + } + + ft = fcoe_transport_lookup(netdev); + if (!ft) { + LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n", + netdev->name); + goto out_putdev; + } + + rc = fcoe_add_netdev_mapping(netdev, ft); + if (rc) { + LIBFCOE_TRANSPORT_DBG("failed to add new netdev mapping " + "for FCoE transport %s for %s.\n", + ft->name, netdev->name); + goto out_putdev; + } + + /* pass to transport create */ + rc = ft->create ? ft->create(netdev, fip_mode) : -ENODEV; + if (rc) + fcoe_del_netdev_mapping(netdev); + + LIBFCOE_TRANSPORT_DBG("transport %s %s to create fcoe on %s.\n", + ft->name, (rc) ? "failed" : "succeeded", + netdev->name); + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + if (rc == -ERESTARTSYS) + return restart_syscall(); + else + return rc; +} + +/** + * fcoe_transport_destroy() - Destroy a FCoE interface + * @buffer: The name of the Ethernet interface to be destroyed + * @kp: The associated kernel parameter + * + * Called from sysfs. This holds the ft_mutex while calling the + * registered fcoe transport's destroy function. + * + * Returns: 0 for success + */ +static int fcoe_transport_destroy(const char *buffer, struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + + if (!mutex_trylock(&ft_mutex)) + return restart_syscall(); + +#ifdef CONFIG_LIBFCOE_MODULE + /* + * Make sure the module has been initialized, and is not about to be + * removed. Module parameter sysfs files are writable before the + * module_init function is called and after module_exit. + */ + if (THIS_MODULE->state != MODULE_STATE_LIVE) + goto out_nodev; +#endif + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) { + LIBFCOE_TRANSPORT_DBG("invalid device %s.\n", buffer); + goto out_nodev; + } + + ft = fcoe_netdev_map_lookup(netdev); + if (!ft) { + LIBFCOE_TRANSPORT_DBG("no FCoE transport found for %s.\n", + netdev->name); + goto out_putdev; + } + + /* pass to transport destroy */ + rc = ft->destroy ? ft->destroy(netdev) : -ENODEV; + fcoe_del_netdev_mapping(netdev); + LIBFCOE_TRANSPORT_DBG("transport %s %s to destroy fcoe on %s.\n", + ft->name, (rc) ? "failed" : "succeeded", + netdev->name); + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + + if (rc == -ERESTARTSYS) + return restart_syscall(); + else + return rc; +} + +/** + * fcoe_transport_disable() - Disables a FCoE interface + * @buffer: The name of the Ethernet interface to be disabled + * @kp: The associated kernel parameter + * + * Called from sysfs. + * + * Returns: 0 for success + */ +static int fcoe_transport_disable(const char *buffer, struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + + if (!mutex_trylock(&ft_mutex)) + return restart_syscall(); + +#ifdef CONFIG_LIBFCOE_MODULE + /* + * Make sure the module has been initialized, and is not about to be + * removed. Module parameter sysfs files are writable before the + * module_init function is called and after module_exit. + */ + if (THIS_MODULE->state != MODULE_STATE_LIVE) + goto out_nodev; +#endif + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) + goto out_nodev; + + ft = fcoe_netdev_map_lookup(netdev); + if (!ft) + goto out_putdev; + + rc = ft->disable ? ft->disable(netdev) : -ENODEV; + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + + if (rc == -ERESTARTSYS) + return restart_syscall(); + else + return rc; +} + +/** + * fcoe_transport_enable() - Enables a FCoE interface + * @buffer: The name of the Ethernet interface to be enabled + * @kp: The associated kernel parameter + * + * Called from sysfs. + * + * Returns: 0 for success + */ +static int fcoe_transport_enable(const char *buffer, struct kernel_param *kp) +{ + int rc = -ENODEV; + struct net_device *netdev = NULL; + struct fcoe_transport *ft = NULL; + + if (!mutex_trylock(&ft_mutex)) + return restart_syscall(); + +#ifdef CONFIG_LIBFCOE_MODULE + /* + * Make sure the module has been initialized, and is not about to be + * removed. Module parameter sysfs files are writable before the + * module_init function is called and after module_exit. + */ + if (THIS_MODULE->state != MODULE_STATE_LIVE) + goto out_nodev; +#endif + + netdev = fcoe_if_to_netdev(buffer); + if (!netdev) + goto out_nodev; + + ft = fcoe_netdev_map_lookup(netdev); + if (!ft) + goto out_putdev; + + rc = ft->enable ? ft->enable(netdev) : -ENODEV; + +out_putdev: + dev_put(netdev); +out_nodev: + mutex_unlock(&ft_mutex); + if (rc == -ERESTARTSYS) + return restart_syscall(); + else + return rc; +} + +/** + * libfcoe_init() - Initialization routine for libfcoe.ko + */ +static int __init libfcoe_init(void) +{ + fcoe_transport_init(); + + return 0; +} +module_init(libfcoe_init); + +/** + * libfcoe_exit() - Tear down libfcoe.ko + */ +static void __exit libfcoe_exit(void) +{ + fcoe_transport_exit(); +} +module_exit(libfcoe_exit); diff --git a/drivers/scsi/fcoe/libfcoe.h b/drivers/scsi/fcoe/libfcoe.h new file mode 100644 index 000000000000..6af5fc3a17d8 --- /dev/null +++ b/drivers/scsi/fcoe/libfcoe.h @@ -0,0 +1,31 @@ +#ifndef _FCOE_LIBFCOE_H_ +#define _FCOE_LIBFCOE_H_ + +extern unsigned int libfcoe_debug_logging; +#define LIBFCOE_LOGGING 0x01 /* General logging, not categorized */ +#define LIBFCOE_FIP_LOGGING 0x02 /* FIP logging */ +#define LIBFCOE_TRANSPORT_LOGGING 0x04 /* FCoE transport logging */ + +#define LIBFCOE_CHECK_LOGGING(LEVEL, CMD) \ +do { \ + if (unlikely(libfcoe_debug_logging & LEVEL)) \ + do { \ + CMD; \ + } while (0); \ +} while (0) + +#define LIBFCOE_DBG(fmt, args...) \ + LIBFCOE_CHECK_LOGGING(LIBFCOE_LOGGING, \ + printk(KERN_INFO "libfcoe: " fmt, ##args);) + +#define LIBFCOE_FIP_DBG(fip, fmt, args...) \ + LIBFCOE_CHECK_LOGGING(LIBFCOE_FIP_LOGGING, \ + printk(KERN_INFO "host%d: fip: " fmt, \ + (fip)->lp->host->host_no, ##args);) + +#define LIBFCOE_TRANSPORT_DBG(fmt, args...) \ + LIBFCOE_CHECK_LOGGING(LIBFCOE_TRANSPORT_LOGGING, \ + printk(KERN_INFO "%s: " fmt, \ + __func__, ##args);) + +#endif /* _FCOE_LIBFCOE_H_ */ diff --git a/drivers/scsi/fnic/fnic.h b/drivers/scsi/fnic/fnic.h index 92f185081e62..671cde9d4060 100644 --- a/drivers/scsi/fnic/fnic.h +++ b/drivers/scsi/fnic/fnic.h @@ -37,7 +37,7 @@ #define DRV_NAME "fnic" #define DRV_DESCRIPTION "Cisco FCoE HBA Driver" -#define DRV_VERSION "1.4.0.145" +#define DRV_VERSION "1.5.0.1" #define PFX DRV_NAME ": " #define DFX DRV_NAME "%d: " diff --git a/drivers/scsi/fnic/vnic_dev.c b/drivers/scsi/fnic/vnic_dev.c index db710148d156..b576be734e2e 100644 --- a/drivers/scsi/fnic/vnic_dev.c +++ b/drivers/scsi/fnic/vnic_dev.c @@ -654,7 +654,7 @@ void vnic_dev_unregister(struct vnic_dev *vdev) vdev->linkstatus_pa); if (vdev->stats) pci_free_consistent(vdev->pdev, - sizeof(struct vnic_dev), + sizeof(struct vnic_stats), vdev->stats, vdev->stats_pa); if (vdev->fw_info) pci_free_consistent(vdev->pdev, diff --git a/drivers/scsi/hpsa.c b/drivers/scsi/hpsa.c index 12deffccb8da..c30591f84a06 100644 --- a/drivers/scsi/hpsa.c +++ b/drivers/scsi/hpsa.c @@ -74,6 +74,10 @@ static int hpsa_allow_any; module_param(hpsa_allow_any, int, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(hpsa_allow_any, "Allow hpsa driver to access unknown HP Smart Array hardware"); +static int hpsa_simple_mode; +module_param(hpsa_simple_mode, int, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(hpsa_simple_mode, + "Use 'simple mode' rather than 'performant mode'"); /* define the PCI info for the cards we can control */ static const struct pci_device_id hpsa_pci_device_id[] = { @@ -155,6 +159,10 @@ static ssize_t unique_id_show(struct device *dev, struct device_attribute *attr, char *buf); static ssize_t host_show_firmware_revision(struct device *dev, struct device_attribute *attr, char *buf); +static ssize_t host_show_commands_outstanding(struct device *dev, + struct device_attribute *attr, char *buf); +static ssize_t host_show_transport_mode(struct device *dev, + struct device_attribute *attr, char *buf); static void hpsa_update_scsi_devices(struct ctlr_info *h, int hostno); static ssize_t host_store_rescan(struct device *dev, struct device_attribute *attr, const char *buf, size_t count); @@ -173,6 +181,10 @@ static int __devinit hpsa_find_cfg_addrs(struct pci_dev *pdev, static int __devinit hpsa_pci_find_memory_BAR(struct pci_dev *pdev, unsigned long *memory_bar); static int __devinit hpsa_lookup_board_id(struct pci_dev *pdev, u32 *board_id); +static int __devinit hpsa_wait_for_board_state(struct pci_dev *pdev, + void __iomem *vaddr, int wait_for_ready); +#define BOARD_NOT_READY 0 +#define BOARD_READY 1 static DEVICE_ATTR(raid_level, S_IRUGO, raid_level_show, NULL); static DEVICE_ATTR(lunid, S_IRUGO, lunid_show, NULL); @@ -180,6 +192,10 @@ static DEVICE_ATTR(unique_id, S_IRUGO, unique_id_show, NULL); static DEVICE_ATTR(rescan, S_IWUSR, NULL, host_store_rescan); static DEVICE_ATTR(firmware_revision, S_IRUGO, host_show_firmware_revision, NULL); +static DEVICE_ATTR(commands_outstanding, S_IRUGO, + host_show_commands_outstanding, NULL); +static DEVICE_ATTR(transport_mode, S_IRUGO, + host_show_transport_mode, NULL); static struct device_attribute *hpsa_sdev_attrs[] = { &dev_attr_raid_level, @@ -191,6 +207,8 @@ static struct device_attribute *hpsa_sdev_attrs[] = { static struct device_attribute *hpsa_shost_attrs[] = { &dev_attr_rescan, &dev_attr_firmware_revision, + &dev_attr_commands_outstanding, + &dev_attr_transport_mode, NULL, }; @@ -291,17 +309,38 @@ static ssize_t host_show_firmware_revision(struct device *dev, fwrev[0], fwrev[1], fwrev[2], fwrev[3]); } +static ssize_t host_show_commands_outstanding(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct ctlr_info *h = shost_to_hba(shost); + + return snprintf(buf, 20, "%d\n", h->commands_outstanding); +} + +static ssize_t host_show_transport_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ctlr_info *h; + struct Scsi_Host *shost = class_to_shost(dev); + + h = shost_to_hba(shost); + return snprintf(buf, 20, "%s\n", + h->transMethod & CFGTBL_Trans_Performant ? + "performant" : "simple"); +} + /* Enqueuing and dequeuing functions for cmdlists. */ -static inline void addQ(struct hlist_head *list, struct CommandList *c) +static inline void addQ(struct list_head *list, struct CommandList *c) { - hlist_add_head(&c->list, list); + list_add_tail(&c->list, list); } static inline u32 next_command(struct ctlr_info *h) { u32 a; - if (unlikely(h->transMethod != CFGTBL_Trans_Performant)) + if (unlikely(!(h->transMethod & CFGTBL_Trans_Performant))) return h->access.command_completed(h); if ((*(h->reply_pool_head) & 1) == (h->reply_pool_wraparound)) { @@ -325,7 +364,7 @@ static inline u32 next_command(struct ctlr_info *h) */ static void set_performant_mode(struct ctlr_info *h, struct CommandList *c) { - if (likely(h->transMethod == CFGTBL_Trans_Performant)) + if (likely(h->transMethod & CFGTBL_Trans_Performant)) c->busaddr |= 1 | (h->blockFetchTable[c->Header.SGList] << 1); } @@ -344,9 +383,9 @@ static void enqueue_cmd_and_start_io(struct ctlr_info *h, static inline void removeQ(struct CommandList *c) { - if (WARN_ON(hlist_unhashed(&c->list))) + if (WARN_ON(list_empty(&c->list))) return; - hlist_del_init(&c->list); + list_del_init(&c->list); } static inline int is_hba_lunid(unsigned char scsi3addr[]) @@ -1130,6 +1169,10 @@ static void complete_scsi_command(struct CommandList *cp, cmd->result = DID_TIME_OUT << 16; dev_warn(&h->pdev->dev, "cp %p timedout\n", cp); break; + case CMD_UNABORTABLE: + cmd->result = DID_ERROR << 16; + dev_warn(&h->pdev->dev, "Command unabortable\n"); + break; default: cmd->result = DID_ERROR << 16; dev_warn(&h->pdev->dev, "cp %p returned unknown status %x\n", @@ -1160,7 +1203,7 @@ static int hpsa_scsi_detect(struct ctlr_info *h) sh->sg_tablesize = h->maxsgentries; h->scsi_host = sh; sh->hostdata[0] = (unsigned long) h; - sh->irq = h->intr[PERF_MODE_INT]; + sh->irq = h->intr[h->intr_mode]; sh->unique_id = sh->irq; error = scsi_add_host(sh, &h->pdev->dev); if (error) @@ -1295,6 +1338,9 @@ static void hpsa_scsi_interpret_error(struct CommandList *cp) case CMD_TIMEOUT: dev_warn(d, "cp %p timed out\n", cp); break; + case CMD_UNABORTABLE: + dev_warn(d, "Command unabortable\n"); + break; default: dev_warn(d, "cp %p returned unknown status %x\n", cp, ei->CommandStatus); @@ -1595,6 +1641,8 @@ static int add_msa2xxx_enclosure_device(struct ctlr_info *h, if (lun == 0) /* if lun is 0, then obviously we have a lun 0. */ return 0; + memset(scsi3addr, 0, 8); + scsi3addr[3] = target; if (is_hba_lunid(scsi3addr)) return 0; /* Don't add the RAID controller here. */ @@ -1609,8 +1657,6 @@ static int add_msa2xxx_enclosure_device(struct ctlr_info *h, return 0; } - memset(scsi3addr, 0, 8); - scsi3addr[3] = target; if (hpsa_update_device_info(h, scsi3addr, this_device)) return 0; (*nmsa2xxx_enclosures)++; @@ -2199,7 +2245,7 @@ static struct CommandList *cmd_alloc(struct ctlr_info *h) c->cmdindex = i; - INIT_HLIST_NODE(&c->list); + INIT_LIST_HEAD(&c->list); c->busaddr = (u32) cmd_dma_handle; temp64.val = (u64) err_dma_handle; c->ErrDesc.Addr.lower = temp64.val32.lower; @@ -2237,7 +2283,7 @@ static struct CommandList *cmd_special_alloc(struct ctlr_info *h) } memset(c->err_info, 0, sizeof(*c->err_info)); - INIT_HLIST_NODE(&c->list); + INIT_LIST_HEAD(&c->list); c->busaddr = (u32) cmd_dma_handle; temp64.val = (u64) err_dma_handle; c->ErrDesc.Addr.lower = temp64.val32.lower; @@ -2267,7 +2313,7 @@ static void cmd_special_free(struct ctlr_info *h, struct CommandList *c) pci_free_consistent(h->pdev, sizeof(*c->err_info), c->err_info, (dma_addr_t) temp64.val); pci_free_consistent(h->pdev, sizeof(*c), - c, (dma_addr_t) c->busaddr); + c, (dma_addr_t) (c->busaddr & DIRECT_LOOKUP_MASK)); } #ifdef CONFIG_COMPAT @@ -2281,6 +2327,7 @@ static int hpsa_ioctl32_passthru(struct scsi_device *dev, int cmd, void *arg) int err; u32 cp; + memset(&arg64, 0, sizeof(arg64)); err = 0; err |= copy_from_user(&arg64.LUN_info, &arg32->LUN_info, sizeof(arg64.LUN_info)); @@ -2317,6 +2364,7 @@ static int hpsa_ioctl32_big_passthru(struct scsi_device *dev, int err; u32 cp; + memset(&arg64, 0, sizeof(arg64)); err = 0; err |= copy_from_user(&arg64.LUN_info, &arg32->LUN_info, sizeof(arg64.LUN_info)); @@ -2433,15 +2481,17 @@ static int hpsa_passthru_ioctl(struct ctlr_info *h, void __user *argp) buff = kmalloc(iocommand.buf_size, GFP_KERNEL); if (buff == NULL) return -EFAULT; - } - if (iocommand.Request.Type.Direction == XFER_WRITE) { - /* Copy the data into the buffer we created */ - if (copy_from_user(buff, iocommand.buf, iocommand.buf_size)) { - kfree(buff); - return -EFAULT; + if (iocommand.Request.Type.Direction == XFER_WRITE) { + /* Copy the data into the buffer we created */ + if (copy_from_user(buff, iocommand.buf, + iocommand.buf_size)) { + kfree(buff); + return -EFAULT; + } + } else { + memset(buff, 0, iocommand.buf_size); } - } else - memset(buff, 0, iocommand.buf_size); + } c = cmd_special_alloc(h); if (c == NULL) { kfree(buff); @@ -2487,8 +2537,8 @@ static int hpsa_passthru_ioctl(struct ctlr_info *h, void __user *argp) cmd_special_free(h, c); return -EFAULT; } - - if (iocommand.Request.Type.Direction == XFER_READ) { + if (iocommand.Request.Type.Direction == XFER_READ && + iocommand.buf_size > 0) { /* Copy the data out of the buffer we created */ if (copy_to_user(iocommand.buf, buff, iocommand.buf_size)) { kfree(buff); @@ -2581,14 +2631,7 @@ static int hpsa_big_passthru_ioctl(struct ctlr_info *h, void __user *argp) } c->cmd_type = CMD_IOCTL_PEND; c->Header.ReplyQueue = 0; - - if (ioc->buf_size > 0) { - c->Header.SGList = sg_used; - c->Header.SGTotal = sg_used; - } else { - c->Header.SGList = 0; - c->Header.SGTotal = 0; - } + c->Header.SGList = c->Header.SGTotal = sg_used; memcpy(&c->Header.LUN, &ioc->LUN_info, sizeof(c->Header.LUN)); c->Header.Tag.lower = c->busaddr; memcpy(&c->Request, &ioc->Request, sizeof(c->Request)); @@ -2605,7 +2648,8 @@ static int hpsa_big_passthru_ioctl(struct ctlr_info *h, void __user *argp) } } hpsa_scsi_do_simple_cmd_core(h, c); - hpsa_pci_unmap(h->pdev, c, sg_used, PCI_DMA_BIDIRECTIONAL); + if (sg_used) + hpsa_pci_unmap(h->pdev, c, sg_used, PCI_DMA_BIDIRECTIONAL); check_ioctl_unit_attention(h, c); /* Copy the error information out */ memcpy(&ioc->error_info, c->err_info, sizeof(ioc->error_info)); @@ -2614,7 +2658,7 @@ static int hpsa_big_passthru_ioctl(struct ctlr_info *h, void __user *argp) status = -EFAULT; goto cleanup1; } - if (ioc->Request.Type.Direction == XFER_READ) { + if (ioc->Request.Type.Direction == XFER_READ && ioc->buf_size > 0) { /* Copy the data out of the buffer we created */ BYTE __user *ptr = ioc->buf; for (i = 0; i < sg_used; i++) { @@ -2810,8 +2854,8 @@ static void start_io(struct ctlr_info *h) { struct CommandList *c; - while (!hlist_empty(&h->reqQ)) { - c = hlist_entry(h->reqQ.first, struct CommandList, list); + while (!list_empty(&h->reqQ)) { + c = list_entry(h->reqQ.next, struct CommandList, list); /* can't do anything if fifo is full */ if ((h->access.fifo_full(h))) { dev_warn(&h->pdev->dev, "fifo full\n"); @@ -2867,20 +2911,22 @@ static inline void finish_cmd(struct CommandList *c, u32 raw_tag) static inline u32 hpsa_tag_contains_index(u32 tag) { -#define DIRECT_LOOKUP_BIT 0x10 return tag & DIRECT_LOOKUP_BIT; } static inline u32 hpsa_tag_to_index(u32 tag) { -#define DIRECT_LOOKUP_SHIFT 5 return tag >> DIRECT_LOOKUP_SHIFT; } -static inline u32 hpsa_tag_discard_error_bits(u32 tag) + +static inline u32 hpsa_tag_discard_error_bits(struct ctlr_info *h, u32 tag) { -#define HPSA_ERROR_BITS 0x03 - return tag & ~HPSA_ERROR_BITS; +#define HPSA_PERF_ERROR_BITS ((1 << DIRECT_LOOKUP_SHIFT) - 1) +#define HPSA_SIMPLE_ERROR_BITS 0x03 + if (unlikely(!(h->transMethod & CFGTBL_Trans_Performant))) + return tag & ~HPSA_SIMPLE_ERROR_BITS; + return tag & ~HPSA_PERF_ERROR_BITS; } /* process completion of an indexed ("direct lookup") command */ @@ -2904,10 +2950,9 @@ static inline u32 process_nonindexed_cmd(struct ctlr_info *h, { u32 tag; struct CommandList *c = NULL; - struct hlist_node *tmp; - tag = hpsa_tag_discard_error_bits(raw_tag); - hlist_for_each_entry(c, tmp, &h->cmpQ, list) { + tag = hpsa_tag_discard_error_bits(h, raw_tag); + list_for_each_entry(c, &h->cmpQ, list) { if ((c->busaddr & 0xFFFFFFE0) == (tag & 0xFFFFFFE0)) { finish_cmd(c, raw_tag); return next_command(h); @@ -2957,7 +3002,10 @@ static irqreturn_t do_hpsa_intr_msi(int irq, void *dev_id) return IRQ_HANDLED; } -/* Send a message CDB to the firmware. */ +/* Send a message CDB to the firmware. Careful, this only works + * in simple mode, not performant mode due to the tag lookup. + * We only ever use this immediately after a controller reset. + */ static __devinit int hpsa_message(struct pci_dev *pdev, unsigned char opcode, unsigned char type) { @@ -3023,7 +3071,7 @@ static __devinit int hpsa_message(struct pci_dev *pdev, unsigned char opcode, for (i = 0; i < HPSA_MSG_SEND_RETRY_LIMIT; i++) { tag = readl(vaddr + SA5_REPLY_PORT_OFFSET); - if (hpsa_tag_discard_error_bits(tag) == paddr32) + if ((tag & ~HPSA_SIMPLE_ERROR_BITS) == paddr32) break; msleep(HPSA_MSG_SEND_RETRY_INTERVAL_MSECS); } @@ -3055,38 +3103,6 @@ static __devinit int hpsa_message(struct pci_dev *pdev, unsigned char opcode, #define hpsa_soft_reset_controller(p) hpsa_message(p, 1, 0) #define hpsa_noop(p) hpsa_message(p, 3, 0) -static __devinit int hpsa_reset_msi(struct pci_dev *pdev) -{ -/* the #defines are stolen from drivers/pci/msi.h. */ -#define msi_control_reg(base) (base + PCI_MSI_FLAGS) -#define PCI_MSIX_FLAGS_ENABLE (1 << 15) - - int pos; - u16 control = 0; - - pos = pci_find_capability(pdev, PCI_CAP_ID_MSI); - if (pos) { - pci_read_config_word(pdev, msi_control_reg(pos), &control); - if (control & PCI_MSI_FLAGS_ENABLE) { - dev_info(&pdev->dev, "resetting MSI\n"); - pci_write_config_word(pdev, msi_control_reg(pos), - control & ~PCI_MSI_FLAGS_ENABLE); - } - } - - pos = pci_find_capability(pdev, PCI_CAP_ID_MSIX); - if (pos) { - pci_read_config_word(pdev, msi_control_reg(pos), &control); - if (control & PCI_MSIX_FLAGS_ENABLE) { - dev_info(&pdev->dev, "resetting MSI-X\n"); - pci_write_config_word(pdev, msi_control_reg(pos), - control & ~PCI_MSIX_FLAGS_ENABLE); - } - } - - return 0; -} - static int hpsa_controller_hard_reset(struct pci_dev *pdev, void * __iomem vaddr, bool use_doorbell) { @@ -3142,17 +3158,17 @@ static int hpsa_controller_hard_reset(struct pci_dev *pdev, */ static __devinit int hpsa_kdump_hard_reset_controller(struct pci_dev *pdev) { - u16 saved_config_space[32]; u64 cfg_offset; u32 cfg_base_addr; u64 cfg_base_addr_index; void __iomem *vaddr; unsigned long paddr; u32 misc_fw_support, active_transport; - int rc, i; + int rc; struct CfgTable __iomem *cfgtable; bool use_doorbell; u32 board_id; + u16 command_register; /* For controllers as old as the P600, this is very nearly * the same thing as @@ -3162,14 +3178,6 @@ static __devinit int hpsa_kdump_hard_reset_controller(struct pci_dev *pdev) * pci_set_power_state(pci_dev, PCI_D0); * pci_restore_state(pci_dev); * - * but we can't use these nice canned kernel routines on - * kexec, because they also check the MSI/MSI-X state in PCI - * configuration space and do the wrong thing when it is - * set/cleared. Also, the pci_save/restore_state functions - * violate the ordering requirements for restoring the - * configuration space from the CCISS document (see the - * comment below). So we roll our own .... - * * For controllers newer than the P600, the pci power state * method of resetting doesn't work so we have another way * using the doorbell register. @@ -3182,13 +3190,21 @@ static __devinit int hpsa_kdump_hard_reset_controller(struct pci_dev *pdev) * likely not be happy. Just forbid resetting this conjoined mess. * The 640x isn't really supported by hpsa anyway. */ - hpsa_lookup_board_id(pdev, &board_id); + rc = hpsa_lookup_board_id(pdev, &board_id); + if (rc < 0) { + dev_warn(&pdev->dev, "Not resetting device.\n"); + return -ENODEV; + } if (board_id == 0x409C0E11 || board_id == 0x409D0E11) return -ENOTSUPP; - for (i = 0; i < 32; i++) - pci_read_config_word(pdev, 2*i, &saved_config_space[i]); - + /* Save the PCI command register */ + pci_read_config_word(pdev, 4, &command_register); + /* Turn the board off. This is so that later pci_restore_state() + * won't turn the board on before the rest of config space is ready. + */ + pci_disable_device(pdev); + pci_save_state(pdev); /* find the first memory BAR, so we can find the cfg table */ rc = hpsa_pci_find_memory_BAR(pdev, &paddr); @@ -3214,46 +3230,47 @@ static __devinit int hpsa_kdump_hard_reset_controller(struct pci_dev *pdev) misc_fw_support = readl(&cfgtable->misc_fw_support); use_doorbell = misc_fw_support & MISC_FW_DOORBELL_RESET; - /* The doorbell reset seems to cause lockups on some Smart - * Arrays (e.g. P410, P410i, maybe others). Until this is - * fixed or at least isolated, avoid the doorbell reset. - */ - use_doorbell = 0; - rc = hpsa_controller_hard_reset(pdev, vaddr, use_doorbell); if (rc) goto unmap_cfgtable; - /* Restore the PCI configuration space. The Open CISS - * Specification says, "Restore the PCI Configuration - * Registers, offsets 00h through 60h. It is important to - * restore the command register, 16-bits at offset 04h, - * last. Do not restore the configuration status register, - * 16-bits at offset 06h." Note that the offset is 2*i. - */ - for (i = 0; i < 32; i++) { - if (i == 2 || i == 3) - continue; - pci_write_config_word(pdev, 2*i, saved_config_space[i]); + pci_restore_state(pdev); + rc = pci_enable_device(pdev); + if (rc) { + dev_warn(&pdev->dev, "failed to enable device.\n"); + goto unmap_cfgtable; } - wmb(); - pci_write_config_word(pdev, 4, saved_config_space[2]); + pci_write_config_word(pdev, 4, command_register); /* Some devices (notably the HP Smart Array 5i Controller) need a little pause here */ msleep(HPSA_POST_RESET_PAUSE_MSECS); + /* Wait for board to become not ready, then ready. */ + dev_info(&pdev->dev, "Waiting for board to become ready.\n"); + rc = hpsa_wait_for_board_state(pdev, vaddr, BOARD_NOT_READY); + if (rc) + dev_warn(&pdev->dev, + "failed waiting for board to become not ready\n"); + rc = hpsa_wait_for_board_state(pdev, vaddr, BOARD_READY); + if (rc) { + dev_warn(&pdev->dev, + "failed waiting for board to become ready\n"); + goto unmap_cfgtable; + } + dev_info(&pdev->dev, "board ready.\n"); + /* Controller should be in simple mode at this point. If it's not, * It means we're on one of those controllers which doesn't support * the doorbell reset method and on which the PCI power management reset * method doesn't work (P800, for example.) - * In those cases, pretend the reset worked and hope for the best. + * In those cases, don't try to proceed, as it generally doesn't work. */ active_transport = readl(&cfgtable->TransportActive); if (active_transport & PERFORMANT_MODE) { dev_warn(&pdev->dev, "Unable to successfully reset controller," - " proceeding anyway.\n"); - rc = -ENOTSUPP; + " Ignoring controller.\n"); + rc = -ENODEV; } unmap_cfgtable: @@ -3386,7 +3403,7 @@ static void __devinit hpsa_interrupt_mode(struct ctlr_info *h) default_int_mode: #endif /* CONFIG_PCI_MSI */ /* if we get here we're going to use the default interrupt mode */ - h->intr[PERF_MODE_INT] = h->pdev->irq; + h->intr[h->intr_mode] = h->pdev->irq; } static int __devinit hpsa_lookup_board_id(struct pci_dev *pdev, u32 *board_id) @@ -3438,18 +3455,28 @@ static int __devinit hpsa_pci_find_memory_BAR(struct pci_dev *pdev, return -ENODEV; } -static int __devinit hpsa_wait_for_board_ready(struct ctlr_info *h) +static int __devinit hpsa_wait_for_board_state(struct pci_dev *pdev, + void __iomem *vaddr, int wait_for_ready) { - int i; + int i, iterations; u32 scratchpad; + if (wait_for_ready) + iterations = HPSA_BOARD_READY_ITERATIONS; + else + iterations = HPSA_BOARD_NOT_READY_ITERATIONS; - for (i = 0; i < HPSA_BOARD_READY_ITERATIONS; i++) { - scratchpad = readl(h->vaddr + SA5_SCRATCHPAD_OFFSET); - if (scratchpad == HPSA_FIRMWARE_READY) - return 0; + for (i = 0; i < iterations; i++) { + scratchpad = readl(vaddr + SA5_SCRATCHPAD_OFFSET); + if (wait_for_ready) { + if (scratchpad == HPSA_FIRMWARE_READY) + return 0; + } else { + if (scratchpad != HPSA_FIRMWARE_READY) + return 0; + } msleep(HPSA_BOARD_READY_POLL_INTERVAL_MSECS); } - dev_warn(&h->pdev->dev, "board not ready, timed out.\n"); + dev_warn(&pdev->dev, "board not ready, timed out.\n"); return -ENODEV; } @@ -3497,6 +3524,11 @@ static int __devinit hpsa_find_cfgtables(struct ctlr_info *h) static void __devinit hpsa_get_max_perf_mode_cmds(struct ctlr_info *h) { h->max_commands = readl(&(h->cfgtable->MaxPerformantModeCommands)); + + /* Limit commands in memory limited kdump scenario. */ + if (reset_devices && h->max_commands > 32) + h->max_commands = 32; + if (h->max_commands < 16) { dev_warn(&h->pdev->dev, "Controller reports " "max supported commands of %d, an obvious lie. " @@ -3571,16 +3603,21 @@ static inline void hpsa_p600_dma_prefetch_quirk(struct ctlr_info *h) static void __devinit hpsa_wait_for_mode_change_ack(struct ctlr_info *h) { int i; + u32 doorbell_value; + unsigned long flags; /* under certain very rare conditions, this can take awhile. * (e.g.: hot replace a failed 144GB drive in a RAID 5 set right * as we enter this code.) */ for (i = 0; i < MAX_CONFIG_WAIT; i++) { - if (!(readl(h->vaddr + SA5_DOORBELL) & CFGTBL_ChangeReq)) + spin_lock_irqsave(&h->lock, flags); + doorbell_value = readl(h->vaddr + SA5_DOORBELL); + spin_unlock_irqrestore(&h->lock, flags); + if (!(doorbell_value & CFGTBL_ChangeReq)) break; /* delay and try again */ - msleep(10); + usleep_range(10000, 20000); } } @@ -3603,6 +3640,7 @@ static int __devinit hpsa_enter_simple_mode(struct ctlr_info *h) "unable to get board into simple mode\n"); return -ENODEV; } + h->transMethod = CFGTBL_Trans_Simple; return 0; } @@ -3641,7 +3679,7 @@ static int __devinit hpsa_pci_init(struct ctlr_info *h) err = -ENOMEM; goto err_out_free_res; } - err = hpsa_wait_for_board_ready(h); + err = hpsa_wait_for_board_state(h->pdev, h->vaddr, BOARD_READY); if (err) goto err_out_free_res; err = hpsa_find_cfgtables(h); @@ -3710,8 +3748,6 @@ static __devinit int hpsa_init_reset_devices(struct pci_dev *pdev) return 0; /* just try to do the kdump anyhow. */ if (rc) return -ENODEV; - if (hpsa_reset_msi(pdev)) - return -ENODEV; /* Now try to get the controller to respond to a no-op */ for (i = 0; i < HPSA_POST_RESET_NOOP_RETRIES; i++) { @@ -3749,8 +3785,11 @@ static int __devinit hpsa_init_one(struct pci_dev *pdev, h->pdev = pdev; h->busy_initializing = 1; - INIT_HLIST_HEAD(&h->cmpQ); - INIT_HLIST_HEAD(&h->reqQ); + h->intr_mode = hpsa_simple_mode ? SIMPLE_MODE_INT : PERF_MODE_INT; + INIT_LIST_HEAD(&h->cmpQ); + INIT_LIST_HEAD(&h->reqQ); + spin_lock_init(&h->lock); + spin_lock_init(&h->scan_lock); rc = hpsa_pci_init(h); if (rc != 0) goto clean1; @@ -3777,20 +3816,20 @@ static int __devinit hpsa_init_one(struct pci_dev *pdev, h->access.set_intr_mask(h, HPSA_INTR_OFF); if (h->msix_vector || h->msi_vector) - rc = request_irq(h->intr[PERF_MODE_INT], do_hpsa_intr_msi, + rc = request_irq(h->intr[h->intr_mode], do_hpsa_intr_msi, IRQF_DISABLED, h->devname, h); else - rc = request_irq(h->intr[PERF_MODE_INT], do_hpsa_intr_intx, + rc = request_irq(h->intr[h->intr_mode], do_hpsa_intr_intx, IRQF_DISABLED, h->devname, h); if (rc) { dev_err(&pdev->dev, "unable to get irq %d for %s\n", - h->intr[PERF_MODE_INT], h->devname); + h->intr[h->intr_mode], h->devname); goto clean2; } dev_info(&pdev->dev, "%s: <0x%x> at IRQ %d%s using DAC\n", h->devname, pdev->device, - h->intr[PERF_MODE_INT], dac ? "" : " not"); + h->intr[h->intr_mode], dac ? "" : " not"); h->cmd_pool_bits = kmalloc(((h->nr_cmds + BITS_PER_LONG - @@ -3810,8 +3849,6 @@ static int __devinit hpsa_init_one(struct pci_dev *pdev, } if (hpsa_allocate_sg_chain_blocks(h)) goto clean4; - spin_lock_init(&h->lock); - spin_lock_init(&h->scan_lock); init_waitqueue_head(&h->scan_wait_queue); h->scan_finished = 1; /* no scan currently in progress */ @@ -3843,7 +3880,7 @@ clean4: h->nr_cmds * sizeof(struct ErrorInfo), h->errinfo_pool, h->errinfo_pool_dhandle); - free_irq(h->intr[PERF_MODE_INT], h); + free_irq(h->intr[h->intr_mode], h); clean2: clean1: h->busy_initializing = 0; @@ -3887,7 +3924,7 @@ static void hpsa_shutdown(struct pci_dev *pdev) */ hpsa_flush_cache(h); h->access.set_intr_mask(h, HPSA_INTR_OFF); - free_irq(h->intr[PERF_MODE_INT], h); + free_irq(h->intr[h->intr_mode], h); #ifdef CONFIG_PCI_MSI if (h->msix_vector) pci_disable_msix(h->pdev); @@ -3989,7 +4026,8 @@ static void calc_bucket_map(int bucket[], int num_buckets, } } -static __devinit void hpsa_enter_performant_mode(struct ctlr_info *h) +static __devinit void hpsa_enter_performant_mode(struct ctlr_info *h, + u32 use_short_tags) { int i; unsigned long register_value; @@ -4037,7 +4075,7 @@ static __devinit void hpsa_enter_performant_mode(struct ctlr_info *h) writel(0, &h->transtable->RepQCtrAddrHigh32); writel(h->reply_pool_dhandle, &h->transtable->RepQAddr0Low32); writel(0, &h->transtable->RepQAddr0High32); - writel(CFGTBL_Trans_Performant, + writel(CFGTBL_Trans_Performant | use_short_tags, &(h->cfgtable->HostWrite.TransportRequest)); writel(CFGTBL_ChangeReq, h->vaddr + SA5_DOORBELL); hpsa_wait_for_mode_change_ack(h); @@ -4047,12 +4085,18 @@ static __devinit void hpsa_enter_performant_mode(struct ctlr_info *h) " performant mode\n"); return; } + /* Change the access methods to the performant access methods */ + h->access = SA5_performant_access; + h->transMethod = CFGTBL_Trans_Performant; } static __devinit void hpsa_put_ctlr_into_performant_mode(struct ctlr_info *h) { u32 trans_support; + if (hpsa_simple_mode) + return; + trans_support = readl(&(h->cfgtable->TransportSupport)); if (!(trans_support & PERFORMANT_MODE)) return; @@ -4072,11 +4116,8 @@ static __devinit void hpsa_put_ctlr_into_performant_mode(struct ctlr_info *h) || (h->blockFetchTable == NULL)) goto clean_up; - hpsa_enter_performant_mode(h); - - /* Change the access methods to the performant access methods */ - h->access = SA5_performant_access; - h->transMethod = CFGTBL_Trans_Performant; + hpsa_enter_performant_mode(h, + trans_support & CFGTBL_Trans_use_short_tags); return; diff --git a/drivers/scsi/hpsa.h b/drivers/scsi/hpsa.h index 19586e189f0f..621a1530054a 100644 --- a/drivers/scsi/hpsa.h +++ b/drivers/scsi/hpsa.h @@ -72,11 +72,12 @@ struct ctlr_info { unsigned int intr[4]; unsigned int msix_vector; unsigned int msi_vector; + int intr_mode; /* either PERF_MODE_INT or SIMPLE_MODE_INT */ struct access_method access; /* queue and queue Info */ - struct hlist_head reqQ; - struct hlist_head cmpQ; + struct list_head reqQ; + struct list_head cmpQ; unsigned int Qdepth; unsigned int maxQsinceinit; unsigned int maxSG; @@ -154,12 +155,16 @@ struct ctlr_info { * HPSA_BOARD_READY_ITERATIONS are derived from those. */ #define HPSA_BOARD_READY_WAIT_SECS (120) +#define HPSA_BOARD_NOT_READY_WAIT_SECS (10) #define HPSA_BOARD_READY_POLL_INTERVAL_MSECS (100) #define HPSA_BOARD_READY_POLL_INTERVAL \ ((HPSA_BOARD_READY_POLL_INTERVAL_MSECS * HZ) / 1000) #define HPSA_BOARD_READY_ITERATIONS \ ((HPSA_BOARD_READY_WAIT_SECS * 1000) / \ HPSA_BOARD_READY_POLL_INTERVAL_MSECS) +#define HPSA_BOARD_NOT_READY_ITERATIONS \ + ((HPSA_BOARD_NOT_READY_WAIT_SECS * 1000) / \ + HPSA_BOARD_READY_POLL_INTERVAL_MSECS) #define HPSA_POST_RESET_PAUSE_MSECS (3000) #define HPSA_POST_RESET_NOOP_RETRIES (12) diff --git a/drivers/scsi/hpsa_cmd.h b/drivers/scsi/hpsa_cmd.h index f5c4c3cc0530..18464900e761 100644 --- a/drivers/scsi/hpsa_cmd.h +++ b/drivers/scsi/hpsa_cmd.h @@ -104,6 +104,7 @@ #define CFGTBL_Trans_Simple 0x00000002l #define CFGTBL_Trans_Performant 0x00000004l +#define CFGTBL_Trans_use_short_tags 0x20000000l #define CFGTBL_BusType_Ultra2 0x00000001l #define CFGTBL_BusType_Ultra3 0x00000002l @@ -265,6 +266,7 @@ struct ErrorInfo { #define DIRECT_LOOKUP_SHIFT 5 #define DIRECT_LOOKUP_BIT 0x10 +#define DIRECT_LOOKUP_MASK (~((1 << DIRECT_LOOKUP_SHIFT) - 1)) #define HPSA_ERROR_BIT 0x02 struct ctlr_info; /* defined in hpsa.h */ @@ -291,7 +293,7 @@ struct CommandList { struct ctlr_info *h; int cmd_type; long cmdindex; - struct hlist_node list; + struct list_head list; struct request *rq; struct completion *waiting; void *scsi_cmd; diff --git a/drivers/scsi/ipr.c b/drivers/scsi/ipr.c index 9c5c8be72231..0621238fac4a 100644 --- a/drivers/scsi/ipr.c +++ b/drivers/scsi/ipr.c @@ -1301,7 +1301,7 @@ static void ipr_handle_config_change(struct ipr_ioa_cfg *ioa_cfg, ipr_clear_res_target(res); list_move_tail(&res->queue, &ioa_cfg->free_res_q); } - } else if (!res->sdev) { + } else if (!res->sdev || res->del_from_ml) { res->add_to_ml = 1; if (ioa_cfg->allow_ml_add_del) schedule_work(&ioa_cfg->work_q); @@ -3104,7 +3104,10 @@ restart: did_work = 1; sdev = res->sdev; if (!scsi_device_get(sdev)) { - list_move_tail(&res->queue, &ioa_cfg->free_res_q); + if (!res->add_to_ml) + list_move_tail(&res->queue, &ioa_cfg->free_res_q); + else + res->del_from_ml = 0; spin_unlock_irqrestore(ioa_cfg->host->host_lock, lock_flags); scsi_remove_device(sdev); scsi_device_put(sdev); @@ -6219,11 +6222,10 @@ static struct ata_port_operations ipr_sata_ops = { }; static struct ata_port_info sata_port_info = { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | ATA_FLAG_SATA_RESET | - ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA, - .pio_mask = 0x10, /* pio4 */ - .mwdma_mask = 0x07, - .udma_mask = 0x7f, /* udma0-6 */ + .flags = ATA_FLAG_SATA | ATA_FLAG_PIO_DMA, + .pio_mask = ATA_PIO4_ONLY, + .mwdma_mask = ATA_MWDMA2, + .udma_mask = ATA_UDMA6, .port_ops = &ipr_sata_ops }; @@ -8865,7 +8867,7 @@ static void __ipr_remove(struct pci_dev *pdev) spin_unlock_irqrestore(ioa_cfg->host->host_lock, host_lock_flags); wait_event(ioa_cfg->reset_wait_q, !ioa_cfg->in_reset_reload); - flush_scheduled_work(); + flush_work_sync(&ioa_cfg->work_q); spin_lock_irqsave(ioa_cfg->host->host_lock, host_lock_flags); spin_lock(&ipr_driver_lock); diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c index fec47de72535..a860452a8f71 100644 --- a/drivers/scsi/iscsi_tcp.c +++ b/drivers/scsi/iscsi_tcp.c @@ -608,54 +608,12 @@ static void iscsi_sw_tcp_conn_stop(struct iscsi_cls_conn *cls_conn, int flag) iscsi_sw_tcp_release_conn(conn); } -static int iscsi_sw_tcp_get_addr(struct iscsi_conn *conn, struct socket *sock, - char *buf, int *port, - int (*getname)(struct socket *, - struct sockaddr *, - int *addrlen)) -{ - struct sockaddr_storage *addr; - struct sockaddr_in6 *sin6; - struct sockaddr_in *sin; - int rc = 0, len; - - addr = kmalloc(sizeof(*addr), GFP_KERNEL); - if (!addr) - return -ENOMEM; - - if (getname(sock, (struct sockaddr *) addr, &len)) { - rc = -ENODEV; - goto free_addr; - } - - switch (addr->ss_family) { - case AF_INET: - sin = (struct sockaddr_in *)addr; - spin_lock_bh(&conn->session->lock); - sprintf(buf, "%pI4", &sin->sin_addr.s_addr); - *port = be16_to_cpu(sin->sin_port); - spin_unlock_bh(&conn->session->lock); - break; - case AF_INET6: - sin6 = (struct sockaddr_in6 *)addr; - spin_lock_bh(&conn->session->lock); - sprintf(buf, "%pI6", &sin6->sin6_addr); - *port = be16_to_cpu(sin6->sin6_port); - spin_unlock_bh(&conn->session->lock); - break; - } -free_addr: - kfree(addr); - return rc; -} - static int iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session, struct iscsi_cls_conn *cls_conn, uint64_t transport_eph, int is_leading) { - struct Scsi_Host *shost = iscsi_session_to_shost(cls_session); - struct iscsi_host *ihost = shost_priv(shost); + struct iscsi_session *session = cls_session->dd_data; struct iscsi_conn *conn = cls_conn->dd_data; struct iscsi_tcp_conn *tcp_conn = conn->dd_data; struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; @@ -670,27 +628,15 @@ iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session, "sockfd_lookup failed %d\n", err); return -EEXIST; } - /* - * copy these values now because if we drop the session - * userspace may still want to query the values since we will - * be using them for the reconnect - */ - err = iscsi_sw_tcp_get_addr(conn, sock, conn->portal_address, - &conn->portal_port, kernel_getpeername); - if (err) - goto free_socket; - - err = iscsi_sw_tcp_get_addr(conn, sock, ihost->local_address, - &ihost->local_port, kernel_getsockname); - if (err) - goto free_socket; err = iscsi_conn_bind(cls_session, cls_conn, is_leading); if (err) goto free_socket; + spin_lock_bh(&session->lock); /* bind iSCSI connection and socket */ tcp_sw_conn->sock = sock; + spin_unlock_bh(&session->lock); /* setup Socket parameters */ sk = sock->sk; @@ -752,24 +698,74 @@ static int iscsi_sw_tcp_conn_get_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf) { struct iscsi_conn *conn = cls_conn->dd_data; - int len; + struct iscsi_tcp_conn *tcp_conn = conn->dd_data; + struct iscsi_sw_tcp_conn *tcp_sw_conn = tcp_conn->dd_data; + struct sockaddr_in6 addr; + int rc, len; switch(param) { case ISCSI_PARAM_CONN_PORT: - spin_lock_bh(&conn->session->lock); - len = sprintf(buf, "%hu\n", conn->portal_port); - spin_unlock_bh(&conn->session->lock); - break; case ISCSI_PARAM_CONN_ADDRESS: spin_lock_bh(&conn->session->lock); - len = sprintf(buf, "%s\n", conn->portal_address); + if (!tcp_sw_conn || !tcp_sw_conn->sock) { + spin_unlock_bh(&conn->session->lock); + return -ENOTCONN; + } + rc = kernel_getpeername(tcp_sw_conn->sock, + (struct sockaddr *)&addr, &len); spin_unlock_bh(&conn->session->lock); - break; + if (rc) + return rc; + + return iscsi_conn_get_addr_param((struct sockaddr_storage *) + &addr, param, buf); default: return iscsi_conn_get_param(cls_conn, param, buf); } - return len; + return 0; +} + +static int iscsi_sw_tcp_host_get_param(struct Scsi_Host *shost, + enum iscsi_host_param param, char *buf) +{ + struct iscsi_sw_tcp_host *tcp_sw_host = iscsi_host_priv(shost); + struct iscsi_session *session = tcp_sw_host->session; + struct iscsi_conn *conn; + struct iscsi_tcp_conn *tcp_conn; + struct iscsi_sw_tcp_conn *tcp_sw_conn; + struct sockaddr_in6 addr; + int rc, len; + + switch (param) { + case ISCSI_HOST_PARAM_IPADDRESS: + spin_lock_bh(&session->lock); + conn = session->leadconn; + if (!conn) { + spin_unlock_bh(&session->lock); + return -ENOTCONN; + } + tcp_conn = conn->dd_data; + + tcp_sw_conn = tcp_conn->dd_data; + if (!tcp_sw_conn->sock) { + spin_unlock_bh(&session->lock); + return -ENOTCONN; + } + + rc = kernel_getsockname(tcp_sw_conn->sock, + (struct sockaddr *)&addr, &len); + spin_unlock_bh(&session->lock); + if (rc) + return rc; + + return iscsi_conn_get_addr_param((struct sockaddr_storage *) + &addr, param, buf); + default: + return iscsi_host_get_param(shost, param, buf); + } + + return 0; } static void @@ -797,6 +793,7 @@ iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max, { struct iscsi_cls_session *cls_session; struct iscsi_session *session; + struct iscsi_sw_tcp_host *tcp_sw_host; struct Scsi_Host *shost; if (ep) { @@ -804,7 +801,8 @@ iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max, return NULL; } - shost = iscsi_host_alloc(&iscsi_sw_tcp_sht, 0, 1); + shost = iscsi_host_alloc(&iscsi_sw_tcp_sht, + sizeof(struct iscsi_sw_tcp_host), 1); if (!shost) return NULL; shost->transportt = iscsi_sw_tcp_scsi_transport; @@ -825,6 +823,8 @@ iscsi_sw_tcp_session_create(struct iscsi_endpoint *ep, uint16_t cmds_max, if (!cls_session) goto remove_host; session = cls_session->dd_data; + tcp_sw_host = iscsi_host_priv(shost); + tcp_sw_host->session = session; shost->can_queue = session->scsi_cmds_max; if (iscsi_tcp_r2tpool_alloc(session)) @@ -929,7 +929,7 @@ static struct iscsi_transport iscsi_sw_tcp_transport = { .start_conn = iscsi_conn_start, .stop_conn = iscsi_sw_tcp_conn_stop, /* iscsi host params */ - .get_host_param = iscsi_host_get_param, + .get_host_param = iscsi_sw_tcp_host_get_param, .set_host_param = iscsi_host_set_param, /* IO */ .send_pdu = iscsi_conn_send_pdu, diff --git a/drivers/scsi/iscsi_tcp.h b/drivers/scsi/iscsi_tcp.h index 94644bad0ed7..666fe09378fa 100644 --- a/drivers/scsi/iscsi_tcp.h +++ b/drivers/scsi/iscsi_tcp.h @@ -55,6 +55,10 @@ struct iscsi_sw_tcp_conn { ssize_t (*sendpage)(struct socket *, struct page *, int, size_t, int); }; +struct iscsi_sw_tcp_host { + struct iscsi_session *session; +}; + struct iscsi_sw_tcp_hdrbuf { struct iscsi_hdr hdrbuf; char hdrextbuf[ISCSI_MAX_AHS_SIZE + diff --git a/drivers/scsi/libfc/fc_exch.c b/drivers/scsi/libfc/fc_exch.c index d21367d3305f..08bf5fa6afd4 100644 --- a/drivers/scsi/libfc/fc_exch.c +++ b/drivers/scsi/libfc/fc_exch.c @@ -38,7 +38,7 @@ u16 fc_cpu_mask; /* cpu mask for possible cpus */ EXPORT_SYMBOL(fc_cpu_mask); static u16 fc_cpu_order; /* 2's power to represent total possible cpus */ static struct kmem_cache *fc_em_cachep; /* cache for exchanges */ -struct workqueue_struct *fc_exch_workqueue; +static struct workqueue_struct *fc_exch_workqueue; /* * Structure and function definitions for managing Fibre Channel Exchanges @@ -558,6 +558,22 @@ static struct fc_seq *fc_seq_start_next(struct fc_seq *sp) return sp; } +/* + * Set the response handler for the exchange associated with a sequence. + */ +static void fc_seq_set_resp(struct fc_seq *sp, + void (*resp)(struct fc_seq *, struct fc_frame *, + void *), + void *arg) +{ + struct fc_exch *ep = fc_seq_exch(sp); + + spin_lock_bh(&ep->ex_lock); + ep->resp = resp; + ep->arg = arg; + spin_unlock_bh(&ep->ex_lock); +} + /** * fc_seq_exch_abort() - Abort an exchange and sequence * @req_sp: The sequence to be aborted @@ -1266,6 +1282,8 @@ free: * @fp: The request frame * * On success, the sequence pointer will be returned and also in fr_seq(@fp). + * A reference will be held on the exchange/sequence for the caller, which + * must call fc_seq_release(). */ static struct fc_seq *fc_seq_assign(struct fc_lport *lport, struct fc_frame *fp) { @@ -1283,6 +1301,15 @@ static struct fc_seq *fc_seq_assign(struct fc_lport *lport, struct fc_frame *fp) } /** + * fc_seq_release() - Release the hold + * @sp: The sequence. + */ +static void fc_seq_release(struct fc_seq *sp) +{ + fc_exch_release(fc_seq_exch(sp)); +} + +/** * fc_exch_recv_req() - Handler for an incoming request * @lport: The local port that received the request * @mp: The EM that the exchange is on @@ -2254,16 +2281,45 @@ void fc_exch_mgr_free(struct fc_lport *lport) EXPORT_SYMBOL(fc_exch_mgr_free); /** + * fc_find_ema() - Lookup and return appropriate Exchange Manager Anchor depending + * upon 'xid'. + * @f_ctl: f_ctl + * @lport: The local port the frame was received on + * @fh: The received frame header + */ +static struct fc_exch_mgr_anchor *fc_find_ema(u32 f_ctl, + struct fc_lport *lport, + struct fc_frame_header *fh) +{ + struct fc_exch_mgr_anchor *ema; + u16 xid; + + if (f_ctl & FC_FC_EX_CTX) + xid = ntohs(fh->fh_ox_id); + else { + xid = ntohs(fh->fh_rx_id); + if (xid == FC_XID_UNKNOWN) + return list_entry(lport->ema_list.prev, + typeof(*ema), ema_list); + } + + list_for_each_entry(ema, &lport->ema_list, ema_list) { + if ((xid >= ema->mp->min_xid) && + (xid <= ema->mp->max_xid)) + return ema; + } + return NULL; +} +/** * fc_exch_recv() - Handler for received frames * @lport: The local port the frame was received on - * @fp: The received frame + * @fp: The received frame */ void fc_exch_recv(struct fc_lport *lport, struct fc_frame *fp) { struct fc_frame_header *fh = fc_frame_header_get(fp); struct fc_exch_mgr_anchor *ema; - u32 f_ctl, found = 0; - u16 oxid; + u32 f_ctl; /* lport lock ? */ if (!lport || lport->state == LPORT_ST_DISABLED) { @@ -2274,24 +2330,17 @@ void fc_exch_recv(struct fc_lport *lport, struct fc_frame *fp) } f_ctl = ntoh24(fh->fh_f_ctl); - oxid = ntohs(fh->fh_ox_id); - if (f_ctl & FC_FC_EX_CTX) { - list_for_each_entry(ema, &lport->ema_list, ema_list) { - if ((oxid >= ema->mp->min_xid) && - (oxid <= ema->mp->max_xid)) { - found = 1; - break; - } - } - - if (!found) { - FC_LPORT_DBG(lport, "Received response for out " - "of range oxid:%hx\n", oxid); - fc_frame_free(fp); - return; - } - } else - ema = list_entry(lport->ema_list.prev, typeof(*ema), ema_list); + ema = fc_find_ema(f_ctl, lport, fh); + if (!ema) { + FC_LPORT_DBG(lport, "Unable to find Exchange Manager Anchor," + "fc_ctl <0x%x>, xid <0x%x>\n", + f_ctl, + (f_ctl & FC_FC_EX_CTX) ? + ntohs(fh->fh_ox_id) : + ntohs(fh->fh_rx_id)); + fc_frame_free(fp); + return; + } /* * If frame is marked invalid, just drop it. @@ -2329,6 +2378,9 @@ int fc_exch_init(struct fc_lport *lport) if (!lport->tt.seq_start_next) lport->tt.seq_start_next = fc_seq_start_next; + if (!lport->tt.seq_set_resp) + lport->tt.seq_set_resp = fc_seq_set_resp; + if (!lport->tt.exch_seq_send) lport->tt.exch_seq_send = fc_exch_seq_send; @@ -2350,6 +2402,9 @@ int fc_exch_init(struct fc_lport *lport) if (!lport->tt.seq_assign) lport->tt.seq_assign = fc_seq_assign; + if (!lport->tt.seq_release) + lport->tt.seq_release = fc_seq_release; + return 0; } EXPORT_SYMBOL(fc_exch_init); @@ -2357,7 +2412,7 @@ EXPORT_SYMBOL(fc_exch_init); /** * fc_setup_exch_mgr() - Setup an exchange manager */ -int fc_setup_exch_mgr() +int fc_setup_exch_mgr(void) { fc_em_cachep = kmem_cache_create("libfc_em", sizeof(struct fc_exch), 0, SLAB_HWCACHE_ALIGN, NULL); @@ -2395,7 +2450,7 @@ int fc_setup_exch_mgr() /** * fc_destroy_exch_mgr() - Destroy an exchange manager */ -void fc_destroy_exch_mgr() +void fc_destroy_exch_mgr(void) { destroy_workqueue(fc_exch_workqueue); kmem_cache_destroy(fc_em_cachep); diff --git a/drivers/scsi/libfc/fc_fcp.c b/drivers/scsi/libfc/fc_fcp.c index 5962d1a5a674..b1b03af158bf 100644 --- a/drivers/scsi/libfc/fc_fcp.c +++ b/drivers/scsi/libfc/fc_fcp.c @@ -42,7 +42,7 @@ #include "fc_libfc.h" -struct kmem_cache *scsi_pkt_cachep; +static struct kmem_cache *scsi_pkt_cachep; /* SRB state definitions */ #define FC_SRB_FREE 0 /* cmd is free */ @@ -155,6 +155,7 @@ static struct fc_fcp_pkt *fc_fcp_pkt_alloc(struct fc_lport *lport, gfp_t gfp) if (fsp) { memset(fsp, 0, sizeof(*fsp)); fsp->lp = lport; + fsp->xfer_ddp = FC_XID_UNKNOWN; atomic_set(&fsp->ref_cnt, 1); init_timer(&fsp->timer); INIT_LIST_HEAD(&fsp->list); @@ -1201,6 +1202,7 @@ unlock: static int fc_fcp_pkt_abort(struct fc_fcp_pkt *fsp) { int rc = FAILED; + unsigned long ticks_left; if (fc_fcp_send_abort(fsp)) return FAILED; @@ -1209,13 +1211,13 @@ static int fc_fcp_pkt_abort(struct fc_fcp_pkt *fsp) fsp->wait_for_comp = 1; spin_unlock_bh(&fsp->scsi_pkt_lock); - rc = wait_for_completion_timeout(&fsp->tm_done, FC_SCSI_TM_TOV); + ticks_left = wait_for_completion_timeout(&fsp->tm_done, + FC_SCSI_TM_TOV); spin_lock_bh(&fsp->scsi_pkt_lock); fsp->wait_for_comp = 0; - if (!rc) { + if (!ticks_left) { FC_FCP_DBG(fsp, "target abort cmd failed\n"); - rc = FAILED; } else if (fsp->state & FC_SRB_ABORTED) { FC_FCP_DBG(fsp, "target abort cmd passed\n"); rc = SUCCESS; @@ -1321,7 +1323,7 @@ static void fc_tm_done(struct fc_seq *seq, struct fc_frame *fp, void *arg) * * scsi-eh will escalate for when either happens. */ - goto out; + return; } if (fc_fcp_lock_pkt(fsp)) @@ -1787,15 +1789,14 @@ static inline int fc_fcp_lport_queue_ready(struct fc_lport *lport) /** * fc_queuecommand() - The queuecommand function of the SCSI template + * @shost: The Scsi_Host that the command was issued to * @cmd: The scsi_cmnd to be executed - * @done: The callback function to be called when the scsi_cmnd is complete * - * This is the i/o strategy routine, called by the SCSI layer. This routine - * is called with the host_lock held. + * This is the i/o strategy routine, called by the SCSI layer. */ -static int fc_queuecommand_lck(struct scsi_cmnd *sc_cmd, void (*done)(struct scsi_cmnd *)) +int fc_queuecommand(struct Scsi_Host *shost, struct scsi_cmnd *sc_cmd) { - struct fc_lport *lport; + struct fc_lport *lport = shost_priv(shost); struct fc_rport *rport = starget_to_rport(scsi_target(sc_cmd->device)); struct fc_fcp_pkt *fsp; struct fc_rport_libfc_priv *rpriv; @@ -1803,15 +1804,12 @@ static int fc_queuecommand_lck(struct scsi_cmnd *sc_cmd, void (*done)(struct scs int rc = 0; struct fcoe_dev_stats *stats; - lport = shost_priv(sc_cmd->device->host); - rval = fc_remote_port_chkready(rport); if (rval) { sc_cmd->result = rval; - done(sc_cmd); + sc_cmd->scsi_done(sc_cmd); return 0; } - spin_unlock_irq(lport->host->host_lock); if (!*(struct fc_remote_port **)rport->dd_data) { /* @@ -1819,7 +1817,7 @@ static int fc_queuecommand_lck(struct scsi_cmnd *sc_cmd, void (*done)(struct scs * online */ sc_cmd->result = DID_IMM_RETRY << 16; - done(sc_cmd); + sc_cmd->scsi_done(sc_cmd); goto out; } @@ -1842,10 +1840,7 @@ static int fc_queuecommand_lck(struct scsi_cmnd *sc_cmd, void (*done)(struct scs * build the libfc request pkt */ fsp->cmd = sc_cmd; /* save the cmd */ - fsp->lp = lport; /* save the softc ptr */ fsp->rport = rport; /* set the remote port ptr */ - fsp->xfer_ddp = FC_XID_UNKNOWN; - sc_cmd->scsi_done = done; /* * set up the transfer length @@ -1886,11 +1881,8 @@ static int fc_queuecommand_lck(struct scsi_cmnd *sc_cmd, void (*done)(struct scs rc = SCSI_MLQUEUE_HOST_BUSY; } out: - spin_lock_irq(lport->host->host_lock); return rc; } - -DEF_SCSI_QCMD(fc_queuecommand) EXPORT_SYMBOL(fc_queuecommand); /** @@ -2112,7 +2104,6 @@ int fc_eh_device_reset(struct scsi_cmnd *sc_cmd) * the sc passed in is not setup for execution like when sent * through the queuecommand callout. */ - fsp->lp = lport; /* save the softc ptr */ fsp->rport = rport; /* set the remote port ptr */ /* @@ -2245,7 +2236,7 @@ void fc_fcp_destroy(struct fc_lport *lport) } EXPORT_SYMBOL(fc_fcp_destroy); -int fc_setup_fcp() +int fc_setup_fcp(void) { int rc = 0; @@ -2261,7 +2252,7 @@ int fc_setup_fcp() return rc; } -void fc_destroy_fcp() +void fc_destroy_fcp(void) { if (scsi_pkt_cachep) kmem_cache_destroy(scsi_pkt_cachep); diff --git a/drivers/scsi/libfc/fc_libfc.c b/drivers/scsi/libfc/fc_libfc.c index 6a48c28e4420..b7735129f1f3 100644 --- a/drivers/scsi/libfc/fc_libfc.c +++ b/drivers/scsi/libfc/fc_libfc.c @@ -35,6 +35,27 @@ unsigned int fc_debug_logging; module_param_named(debug_logging, fc_debug_logging, int, S_IRUGO|S_IWUSR); MODULE_PARM_DESC(debug_logging, "a bit mask of logging levels"); +DEFINE_MUTEX(fc_prov_mutex); +static LIST_HEAD(fc_local_ports); +struct blocking_notifier_head fc_lport_notifier_head = + BLOCKING_NOTIFIER_INIT(fc_lport_notifier_head); +EXPORT_SYMBOL(fc_lport_notifier_head); + +/* + * Providers which primarily send requests and PRLIs. + */ +struct fc4_prov *fc_active_prov[FC_FC4_PROV_SIZE] = { + [0] = &fc_rport_t0_prov, + [FC_TYPE_FCP] = &fc_rport_fcp_init, +}; + +/* + * Providers which receive requests. + */ +struct fc4_prov *fc_passive_prov[FC_FC4_PROV_SIZE] = { + [FC_TYPE_ELS] = &fc_lport_els_prov, +}; + /** * libfc_init() - Initialize libfc.ko */ @@ -210,3 +231,102 @@ void fc_fill_reply_hdr(struct fc_frame *fp, const struct fc_frame *in_fp, fc_fill_hdr(fp, in_fp, r_ctl, FC_FCTL_RESP, 0, parm_offset); } EXPORT_SYMBOL(fc_fill_reply_hdr); + +/** + * fc_fc4_conf_lport_params() - Modify "service_params" of specified lport + * if there is service provider (target provider) registered with libfc + * for specified "fc_ft_type" + * @lport: Local port which service_params needs to be modified + * @type: FC-4 type, such as FC_TYPE_FCP + */ +void fc_fc4_conf_lport_params(struct fc_lport *lport, enum fc_fh_type type) +{ + struct fc4_prov *prov_entry; + BUG_ON(type >= FC_FC4_PROV_SIZE); + BUG_ON(!lport); + prov_entry = fc_passive_prov[type]; + if (type == FC_TYPE_FCP) { + if (prov_entry && prov_entry->recv) + lport->service_params |= FCP_SPPF_TARG_FCN; + } +} + +void fc_lport_iterate(void (*notify)(struct fc_lport *, void *), void *arg) +{ + struct fc_lport *lport; + + mutex_lock(&fc_prov_mutex); + list_for_each_entry(lport, &fc_local_ports, lport_list) + notify(lport, arg); + mutex_unlock(&fc_prov_mutex); +} +EXPORT_SYMBOL(fc_lport_iterate); + +/** + * fc_fc4_register_provider() - register FC-4 upper-level provider. + * @type: FC-4 type, such as FC_TYPE_FCP + * @prov: structure describing provider including ops vector. + * + * Returns 0 on success, negative error otherwise. + */ +int fc_fc4_register_provider(enum fc_fh_type type, struct fc4_prov *prov) +{ + struct fc4_prov **prov_entry; + int ret = 0; + + if (type >= FC_FC4_PROV_SIZE) + return -EINVAL; + mutex_lock(&fc_prov_mutex); + prov_entry = (prov->recv ? fc_passive_prov : fc_active_prov) + type; + if (*prov_entry) + ret = -EBUSY; + else + *prov_entry = prov; + mutex_unlock(&fc_prov_mutex); + return ret; +} +EXPORT_SYMBOL(fc_fc4_register_provider); + +/** + * fc_fc4_deregister_provider() - deregister FC-4 upper-level provider. + * @type: FC-4 type, such as FC_TYPE_FCP + * @prov: structure describing provider including ops vector. + */ +void fc_fc4_deregister_provider(enum fc_fh_type type, struct fc4_prov *prov) +{ + BUG_ON(type >= FC_FC4_PROV_SIZE); + mutex_lock(&fc_prov_mutex); + if (prov->recv) + rcu_assign_pointer(fc_passive_prov[type], NULL); + else + rcu_assign_pointer(fc_active_prov[type], NULL); + mutex_unlock(&fc_prov_mutex); + synchronize_rcu(); +} +EXPORT_SYMBOL(fc_fc4_deregister_provider); + +/** + * fc_fc4_add_lport() - add new local port to list and run notifiers. + * @lport: The new local port. + */ +void fc_fc4_add_lport(struct fc_lport *lport) +{ + mutex_lock(&fc_prov_mutex); + list_add_tail(&lport->lport_list, &fc_local_ports); + blocking_notifier_call_chain(&fc_lport_notifier_head, + FC_LPORT_EV_ADD, lport); + mutex_unlock(&fc_prov_mutex); +} + +/** + * fc_fc4_del_lport() - remove local port from list and run notifiers. + * @lport: The new local port. + */ +void fc_fc4_del_lport(struct fc_lport *lport) +{ + mutex_lock(&fc_prov_mutex); + list_del(&lport->lport_list); + blocking_notifier_call_chain(&fc_lport_notifier_head, + FC_LPORT_EV_DEL, lport); + mutex_unlock(&fc_prov_mutex); +} diff --git a/drivers/scsi/libfc/fc_libfc.h b/drivers/scsi/libfc/fc_libfc.h index eea0c3541b71..fedc819d70c0 100644 --- a/drivers/scsi/libfc/fc_libfc.h +++ b/drivers/scsi/libfc/fc_libfc.h @@ -94,6 +94,17 @@ extern unsigned int fc_debug_logging; (lport)->host->host_no, ##args)) /* + * FC-4 Providers. + */ +extern struct fc4_prov *fc_active_prov[]; /* providers without recv */ +extern struct fc4_prov *fc_passive_prov[]; /* providers with recv */ +extern struct mutex fc_prov_mutex; /* lock over table changes */ + +extern struct fc4_prov fc_rport_t0_prov; /* type 0 provider */ +extern struct fc4_prov fc_lport_els_prov; /* ELS provider */ +extern struct fc4_prov fc_rport_fcp_init; /* FCP initiator provider */ + +/* * Set up direct-data placement for this I/O request */ void fc_fcp_ddp_setup(struct fc_fcp_pkt *fsp, u16 xid); @@ -112,6 +123,9 @@ void fc_destroy_fcp(void); * Internal libfc functions */ const char *fc_els_resp_type(struct fc_frame *); +extern void fc_fc4_add_lport(struct fc_lport *); +extern void fc_fc4_del_lport(struct fc_lport *); +extern void fc_fc4_conf_lport_params(struct fc_lport *, enum fc_fh_type); /* * Copies a buffer into an sg list diff --git a/drivers/scsi/libfc/fc_lport.c b/drivers/scsi/libfc/fc_lport.c index c5a10f94f845..735f1f82ccfd 100644 --- a/drivers/scsi/libfc/fc_lport.c +++ b/drivers/scsi/libfc/fc_lport.c @@ -633,6 +633,7 @@ int fc_lport_destroy(struct fc_lport *lport) lport->tt.fcp_abort_io(lport); lport->tt.disc_stop_final(lport); lport->tt.exch_mgr_reset(lport, 0, 0); + fc_fc4_del_lport(lport); return 0; } EXPORT_SYMBOL(fc_lport_destroy); @@ -849,7 +850,7 @@ out: } /** - * fc_lport_recv_req() - The generic lport request handler + * fc_lport_recv_els_req() - The generic lport ELS request handler * @lport: The local port that received the request * @fp: The request frame * @@ -859,9 +860,9 @@ out: * Locking Note: This function should not be called with the lport * lock held becuase it will grab the lock. */ -static void fc_lport_recv_req(struct fc_lport *lport, struct fc_frame *fp) +static void fc_lport_recv_els_req(struct fc_lport *lport, + struct fc_frame *fp) { - struct fc_frame_header *fh = fc_frame_header_get(fp); void (*recv)(struct fc_lport *, struct fc_frame *); mutex_lock(&lport->lp_mutex); @@ -873,8 +874,7 @@ static void fc_lport_recv_req(struct fc_lport *lport, struct fc_frame *fp) */ if (!lport->link_up) fc_frame_free(fp); - else if (fh->fh_type == FC_TYPE_ELS && - fh->fh_r_ctl == FC_RCTL_ELS_REQ) { + else { /* * Check opcode. */ @@ -903,14 +903,62 @@ static void fc_lport_recv_req(struct fc_lport *lport, struct fc_frame *fp) } recv(lport, fp); - } else { - FC_LPORT_DBG(lport, "dropping invalid frame (eof %x)\n", - fr_eof(fp)); - fc_frame_free(fp); } mutex_unlock(&lport->lp_mutex); } +static int fc_lport_els_prli(struct fc_rport_priv *rdata, u32 spp_len, + const struct fc_els_spp *spp_in, + struct fc_els_spp *spp_out) +{ + return FC_SPP_RESP_INVL; +} + +struct fc4_prov fc_lport_els_prov = { + .prli = fc_lport_els_prli, + .recv = fc_lport_recv_els_req, +}; + +/** + * fc_lport_recv_req() - The generic lport request handler + * @lport: The lport that received the request + * @fp: The frame the request is in + * + * Locking Note: This function should not be called with the lport + * lock held becuase it may grab the lock. + */ +static void fc_lport_recv_req(struct fc_lport *lport, + struct fc_frame *fp) +{ + struct fc_frame_header *fh = fc_frame_header_get(fp); + struct fc_seq *sp = fr_seq(fp); + struct fc4_prov *prov; + + /* + * Use RCU read lock and module_lock to be sure module doesn't + * deregister and get unloaded while we're calling it. + * try_module_get() is inlined and accepts a NULL parameter. + * Only ELSes and FCP target ops should come through here. + * The locking is unfortunate, and a better scheme is being sought. + */ + + rcu_read_lock(); + if (fh->fh_type >= FC_FC4_PROV_SIZE) + goto drop; + prov = rcu_dereference(fc_passive_prov[fh->fh_type]); + if (!prov || !try_module_get(prov->module)) + goto drop; + rcu_read_unlock(); + prov->recv(lport, fp); + module_put(prov->module); + return; +drop: + rcu_read_unlock(); + FC_LPORT_DBG(lport, "dropping unexpected frame type %x\n", fh->fh_type); + fc_frame_free(fp); + lport->tt.exch_done(sp); +} + /** * fc_lport_reset() - Reset a local port * @lport: The local port which should be reset @@ -1549,6 +1597,7 @@ int fc_lport_config(struct fc_lport *lport) fc_lport_add_fc4_type(lport, FC_TYPE_FCP); fc_lport_add_fc4_type(lport, FC_TYPE_CT); + fc_fc4_conf_lport_params(lport, FC_TYPE_FCP); return 0; } @@ -1586,6 +1635,7 @@ int fc_lport_init(struct fc_lport *lport) fc_host_supported_speeds(lport->host) |= FC_PORTSPEED_1GBIT; if (lport->link_supported_speeds & FC_PORTSPEED_10GBIT) fc_host_supported_speeds(lport->host) |= FC_PORTSPEED_10GBIT; + fc_fc4_add_lport(lport); return 0; } diff --git a/drivers/scsi/libfc/fc_npiv.c b/drivers/scsi/libfc/fc_npiv.c index dd2b43bb1c70..076cd5ff0e40 100644 --- a/drivers/scsi/libfc/fc_npiv.c +++ b/drivers/scsi/libfc/fc_npiv.c @@ -86,6 +86,7 @@ struct fc_lport *fc_vport_id_lookup(struct fc_lport *n_port, u32 port_id) return lport; } +EXPORT_SYMBOL(fc_vport_id_lookup); /* * When setting the link state of vports during an lport state change, it's diff --git a/drivers/scsi/libfc/fc_rport.c b/drivers/scsi/libfc/fc_rport.c index a7175adab32d..59b16bbb66a3 100644 --- a/drivers/scsi/libfc/fc_rport.c +++ b/drivers/scsi/libfc/fc_rport.c @@ -58,7 +58,7 @@ #include "fc_libfc.h" -struct workqueue_struct *rport_event_queue; +static struct workqueue_struct *rport_event_queue; static void fc_rport_enter_flogi(struct fc_rport_priv *); static void fc_rport_enter_plogi(struct fc_rport_priv *); @@ -145,8 +145,10 @@ static struct fc_rport_priv *fc_rport_create(struct fc_lport *lport, rdata->maxframe_size = FC_MIN_MAX_PAYLOAD; INIT_DELAYED_WORK(&rdata->retry_work, fc_rport_timeout); INIT_WORK(&rdata->event_work, fc_rport_work); - if (port_id != FC_FID_DIR_SERV) + if (port_id != FC_FID_DIR_SERV) { + rdata->lld_event_callback = lport->tt.rport_event_callback; list_add_rcu(&rdata->peers, &lport->disc.rports); + } return rdata; } @@ -257,6 +259,8 @@ static void fc_rport_work(struct work_struct *work) struct fc_rport_operations *rport_ops; struct fc_rport_identifiers ids; struct fc_rport *rport; + struct fc4_prov *prov; + u8 type; mutex_lock(&rdata->rp_mutex); event = rdata->event; @@ -300,12 +304,25 @@ static void fc_rport_work(struct work_struct *work) FC_RPORT_DBG(rdata, "callback ev %d\n", event); rport_ops->event_callback(lport, rdata, event); } + if (rdata->lld_event_callback) { + FC_RPORT_DBG(rdata, "lld callback ev %d\n", event); + rdata->lld_event_callback(lport, rdata, event); + } kref_put(&rdata->kref, lport->tt.rport_destroy); break; case RPORT_EV_FAILED: case RPORT_EV_LOGO: case RPORT_EV_STOP: + if (rdata->prli_count) { + mutex_lock(&fc_prov_mutex); + for (type = 1; type < FC_FC4_PROV_SIZE; type++) { + prov = fc_passive_prov[type]; + if (prov && prov->prlo) + prov->prlo(rdata); + } + mutex_unlock(&fc_prov_mutex); + } port_id = rdata->ids.port_id; mutex_unlock(&rdata->rp_mutex); @@ -313,6 +330,10 @@ static void fc_rport_work(struct work_struct *work) FC_RPORT_DBG(rdata, "callback ev %d\n", event); rport_ops->event_callback(lport, rdata, event); } + if (rdata->lld_event_callback) { + FC_RPORT_DBG(rdata, "lld callback ev %d\n", event); + rdata->lld_event_callback(lport, rdata, event); + } cancel_delayed_work_sync(&rdata->retry_work); /* @@ -575,7 +596,7 @@ static void fc_rport_error_retry(struct fc_rport_priv *rdata, /* make sure this isn't an FC_EX_CLOSED error, never retry those */ if (PTR_ERR(fp) == -FC_EX_CLOSED) - return fc_rport_error(rdata, fp); + goto out; if (rdata->retries < rdata->local_port->max_rport_retry_count) { FC_RPORT_DBG(rdata, "Error %ld in state %s, retrying\n", @@ -588,7 +609,8 @@ static void fc_rport_error_retry(struct fc_rport_priv *rdata, return; } - return fc_rport_error(rdata, fp); +out: + fc_rport_error(rdata, fp); } /** @@ -878,6 +900,9 @@ static void fc_rport_plogi_resp(struct fc_seq *sp, struct fc_frame *fp, rdata->ids.port_name = get_unaligned_be64(&plp->fl_wwpn); rdata->ids.node_name = get_unaligned_be64(&plp->fl_wwnn); + /* save plogi response sp_features for further reference */ + rdata->sp_features = ntohs(plp->fl_csp.sp_features); + if (lport->point_to_multipoint) fc_rport_login_complete(rdata, fp); csp_seq = ntohs(plp->fl_csp.sp_tot_seq); @@ -949,6 +974,8 @@ static void fc_rport_prli_resp(struct fc_seq *sp, struct fc_frame *fp, struct fc_els_prli prli; struct fc_els_spp spp; } *pp; + struct fc_els_spp temp_spp; + struct fc4_prov *prov; u32 roles = FC_RPORT_ROLE_UNKNOWN; u32 fcp_parm = 0; u8 op; @@ -983,6 +1010,7 @@ static void fc_rport_prli_resp(struct fc_seq *sp, struct fc_frame *fp, resp_code = (pp->spp.spp_flags & FC_SPP_RESP_MASK); FC_RPORT_DBG(rdata, "PRLI spp_flags = 0x%x\n", pp->spp.spp_flags); + rdata->spp_type = pp->spp.spp_type; if (resp_code != FC_SPP_RESP_ACK) { if (resp_code == FC_SPP_RESP_CONF) fc_rport_error(rdata, fp); @@ -996,6 +1024,15 @@ static void fc_rport_prli_resp(struct fc_seq *sp, struct fc_frame *fp, fcp_parm = ntohl(pp->spp.spp_params); if (fcp_parm & FCP_SPPF_RETRY) rdata->flags |= FC_RP_FLAGS_RETRY; + if (fcp_parm & FCP_SPPF_CONF_COMPL) + rdata->flags |= FC_RP_FLAGS_CONF_REQ; + + prov = fc_passive_prov[FC_TYPE_FCP]; + if (prov) { + memset(&temp_spp, 0, sizeof(temp_spp)); + prov->prli(rdata, pp->prli.prli_spp_len, + &pp->spp, &temp_spp); + } rdata->supported_classes = FC_COS_CLASS3; if (fcp_parm & FCP_SPPF_INIT_FCN) @@ -1033,6 +1070,7 @@ static void fc_rport_enter_prli(struct fc_rport_priv *rdata) struct fc_els_spp spp; } *pp; struct fc_frame *fp; + struct fc4_prov *prov; /* * If the rport is one of the well known addresses @@ -1054,9 +1092,20 @@ static void fc_rport_enter_prli(struct fc_rport_priv *rdata) return; } - if (!lport->tt.elsct_send(lport, rdata->ids.port_id, fp, ELS_PRLI, - fc_rport_prli_resp, rdata, - 2 * lport->r_a_tov)) + fc_prli_fill(lport, fp); + + prov = fc_passive_prov[FC_TYPE_FCP]; + if (prov) { + pp = fc_frame_payload_get(fp, sizeof(*pp)); + prov->prli(rdata, sizeof(pp->spp), NULL, &pp->spp); + } + + fc_fill_fc_hdr(fp, FC_RCTL_ELS_REQ, rdata->ids.port_id, + fc_host_port_id(lport->host), FC_TYPE_ELS, + FC_FC_FIRST_SEQ | FC_FC_END_SEQ | FC_FC_SEQ_INIT, 0); + + if (!lport->tt.exch_seq_send(lport, fp, fc_rport_prli_resp, + NULL, rdata, 2 * lport->r_a_tov)) fc_rport_error_retry(rdata, NULL); else kref_get(&rdata->kref); @@ -1642,9 +1691,9 @@ static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata, unsigned int len; unsigned int plen; enum fc_els_spp_resp resp; + enum fc_els_spp_resp passive; struct fc_seq_els_data rjt_data; - u32 fcp_parm; - u32 roles = FC_RPORT_ROLE_UNKNOWN; + struct fc4_prov *prov; FC_RPORT_DBG(rdata, "Received PRLI request while in state %s\n", fc_rport_state(rdata)); @@ -1678,46 +1727,42 @@ static void fc_rport_recv_prli_req(struct fc_rport_priv *rdata, pp->prli.prli_len = htons(len); len -= sizeof(struct fc_els_prli); - /* reinitialize remote port roles */ - rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN; - /* * Go through all the service parameter pages and build * response. If plen indicates longer SPP than standard, * use that. The entire response has been pre-cleared above. */ spp = &pp->spp; + mutex_lock(&fc_prov_mutex); while (len >= plen) { + rdata->spp_type = rspp->spp_type; spp->spp_type = rspp->spp_type; spp->spp_type_ext = rspp->spp_type_ext; - spp->spp_flags = rspp->spp_flags & FC_SPP_EST_IMG_PAIR; - resp = FC_SPP_RESP_ACK; - - switch (rspp->spp_type) { - case 0: /* common to all FC-4 types */ - break; - case FC_TYPE_FCP: - fcp_parm = ntohl(rspp->spp_params); - if (fcp_parm & FCP_SPPF_RETRY) - rdata->flags |= FC_RP_FLAGS_RETRY; - rdata->supported_classes = FC_COS_CLASS3; - if (fcp_parm & FCP_SPPF_INIT_FCN) - roles |= FC_RPORT_ROLE_FCP_INITIATOR; - if (fcp_parm & FCP_SPPF_TARG_FCN) - roles |= FC_RPORT_ROLE_FCP_TARGET; - rdata->ids.roles = roles; - - spp->spp_params = htonl(lport->service_params); - break; - default: - resp = FC_SPP_RESP_INVL; - break; + resp = 0; + + if (rspp->spp_type < FC_FC4_PROV_SIZE) { + prov = fc_active_prov[rspp->spp_type]; + if (prov) + resp = prov->prli(rdata, plen, rspp, spp); + prov = fc_passive_prov[rspp->spp_type]; + if (prov) { + passive = prov->prli(rdata, plen, rspp, spp); + if (!resp || passive == FC_SPP_RESP_ACK) + resp = passive; + } + } + if (!resp) { + if (spp->spp_flags & FC_SPP_EST_IMG_PAIR) + resp |= FC_SPP_RESP_CONF; + else + resp |= FC_SPP_RESP_INVL; } spp->spp_flags |= resp; len -= plen; rspp = (struct fc_els_spp *)((char *)rspp + plen); spp = (struct fc_els_spp *)((char *)spp + plen); } + mutex_unlock(&fc_prov_mutex); /* * Send LS_ACC. If this fails, the originator should retry. @@ -1887,9 +1932,82 @@ int fc_rport_init(struct fc_lport *lport) EXPORT_SYMBOL(fc_rport_init); /** + * fc_rport_fcp_prli() - Handle incoming PRLI for the FCP initiator. + * @rdata: remote port private + * @spp_len: service parameter page length + * @rspp: received service parameter page + * @spp: response service parameter page + * + * Returns the value for the response code to be placed in spp_flags; + * Returns 0 if not an initiator. + */ +static int fc_rport_fcp_prli(struct fc_rport_priv *rdata, u32 spp_len, + const struct fc_els_spp *rspp, + struct fc_els_spp *spp) +{ + struct fc_lport *lport = rdata->local_port; + u32 fcp_parm; + + fcp_parm = ntohl(rspp->spp_params); + rdata->ids.roles = FC_RPORT_ROLE_UNKNOWN; + if (fcp_parm & FCP_SPPF_INIT_FCN) + rdata->ids.roles |= FC_RPORT_ROLE_FCP_INITIATOR; + if (fcp_parm & FCP_SPPF_TARG_FCN) + rdata->ids.roles |= FC_RPORT_ROLE_FCP_TARGET; + if (fcp_parm & FCP_SPPF_RETRY) + rdata->flags |= FC_RP_FLAGS_RETRY; + rdata->supported_classes = FC_COS_CLASS3; + + if (!(lport->service_params & FC_RPORT_ROLE_FCP_INITIATOR)) + return 0; + + spp->spp_flags |= rspp->spp_flags & FC_SPP_EST_IMG_PAIR; + + /* + * OR in our service parameters with other providers (target), if any. + */ + fcp_parm = ntohl(spp->spp_params); + spp->spp_params = htonl(fcp_parm | lport->service_params); + return FC_SPP_RESP_ACK; +} + +/* + * FC-4 provider ops for FCP initiator. + */ +struct fc4_prov fc_rport_fcp_init = { + .prli = fc_rport_fcp_prli, +}; + +/** + * fc_rport_t0_prli() - Handle incoming PRLI parameters for type 0 + * @rdata: remote port private + * @spp_len: service parameter page length + * @rspp: received service parameter page + * @spp: response service parameter page + */ +static int fc_rport_t0_prli(struct fc_rport_priv *rdata, u32 spp_len, + const struct fc_els_spp *rspp, + struct fc_els_spp *spp) +{ + if (rspp->spp_flags & FC_SPP_EST_IMG_PAIR) + return FC_SPP_RESP_INVL; + return FC_SPP_RESP_ACK; +} + +/* + * FC-4 provider ops for type 0 service parameters. + * + * This handles the special case of type 0 which is always successful + * but doesn't do anything otherwise. + */ +struct fc4_prov fc_rport_t0_prov = { + .prli = fc_rport_t0_prli, +}; + +/** * fc_setup_rport() - Initialize the rport_event_queue */ -int fc_setup_rport() +int fc_setup_rport(void) { rport_event_queue = create_singlethread_workqueue("fc_rport_eq"); if (!rport_event_queue) @@ -1900,7 +2018,7 @@ int fc_setup_rport() /** * fc_destroy_rport() - Destroy the rport_event_queue */ -void fc_destroy_rport() +void fc_destroy_rport(void) { destroy_workqueue(rport_event_queue); } diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c index da8b61543ee4..0c550d5b9133 100644 --- a/drivers/scsi/libiscsi.c +++ b/drivers/scsi/libiscsi.c @@ -3352,6 +3352,47 @@ int iscsi_session_get_param(struct iscsi_cls_session *cls_session, } EXPORT_SYMBOL_GPL(iscsi_session_get_param); +int iscsi_conn_get_addr_param(struct sockaddr_storage *addr, + enum iscsi_param param, char *buf) +{ + struct sockaddr_in6 *sin6 = NULL; + struct sockaddr_in *sin = NULL; + int len; + + switch (addr->ss_family) { + case AF_INET: + sin = (struct sockaddr_in *)addr; + break; + case AF_INET6: + sin6 = (struct sockaddr_in6 *)addr; + break; + default: + return -EINVAL; + } + + switch (param) { + case ISCSI_PARAM_CONN_ADDRESS: + case ISCSI_HOST_PARAM_IPADDRESS: + if (sin) + len = sprintf(buf, "%pI4\n", &sin->sin_addr.s_addr); + else + len = sprintf(buf, "%pI6\n", &sin6->sin6_addr); + break; + case ISCSI_PARAM_CONN_PORT: + if (sin) + len = sprintf(buf, "%hu\n", be16_to_cpu(sin->sin_port)); + else + len = sprintf(buf, "%hu\n", + be16_to_cpu(sin6->sin6_port)); + break; + default: + return -EINVAL; + } + + return len; +} +EXPORT_SYMBOL_GPL(iscsi_conn_get_addr_param); + int iscsi_conn_get_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf) { @@ -3416,9 +3457,6 @@ int iscsi_host_get_param(struct Scsi_Host *shost, enum iscsi_host_param param, case ISCSI_HOST_PARAM_INITIATOR_NAME: len = sprintf(buf, "%s\n", ihost->initiatorname); break; - case ISCSI_HOST_PARAM_IPADDRESS: - len = sprintf(buf, "%s\n", ihost->local_address); - break; default: return -ENOSYS; } diff --git a/drivers/scsi/libsas/Kconfig b/drivers/scsi/libsas/Kconfig index 18f33cd54411..9dafe64e7c7a 100644 --- a/drivers/scsi/libsas/Kconfig +++ b/drivers/scsi/libsas/Kconfig @@ -46,11 +46,3 @@ config SCSI_SAS_HOST_SMP Allows sas hosts to receive SMP frames. Selecting this option builds an SMP interpreter into libsas. Say N here if you want to save the few kb this consumes. - -config SCSI_SAS_LIBSAS_DEBUG - bool "Compile the SAS Domain Transport Attributes in debug mode" - default y - depends on SCSI_SAS_LIBSAS - help - Compiles the SAS Layer in debug mode. In debug mode, the - SAS Layer prints diagnostic and debug messages. diff --git a/drivers/scsi/libsas/Makefile b/drivers/scsi/libsas/Makefile index 1ad1323c60fa..566a10024598 100644 --- a/drivers/scsi/libsas/Makefile +++ b/drivers/scsi/libsas/Makefile @@ -21,10 +21,6 @@ # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 # USA -ifeq ($(CONFIG_SCSI_SAS_LIBSAS_DEBUG),y) - EXTRA_CFLAGS += -DSAS_DEBUG -endif - obj-$(CONFIG_SCSI_SAS_LIBSAS) += libsas.o libsas-y += sas_init.o \ sas_phy.o \ diff --git a/drivers/scsi/libsas/sas_ata.c b/drivers/scsi/libsas/sas_ata.c index e1a395b438ee..d2b2d1474c28 100644 --- a/drivers/scsi/libsas/sas_ata.c +++ b/drivers/scsi/libsas/sas_ata.c @@ -71,13 +71,13 @@ static enum ata_completion_errors sas_to_ata_err(struct task_status_struct *ts) case SAS_SG_ERR: return AC_ERR_INVALID; - case SAM_STAT_CHECK_CONDITION: case SAS_OPEN_TO: case SAS_OPEN_REJECT: SAS_DPRINTK("%s: Saw error %d. What to do?\n", __func__, ts->stat); return AC_ERR_OTHER; + case SAM_STAT_CHECK_CONDITION: case SAS_ABORTED_TASK: return AC_ERR_DEV; @@ -107,13 +107,15 @@ static void sas_ata_task_done(struct sas_task *task) sas_ha = dev->port->ha; spin_lock_irqsave(dev->sata_dev.ap->lock, flags); - if (stat->stat == SAS_PROTO_RESPONSE || stat->stat == SAM_STAT_GOOD) { + if (stat->stat == SAS_PROTO_RESPONSE || stat->stat == SAM_STAT_GOOD || + ((stat->stat == SAM_STAT_CHECK_CONDITION && + dev->sata_dev.command_set == ATAPI_COMMAND_SET))) { ata_tf_from_fis(resp->ending_fis, &dev->sata_dev.tf); qc->err_mask |= ac_err_mask(dev->sata_dev.tf.command); dev->sata_dev.sstatus = resp->sstatus; dev->sata_dev.serror = resp->serror; dev->sata_dev.scontrol = resp->scontrol; - } else if (stat->stat != SAM_STAT_GOOD) { + } else { ac = sas_to_ata_err(stat); if (ac) { SAS_DPRINTK("%s: SAS error %x\n", __func__, @@ -238,37 +240,43 @@ static bool sas_ata_qc_fill_rtf(struct ata_queued_cmd *qc) return true; } -static void sas_ata_phy_reset(struct ata_port *ap) +static int sas_ata_hard_reset(struct ata_link *link, unsigned int *class, + unsigned long deadline) { + struct ata_port *ap = link->ap; struct domain_device *dev = ap->private_data; struct sas_internal *i = to_sas_internal(dev->port->ha->core.shost->transportt); int res = TMF_RESP_FUNC_FAILED; + int ret = 0; if (i->dft->lldd_I_T_nexus_reset) res = i->dft->lldd_I_T_nexus_reset(dev); - if (res != TMF_RESP_FUNC_COMPLETE) + if (res != TMF_RESP_FUNC_COMPLETE) { SAS_DPRINTK("%s: Unable to reset I T nexus?\n", __func__); + ret = -EAGAIN; + } switch (dev->sata_dev.command_set) { case ATA_COMMAND_SET: SAS_DPRINTK("%s: Found ATA device.\n", __func__); - ap->link.device[0].class = ATA_DEV_ATA; + *class = ATA_DEV_ATA; break; case ATAPI_COMMAND_SET: SAS_DPRINTK("%s: Found ATAPI device.\n", __func__); - ap->link.device[0].class = ATA_DEV_ATAPI; + *class = ATA_DEV_ATAPI; break; default: SAS_DPRINTK("%s: Unknown SATA command set: %d.\n", __func__, dev->sata_dev.command_set); - ap->link.device[0].class = ATA_DEV_UNKNOWN; + *class = ATA_DEV_UNKNOWN; break; } ap->cbl = ATA_CBL_SATA; + return ret; } static void sas_ata_post_internal(struct ata_queued_cmd *qc) @@ -299,57 +307,12 @@ static void sas_ata_post_internal(struct ata_queued_cmd *qc) } } -static int sas_ata_scr_write(struct ata_link *link, unsigned int sc_reg_in, - u32 val) -{ - struct domain_device *dev = link->ap->private_data; - - SAS_DPRINTK("STUB %s\n", __func__); - switch (sc_reg_in) { - case SCR_STATUS: - dev->sata_dev.sstatus = val; - break; - case SCR_CONTROL: - dev->sata_dev.scontrol = val; - break; - case SCR_ERROR: - dev->sata_dev.serror = val; - break; - case SCR_ACTIVE: - dev->sata_dev.ap->link.sactive = val; - break; - default: - return -EINVAL; - } - return 0; -} - -static int sas_ata_scr_read(struct ata_link *link, unsigned int sc_reg_in, - u32 *val) -{ - struct domain_device *dev = link->ap->private_data; - - SAS_DPRINTK("STUB %s\n", __func__); - switch (sc_reg_in) { - case SCR_STATUS: - *val = dev->sata_dev.sstatus; - return 0; - case SCR_CONTROL: - *val = dev->sata_dev.scontrol; - return 0; - case SCR_ERROR: - *val = dev->sata_dev.serror; - return 0; - case SCR_ACTIVE: - *val = dev->sata_dev.ap->link.sactive; - return 0; - default: - return -EINVAL; - } -} - static struct ata_port_operations sas_sata_ops = { - .phy_reset = sas_ata_phy_reset, + .prereset = ata_std_prereset, + .softreset = NULL, + .hardreset = sas_ata_hard_reset, + .postreset = ata_std_postreset, + .error_handler = ata_std_error_handler, .post_internal_cmd = sas_ata_post_internal, .qc_defer = ata_std_qc_defer, .qc_prep = ata_noop_qc_prep, @@ -357,15 +320,12 @@ static struct ata_port_operations sas_sata_ops = { .qc_fill_rtf = sas_ata_qc_fill_rtf, .port_start = ata_sas_port_start, .port_stop = ata_sas_port_stop, - .scr_read = sas_ata_scr_read, - .scr_write = sas_ata_scr_write }; static struct ata_port_info sata_port_info = { - .flags = ATA_FLAG_SATA | ATA_FLAG_NO_LEGACY | ATA_FLAG_SATA_RESET | - ATA_FLAG_MMIO | ATA_FLAG_PIO_DMA | ATA_FLAG_NCQ, - .pio_mask = 0x1f, /* PIO0-4 */ - .mwdma_mask = 0x07, /* MWDMA0-2 */ + .flags = ATA_FLAG_SATA | ATA_FLAG_PIO_DMA | ATA_FLAG_NCQ, + .pio_mask = ATA_PIO4, + .mwdma_mask = ATA_MWDMA2, .udma_mask = ATA_UDMA6, .port_ops = &sas_sata_ops }; @@ -781,3 +741,68 @@ int sas_discover_sata(struct domain_device *dev) return res; } + +void sas_ata_strategy_handler(struct Scsi_Host *shost) +{ + struct scsi_device *sdev; + + shost_for_each_device(sdev, shost) { + struct domain_device *ddev = sdev_to_domain_dev(sdev); + struct ata_port *ap = ddev->sata_dev.ap; + + if (!dev_is_sata(ddev)) + continue; + + ata_port_printk(ap, KERN_DEBUG, "sas eh calling libata port error handler"); + ata_scsi_port_error_handler(shost, ap); + } +} + +int sas_ata_timed_out(struct scsi_cmnd *cmd, struct sas_task *task, + enum blk_eh_timer_return *rtn) +{ + struct domain_device *ddev = cmd_to_domain_dev(cmd); + + if (!dev_is_sata(ddev) || task) + return 0; + + /* we're a sata device with no task, so this must be a libata + * eh timeout. Ideally should hook into libata timeout + * handling, but there's no point, it just wants to activate + * the eh thread */ + *rtn = BLK_EH_NOT_HANDLED; + return 1; +} + +int sas_ata_eh(struct Scsi_Host *shost, struct list_head *work_q, + struct list_head *done_q) +{ + int rtn = 0; + struct scsi_cmnd *cmd, *n; + struct ata_port *ap; + + do { + LIST_HEAD(sata_q); + + ap = NULL; + + list_for_each_entry_safe(cmd, n, work_q, eh_entry) { + struct domain_device *ddev = cmd_to_domain_dev(cmd); + + if (!dev_is_sata(ddev) || TO_SAS_TASK(cmd)) + continue; + if (ap && ap != ddev->sata_dev.ap) + continue; + ap = ddev->sata_dev.ap; + rtn = 1; + list_move(&cmd->eh_entry, &sata_q); + } + + if (!list_empty(&sata_q)) { + ata_port_printk(ap, KERN_DEBUG, "sas eh calling libata cmd error handler\n"); + ata_scsi_cmd_error_handler(shost, ap, &sata_q); + } + } while (ap); + + return rtn; +} diff --git a/drivers/scsi/libsas/sas_dump.c b/drivers/scsi/libsas/sas_dump.c index c17c25030f1c..fc460933575c 100644 --- a/drivers/scsi/libsas/sas_dump.c +++ b/drivers/scsi/libsas/sas_dump.c @@ -24,8 +24,6 @@ #include "sas_dump.h" -#ifdef SAS_DEBUG - static const char *sas_hae_str[] = { [0] = "HAE_RESET", }; @@ -72,5 +70,3 @@ void sas_dump_port(struct asd_sas_port *port) SAS_DPRINTK("port%d: oob_mode:0x%x\n", port->id, port->oob_mode); SAS_DPRINTK("port%d: num_phys:%d\n", port->id, port->num_phys); } - -#endif /* SAS_DEBUG */ diff --git a/drivers/scsi/libsas/sas_dump.h b/drivers/scsi/libsas/sas_dump.h index 47b45d4f5258..800e4c69093f 100644 --- a/drivers/scsi/libsas/sas_dump.h +++ b/drivers/scsi/libsas/sas_dump.h @@ -24,19 +24,7 @@ #include "sas_internal.h" -#ifdef SAS_DEBUG - void sas_dprint_porte(int phyid, enum port_event pe); void sas_dprint_phye(int phyid, enum phy_event pe); void sas_dprint_hae(struct sas_ha_struct *sas_ha, enum ha_event he); void sas_dump_port(struct asd_sas_port *port); - -#else /* SAS_DEBUG */ - -static inline void sas_dprint_porte(int phyid, enum port_event pe) { } -static inline void sas_dprint_phye(int phyid, enum phy_event pe) { } -static inline void sas_dprint_hae(struct sas_ha_struct *sas_ha, - enum ha_event he) { } -static inline void sas_dump_port(struct asd_sas_port *port) { } - -#endif /* SAS_DEBUG */ diff --git a/drivers/scsi/libsas/sas_expander.c b/drivers/scsi/libsas/sas_expander.c index 505ffe358293..f3f693b772ac 100644 --- a/drivers/scsi/libsas/sas_expander.c +++ b/drivers/scsi/libsas/sas_expander.c @@ -244,6 +244,11 @@ static int sas_ex_phy_discover_helper(struct domain_device *dev, u8 *disc_req, * dev to host FIS as described in section G.5 of * sas-2 r 04b */ dr = &((struct smp_resp *)disc_resp)->disc; + if (memcmp(dev->sas_addr, dr->attached_sas_addr, + SAS_ADDR_SIZE) == 0) { + sas_printk("Found loopback topology, just ignore it!\n"); + return 0; + } if (!(dr->attached_dev_type == 0 && dr->attached_sata_dev)) break; diff --git a/drivers/scsi/libsas/sas_internal.h b/drivers/scsi/libsas/sas_internal.h index 0001374bd6b2..8b538bd1ff2b 100644 --- a/drivers/scsi/libsas/sas_internal.h +++ b/drivers/scsi/libsas/sas_internal.h @@ -33,11 +33,7 @@ #define sas_printk(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__) -#ifdef SAS_DEBUG -#define SAS_DPRINTK(fmt, ...) printk(KERN_NOTICE "sas: " fmt, ## __VA_ARGS__) -#else -#define SAS_DPRINTK(fmt, ...) -#endif +#define SAS_DPRINTK(fmt, ...) printk(KERN_DEBUG "sas: " fmt, ## __VA_ARGS__) #define TO_SAS_TASK(_scsi_cmd) ((void *)(_scsi_cmd)->host_scribble) #define ASSIGN_SAS_TASK(_sc, _t) do { (_sc)->host_scribble = (void *) _t; } while (0) diff --git a/drivers/scsi/libsas/sas_scsi_host.c b/drivers/scsi/libsas/sas_scsi_host.c index 9a7aaf5f1311..bece759c51ec 100644 --- a/drivers/scsi/libsas/sas_scsi_host.c +++ b/drivers/scsi/libsas/sas_scsi_host.c @@ -663,11 +663,16 @@ void sas_scsi_recover_host(struct Scsi_Host *shost) * scsi_unjam_host does, but we skip scsi_eh_abort_cmds because any * command we see here has no sas_task and is thus unknown to the HA. */ - if (!scsi_eh_get_sense(&eh_work_q, &ha->eh_done_q)) - scsi_eh_ready_devs(shost, &eh_work_q, &ha->eh_done_q); + if (!sas_ata_eh(shost, &eh_work_q, &ha->eh_done_q)) + if (!scsi_eh_get_sense(&eh_work_q, &ha->eh_done_q)) + scsi_eh_ready_devs(shost, &eh_work_q, &ha->eh_done_q); out: + /* now link into libata eh --- if we have any ata devices */ + sas_ata_strategy_handler(shost); + scsi_eh_flush_done_q(&ha->eh_done_q); + SAS_DPRINTK("--- Exit %s\n", __func__); return; } @@ -676,6 +681,11 @@ enum blk_eh_timer_return sas_scsi_timed_out(struct scsi_cmnd *cmd) { struct sas_task *task = TO_SAS_TASK(cmd); unsigned long flags; + enum blk_eh_timer_return rtn; + + if (sas_ata_timed_out(cmd, task, &rtn)) + return rtn; + if (!task) { cmd->request->timeout /= 2; diff --git a/drivers/scsi/lpfc/lpfc.h b/drivers/scsi/lpfc/lpfc.h index 746dd3d7a092..b64c6da870d3 100644 --- a/drivers/scsi/lpfc/lpfc.h +++ b/drivers/scsi/lpfc/lpfc.h @@ -1,7 +1,7 @@ /******************************************************************* * This file is part of the Emulex Linux Device Driver for * * Fibre Channel Host Bus Adapters. * - * Copyright (C) 2004-2010 Emulex. All rights reserved. * + * Copyright (C) 2004-2011 Emulex. All rights reserved. * * EMULEX and SLI are trademarks of Emulex. * * www.emulex.com * * Portions Copyright (C) 2004-2005 Christoph Hellwig * @@ -325,6 +325,7 @@ struct lpfc_vport { #define FC_VPORT_CVL_RCVD 0x400000 /* VLink failed due to CVL */ #define FC_VFI_REGISTERED 0x800000 /* VFI is registered */ #define FC_FDISC_COMPLETED 0x1000000/* FDISC completed */ +#define FC_DISC_DELAYED 0x2000000/* Delay NPort discovery */ uint32_t ct_flags; #define FC_CT_RFF_ID 0x1 /* RFF_ID accepted by switch */ @@ -348,6 +349,8 @@ struct lpfc_vport { uint32_t fc_myDID; /* fibre channel S_ID */ uint32_t fc_prevDID; /* previous fibre channel S_ID */ + struct lpfc_name fabric_portname; + struct lpfc_name fabric_nodename; int32_t stopped; /* HBA has not been restarted since last ERATT */ uint8_t fc_linkspeed; /* Link speed after last READ_LA */ @@ -372,6 +375,7 @@ struct lpfc_vport { #define WORKER_DISC_TMO 0x1 /* vport: Discovery timeout */ #define WORKER_ELS_TMO 0x2 /* vport: ELS timeout */ #define WORKER_FDMI_TMO 0x4 /* vport: FDMI timeout */ +#define WORKER_DELAYED_DISC_TMO 0x8 /* vport: delayed discovery */ #define WORKER_MBOX_TMO 0x100 /* hba: MBOX timeout */ #define WORKER_HB_TMO 0x200 /* hba: Heart beat timeout */ @@ -382,6 +386,7 @@ struct lpfc_vport { struct timer_list fc_fdmitmo; struct timer_list els_tmofunc; + struct timer_list delayed_disc_tmo; int unreg_vpi_cmpl; @@ -548,6 +553,8 @@ struct lpfc_hba { #define LPFC_SLI3_CRP_ENABLED 0x08 #define LPFC_SLI3_BG_ENABLED 0x20 #define LPFC_SLI3_DSS_ENABLED 0x40 +#define LPFC_SLI4_PERFH_ENABLED 0x80 +#define LPFC_SLI4_PHWQ_ENABLED 0x100 uint32_t iocb_cmd_size; uint32_t iocb_rsp_size; @@ -655,7 +662,7 @@ struct lpfc_hba { #define LPFC_INITIALIZE_LINK 0 /* do normal init_link mbox */ #define LPFC_DELAY_INIT_LINK 1 /* layered driver hold off */ #define LPFC_DELAY_INIT_LINK_INDEFINITELY 2 /* wait, manual intervention */ - + uint32_t cfg_enable_dss; lpfc_vpd_t vpd; /* vital product data */ struct pci_dev *pcidev; @@ -792,6 +799,10 @@ struct lpfc_hba { struct dentry *debug_slow_ring_trc; struct lpfc_debugfs_trc *slow_ring_trc; atomic_t slow_ring_trc_cnt; + /* iDiag debugfs sub-directory */ + struct dentry *idiag_root; + struct dentry *idiag_pci_cfg; + struct dentry *idiag_que_info; #endif /* Used for deferred freeing of ELS data buffers */ diff --git a/drivers/scsi/lpfc/lpfc_attr.c b/drivers/scsi/lpfc/lpfc_attr.c index 3512abb8a587..e7c020df12fa 100644 --- a/drivers/scsi/lpfc/lpfc_attr.c +++ b/drivers/scsi/lpfc/lpfc_attr.c @@ -1,7 +1,7 @@ /******************************************************************* * This file is part of the Emulex Linux Device Driver for * * Fibre Channel Host Bus Adapters. * - * Copyright (C) 2004-2009 Emulex. All rights reserved. * + * Copyright (C) 2004-2011 Emulex. All rights reserved. * * EMULEX and SLI are trademarks of Emulex. * * www.emulex.com * * Portions Copyright (C) 2004-2005 Christoph Hellwig * @@ -623,10 +623,14 @@ lpfc_do_offline(struct lpfc_hba *phba, uint32_t type) int status = 0; int cnt = 0; int i; + int rc; init_completion(&online_compl); - lpfc_workq_post_event(phba, &status, &online_compl, + rc = lpfc_workq_post_event(phba, &status, &online_compl, LPFC_EVT_OFFLINE_PREP); + if (rc == 0) + return -ENOMEM; + wait_for_completion(&online_compl); if (status != 0) @@ -652,7 +656,10 @@ lpfc_do_offline(struct lpfc_hba *phba, uint32_t type) } init_completion(&online_compl); - lpfc_workq_post_event(phba, &status, &online_compl, type); + rc = lpfc_workq_post_event(phba, &status, &online_compl, type); + if (rc == 0) + return -ENOMEM; + wait_for_completion(&online_compl); if (status != 0) @@ -671,6 +678,7 @@ lpfc_do_offline(struct lpfc_hba *phba, uint32_t type) * * Notes: * Assumes any error from lpfc_do_offline() will be negative. + * Do not make this function static. * * Returns: * lpfc_do_offline() return code if not zero @@ -682,6 +690,7 @@ lpfc_selective_reset(struct lpfc_hba *phba) { struct completion online_compl; int status = 0; + int rc; if (!phba->cfg_enable_hba_reset) return -EIO; @@ -692,8 +701,11 @@ lpfc_selective_reset(struct lpfc_hba *phba) return status; init_completion(&online_compl); - lpfc_workq_post_event(phba, &status, &online_compl, + rc = lpfc_workq_post_event(phba, &status, &online_compl, LPFC_EVT_ONLINE); + if (rc == 0) + return -ENOMEM; + wait_for_completion(&online_compl); if (status != 0) @@ -812,14 +824,17 @@ lpfc_board_mode_store(struct device *dev, struct device_attribute *attr, struct lpfc_hba *phba = vport->phba; struct completion online_compl; int status=0; + int rc; if (!phba->cfg_enable_hba_reset) return -EACCES; init_completion(&online_compl); if(strncmp(buf, "online", sizeof("online") - 1) == 0) { - lpfc_workq_post_event(phba, &status, &online_compl, + rc = lpfc_workq_post_event(phba, &status, &online_compl, LPFC_EVT_ONLINE); + if (rc == 0) + return -ENOMEM; wait_for_completion(&online_compl); } else if (strncmp(buf, "offline", sizeof("offline") - 1) == 0) status = lpfc_do_offline(phba, LPFC_EVT_OFFLINE); @@ -1279,6 +1294,28 @@ lpfc_fips_rev_show(struct device *dev, struct device_attribute *attr, } /** + * lpfc_dss_show - Return the current state of dss and the configured state + * @dev: class converted to a Scsi_host structure. + * @attr: device attribute, not used. + * @buf: on return contains the formatted text. + * + * Returns: size of formatted string. + **/ +static ssize_t +lpfc_dss_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct Scsi_Host *shost = class_to_shost(dev); + struct lpfc_vport *vport = (struct lpfc_vport *) shost->hostdata; + struct lpfc_hba *phba = vport->phba; + + return snprintf(buf, PAGE_SIZE, "%s - %sOperational\n", + (phba->cfg_enable_dss) ? "Enabled" : "Disabled", + (phba->sli3_options & LPFC_SLI3_DSS_ENABLED) ? + "" : "Not "); +} + +/** * lpfc_param_show - Return a cfg attribute value in decimal * * Description: @@ -1597,13 +1634,13 @@ lpfc_##attr##_store(struct device *dev, struct device_attribute *attr, \ #define LPFC_ATTR(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_param_init(name, defval, minval, maxval) #define LPFC_ATTR_R(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_param_show(name)\ lpfc_param_init(name, defval, minval, maxval)\ @@ -1611,7 +1648,7 @@ static DEVICE_ATTR(lpfc_##name, S_IRUGO , lpfc_##name##_show, NULL) #define LPFC_ATTR_RW(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_param_show(name)\ lpfc_param_init(name, defval, minval, maxval)\ @@ -1622,7 +1659,7 @@ static DEVICE_ATTR(lpfc_##name, S_IRUGO | S_IWUSR,\ #define LPFC_ATTR_HEX_R(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_param_hex_show(name)\ lpfc_param_init(name, defval, minval, maxval)\ @@ -1630,7 +1667,7 @@ static DEVICE_ATTR(lpfc_##name, S_IRUGO , lpfc_##name##_show, NULL) #define LPFC_ATTR_HEX_RW(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_param_hex_show(name)\ lpfc_param_init(name, defval, minval, maxval)\ @@ -1641,13 +1678,13 @@ static DEVICE_ATTR(lpfc_##name, S_IRUGO | S_IWUSR,\ #define LPFC_VPORT_ATTR(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_vport_param_init(name, defval, minval, maxval) #define LPFC_VPORT_ATTR_R(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_vport_param_show(name)\ lpfc_vport_param_init(name, defval, minval, maxval)\ @@ -1655,7 +1692,7 @@ static DEVICE_ATTR(lpfc_##name, S_IRUGO , lpfc_##name##_show, NULL) #define LPFC_VPORT_ATTR_RW(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_vport_param_show(name)\ lpfc_vport_param_init(name, defval, minval, maxval)\ @@ -1666,7 +1703,7 @@ static DEVICE_ATTR(lpfc_##name, S_IRUGO | S_IWUSR,\ #define LPFC_VPORT_ATTR_HEX_R(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_vport_param_hex_show(name)\ lpfc_vport_param_init(name, defval, minval, maxval)\ @@ -1674,7 +1711,7 @@ static DEVICE_ATTR(lpfc_##name, S_IRUGO , lpfc_##name##_show, NULL) #define LPFC_VPORT_ATTR_HEX_RW(name, defval, minval, maxval, desc) \ static uint lpfc_##name = defval;\ -module_param(lpfc_##name, uint, 0);\ +module_param(lpfc_##name, uint, S_IRUGO);\ MODULE_PARM_DESC(lpfc_##name, desc);\ lpfc_vport_param_hex_show(name)\ lpfc_vport_param_init(name, defval, minval, maxval)\ @@ -1718,7 +1755,7 @@ static DEVICE_ATTR(npiv_info, S_IRUGO, lpfc_npiv_info_show, NULL); static DEVICE_ATTR(lpfc_temp_sensor, S_IRUGO, lpfc_temp_sensor_show, NULL); static DEVICE_ATTR(lpfc_fips_level, S_IRUGO, lpfc_fips_level_show, NULL); static DEVICE_ATTR(lpfc_fips_rev, S_IRUGO, lpfc_fips_rev_show, NULL); - +static DEVICE_ATTR(lpfc_dss, S_IRUGO, lpfc_dss_show, NULL); static char *lpfc_soft_wwn_key = "C99G71SL8032A"; @@ -1813,6 +1850,7 @@ lpfc_soft_wwpn_store(struct device *dev, struct device_attribute *attr, int stat1=0, stat2=0; unsigned int i, j, cnt=count; u8 wwpn[8]; + int rc; if (!phba->cfg_enable_hba_reset) return -EACCES; @@ -1863,7 +1901,11 @@ lpfc_soft_wwpn_store(struct device *dev, struct device_attribute *attr, "0463 lpfc_soft_wwpn attribute set failed to " "reinit adapter - %d\n", stat1); init_completion(&online_compl); - lpfc_workq_post_event(phba, &stat2, &online_compl, LPFC_EVT_ONLINE); + rc = lpfc_workq_post_event(phba, &stat2, &online_compl, + LPFC_EVT_ONLINE); + if (rc == 0) + return -ENOMEM; + wait_for_completion(&online_compl); if (stat2) lpfc_printf_log(phba, KERN_ERR, LOG_INIT, @@ -1954,7 +1996,7 @@ static DEVICE_ATTR(lpfc_soft_wwnn, S_IRUGO | S_IWUSR,\ static int lpfc_poll = 0; -module_param(lpfc_poll, int, 0); +module_param(lpfc_poll, int, S_IRUGO); MODULE_PARM_DESC(lpfc_poll, "FCP ring polling mode control:" " 0 - none," " 1 - poll with interrupts enabled" @@ -1964,21 +2006,21 @@ static DEVICE_ATTR(lpfc_poll, S_IRUGO | S_IWUSR, lpfc_poll_show, lpfc_poll_store); int lpfc_sli_mode = 0; -module_param(lpfc_sli_mode, int, 0); +module_param(lpfc_sli_mode, int, S_IRUGO); MODULE_PARM_DESC(lpfc_sli_mode, "SLI mode selector:" " 0 - auto (SLI-3 if supported)," " 2 - select SLI-2 even on SLI-3 capable HBAs," " 3 - select SLI-3"); int lpfc_enable_npiv = 1; -module_param(lpfc_enable_npiv, int, 0); +module_param(lpfc_enable_npiv, int, S_IRUGO); MODULE_PARM_DESC(lpfc_enable_npiv, "Enable NPIV functionality"); lpfc_param_show(enable_npiv); lpfc_param_init(enable_npiv, 1, 0, 1); static DEVICE_ATTR(lpfc_enable_npiv, S_IRUGO, lpfc_enable_npiv_show, NULL); int lpfc_enable_rrq; -module_param(lpfc_enable_rrq, int, 0); +module_param(lpfc_enable_rrq, int, S_IRUGO); MODULE_PARM_DESC(lpfc_enable_rrq, "Enable RRQ functionality"); lpfc_param_show(enable_rrq); lpfc_param_init(enable_rrq, 0, 0, 1); @@ -2040,7 +2082,7 @@ static DEVICE_ATTR(txcmplq_hw, S_IRUGO, lpfc_txcmplq_hw_show, NULL); int lpfc_iocb_cnt = 2; -module_param(lpfc_iocb_cnt, int, 1); +module_param(lpfc_iocb_cnt, int, S_IRUGO); MODULE_PARM_DESC(lpfc_iocb_cnt, "Number of IOCBs alloc for ELS, CT, and ABTS: 1k to 5k IOCBs"); lpfc_param_show(iocb_cnt); @@ -2192,7 +2234,7 @@ static DEVICE_ATTR(lpfc_nodev_tmo, S_IRUGO | S_IWUSR, # disappear until the timer expires. Value range is [0,255]. Default # value is 30. */ -module_param(lpfc_devloss_tmo, int, 0); +module_param(lpfc_devloss_tmo, int, S_IRUGO); MODULE_PARM_DESC(lpfc_devloss_tmo, "Seconds driver will hold I/O waiting " "for a device to come back"); @@ -2302,7 +2344,7 @@ LPFC_VPORT_ATTR_R(peer_port_login, 0, 0, 1, # Default value of this parameter is 1. */ static int lpfc_restrict_login = 1; -module_param(lpfc_restrict_login, int, 0); +module_param(lpfc_restrict_login, int, S_IRUGO); MODULE_PARM_DESC(lpfc_restrict_login, "Restrict virtual ports login to remote initiators."); lpfc_vport_param_show(restrict_login); @@ -2473,7 +2515,7 @@ lpfc_topology_store(struct device *dev, struct device_attribute *attr, return -EINVAL; } static int lpfc_topology = 0; -module_param(lpfc_topology, int, 0); +module_param(lpfc_topology, int, S_IRUGO); MODULE_PARM_DESC(lpfc_topology, "Select Fibre Channel topology"); lpfc_param_show(topology) lpfc_param_init(topology, 0, 0, 6) @@ -2915,7 +2957,7 @@ lpfc_link_speed_store(struct device *dev, struct device_attribute *attr, } static int lpfc_link_speed = 0; -module_param(lpfc_link_speed, int, 0); +module_param(lpfc_link_speed, int, S_IRUGO); MODULE_PARM_DESC(lpfc_link_speed, "Select link speed"); lpfc_param_show(link_speed) @@ -3043,7 +3085,7 @@ lpfc_aer_support_store(struct device *dev, struct device_attribute *attr, } static int lpfc_aer_support = 1; -module_param(lpfc_aer_support, int, 1); +module_param(lpfc_aer_support, int, S_IRUGO); MODULE_PARM_DESC(lpfc_aer_support, "Enable PCIe device AER support"); lpfc_param_show(aer_support) @@ -3155,7 +3197,7 @@ LPFC_VPORT_ATTR_RW(use_adisc, 0, 0, 1, # The value is set in milliseconds. */ static int lpfc_max_scsicmpl_time; -module_param(lpfc_max_scsicmpl_time, int, 0); +module_param(lpfc_max_scsicmpl_time, int, S_IRUGO); MODULE_PARM_DESC(lpfc_max_scsicmpl_time, "Use command completion time to control queue depth"); lpfc_vport_param_show(max_scsicmpl_time); @@ -3331,7 +3373,7 @@ LPFC_ATTR_R(enable_bg, 0, 0, 1, "Enable BlockGuard Support"); */ unsigned int lpfc_prot_mask = SHOST_DIF_TYPE1_PROTECTION; -module_param(lpfc_prot_mask, uint, 0); +module_param(lpfc_prot_mask, uint, S_IRUGO); MODULE_PARM_DESC(lpfc_prot_mask, "host protection mask"); /* @@ -3343,9 +3385,28 @@ MODULE_PARM_DESC(lpfc_prot_mask, "host protection mask"); # */ unsigned char lpfc_prot_guard = SHOST_DIX_GUARD_IP; -module_param(lpfc_prot_guard, byte, 0); +module_param(lpfc_prot_guard, byte, S_IRUGO); MODULE_PARM_DESC(lpfc_prot_guard, "host protection guard type"); +/* + * Delay initial NPort discovery when Clean Address bit is cleared in + * FLOGI/FDISC accept and FCID/Fabric name/Fabric portname is changed. + * This parameter can have value 0 or 1. + * When this parameter is set to 0, no delay is added to the initial + * discovery. + * When this parameter is set to non-zero value, initial Nport discovery is + * delayed by ra_tov seconds when Clean Address bit is cleared in FLOGI/FDISC + * accept and FCID/Fabric name/Fabric portname is changed. + * Driver always delay Nport discovery for subsequent FLOGI/FDISC completion + * when Clean Address bit is cleared in FLOGI/FDISC + * accept and FCID/Fabric name/Fabric portname is changed. + * Default value is 0. + */ +int lpfc_delay_discovery; +module_param(lpfc_delay_discovery, int, S_IRUGO); +MODULE_PARM_DESC(lpfc_delay_discovery, + "Delay NPort discovery when Clean Address bit is cleared. " + "Allowed values: 0,1."); /* * lpfc_sg_seg_cnt - Initial Maximum DMA Segment Count @@ -3437,6 +3498,7 @@ struct device_attribute *lpfc_hba_attrs[] = { &dev_attr_txcmplq_hw, &dev_attr_lpfc_fips_level, &dev_attr_lpfc_fips_rev, + &dev_attr_lpfc_dss, NULL, }; @@ -4639,6 +4701,7 @@ lpfc_get_cfgparam(struct lpfc_hba *phba) lpfc_aer_support_init(phba, lpfc_aer_support); lpfc_suppress_link_up_init(phba, lpfc_suppress_link_up); lpfc_iocb_cnt_init(phba, lpfc_iocb_cnt); + phba->cfg_enable_dss = 1; return; } diff --git a/drivers/scsi/lpfc/lpfc_crtn.h b/drivers/scsi/lpfc/lpfc_crtn.h index 17fde522c84a..3d40023f4804 100644 --- a/drivers/scsi/lpfc/lpfc_crtn.h +++ b/drivers/scsi/lpfc/lpfc_crtn.h @@ -53,9 +53,9 @@ void lpfc_unreg_vpi(struct lpfc_hba *, uint16_t, LPFC_MBOXQ_t *); void lpfc_init_link(struct lpfc_hba *, LPFC_MBOXQ_t *, uint32_t, uint32_t); void lpfc_request_features(struct lpfc_hba *, struct lpfcMboxq *); void lpfc_supported_pages(struct lpfcMboxq *); -void lpfc_sli4_params(struct lpfcMboxq *); +void lpfc_pc_sli4_params(struct lpfcMboxq *); int lpfc_pc_sli4_params_get(struct lpfc_hba *, LPFC_MBOXQ_t *); - +int lpfc_get_sli4_parameters(struct lpfc_hba *, LPFC_MBOXQ_t *); struct lpfc_vport *lpfc_find_vport_by_did(struct lpfc_hba *, uint32_t); void lpfc_cleanup_rcv_buffers(struct lpfc_vport *); void lpfc_rcv_seq_check_edtov(struct lpfc_vport *); @@ -167,6 +167,8 @@ int lpfc_ns_cmd(struct lpfc_vport *, int, uint8_t, uint32_t); int lpfc_fdmi_cmd(struct lpfc_vport *, struct lpfc_nodelist *, int); void lpfc_fdmi_tmo(unsigned long); void lpfc_fdmi_timeout_handler(struct lpfc_vport *); +void lpfc_delayed_disc_tmo(unsigned long); +void lpfc_delayed_disc_timeout_handler(struct lpfc_vport *); int lpfc_config_port_prep(struct lpfc_hba *); int lpfc_config_port_post(struct lpfc_hba *); @@ -341,6 +343,7 @@ extern struct fc_function_template lpfc_transport_functions; extern struct fc_function_template lpfc_vport_transport_functions; extern int lpfc_sli_mode; extern int lpfc_enable_npiv; +extern int lpfc_delay_discovery; int lpfc_vport_symbolic_node_name(struct lpfc_vport *, char *, size_t); int lpfc_vport_symbolic_port_name(struct lpfc_vport *, char *, size_t); @@ -423,6 +426,6 @@ int lpfc_send_rrq(struct lpfc_hba *, struct lpfc_node_rrq *); int lpfc_set_rrq_active(struct lpfc_hba *, struct lpfc_nodelist *, uint16_t, uint16_t, uint16_t); void lpfc_cleanup_wt_rrqs(struct lpfc_hba *); -void lpfc_cleanup_vports_rrqs(struct lpfc_vport *); +void lpfc_cleanup_vports_rrqs(struct lpfc_vport *, struct lpfc_nodelist *); struct lpfc_node_rrq *lpfc_get_active_rrq(struct lpfc_vport *, uint16_t, uint32_t); diff --git a/drivers/scsi/lpfc/lpfc_ct.c b/drivers/scsi/lpfc/lpfc_ct.c index c004fa9a681e..d9edfd90d7ff 100644 --- a/drivers/scsi/lpfc/lpfc_ct.c +++ b/drivers/scsi/lpfc/lpfc_ct.c @@ -1738,6 +1738,55 @@ fdmi_cmd_exit: return 1; } +/** + * lpfc_delayed_disc_tmo - Timeout handler for delayed discovery timer. + * @ptr - Context object of the timer. + * + * This function set the WORKER_DELAYED_DISC_TMO flag and wake up + * the worker thread. + **/ +void +lpfc_delayed_disc_tmo(unsigned long ptr) +{ + struct lpfc_vport *vport = (struct lpfc_vport *)ptr; + struct lpfc_hba *phba = vport->phba; + uint32_t tmo_posted; + unsigned long iflag; + + spin_lock_irqsave(&vport->work_port_lock, iflag); + tmo_posted = vport->work_port_events & WORKER_DELAYED_DISC_TMO; + if (!tmo_posted) + vport->work_port_events |= WORKER_DELAYED_DISC_TMO; + spin_unlock_irqrestore(&vport->work_port_lock, iflag); + + if (!tmo_posted) + lpfc_worker_wake_up(phba); + return; +} + +/** + * lpfc_delayed_disc_timeout_handler - Function called by worker thread to + * handle delayed discovery. + * @vport: pointer to a host virtual N_Port data structure. + * + * This function start nport discovery of the vport. + **/ +void +lpfc_delayed_disc_timeout_handler(struct lpfc_vport *vport) +{ + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + + spin_lock_irq(shost->host_lock); + if (!(vport->fc_flag & FC_DISC_DELAYED)) { + spin_unlock_irq(shost->host_lock); + return; + } + vport->fc_flag &= ~FC_DISC_DELAYED; + spin_unlock_irq(shost->host_lock); + + lpfc_do_scr_ns_plogi(vport->phba, vport); +} + void lpfc_fdmi_tmo(unsigned long ptr) { diff --git a/drivers/scsi/lpfc/lpfc_debugfs.c b/drivers/scsi/lpfc/lpfc_debugfs.c index a80d938fafc9..a753581509d6 100644 --- a/drivers/scsi/lpfc/lpfc_debugfs.c +++ b/drivers/scsi/lpfc/lpfc_debugfs.c @@ -1,7 +1,7 @@ /******************************************************************* * This file is part of the Emulex Linux Device Driver for * * Fibre Channel Host Bus Adapters. * - * Copyright (C) 2007-2009 Emulex. All rights reserved. * + * Copyright (C) 2007-2011 Emulex. All rights reserved. * * EMULEX and SLI are trademarks of Emulex. * * www.emulex.com * * * @@ -57,8 +57,8 @@ * # mount -t debugfs none /sys/kernel/debug * * The lpfc debugfs directory hierarchy is: - * lpfc/lpfcX/vportY - * where X is the lpfc hba unique_id + * /sys/kernel/debug/lpfc/fnX/vportY + * where X is the lpfc hba function unique_id * where Y is the vport VPI on that hba * * Debugging services available per vport: @@ -82,52 +82,34 @@ * the HBA. X MUST also be a power of 2. */ static int lpfc_debugfs_enable = 1; -module_param(lpfc_debugfs_enable, int, 0); +module_param(lpfc_debugfs_enable, int, S_IRUGO); MODULE_PARM_DESC(lpfc_debugfs_enable, "Enable debugfs services"); /* This MUST be a power of 2 */ static int lpfc_debugfs_max_disc_trc; -module_param(lpfc_debugfs_max_disc_trc, int, 0); +module_param(lpfc_debugfs_max_disc_trc, int, S_IRUGO); MODULE_PARM_DESC(lpfc_debugfs_max_disc_trc, "Set debugfs discovery trace depth"); /* This MUST be a power of 2 */ static int lpfc_debugfs_max_slow_ring_trc; -module_param(lpfc_debugfs_max_slow_ring_trc, int, 0); +module_param(lpfc_debugfs_max_slow_ring_trc, int, S_IRUGO); MODULE_PARM_DESC(lpfc_debugfs_max_slow_ring_trc, "Set debugfs slow ring trace depth"); static int lpfc_debugfs_mask_disc_trc; -module_param(lpfc_debugfs_mask_disc_trc, int, 0); +module_param(lpfc_debugfs_mask_disc_trc, int, S_IRUGO); MODULE_PARM_DESC(lpfc_debugfs_mask_disc_trc, "Set debugfs discovery trace mask"); #include <linux/debugfs.h> -/* size of output line, for discovery_trace and slow_ring_trace */ -#define LPFC_DEBUG_TRC_ENTRY_SIZE 100 - -/* nodelist output buffer size */ -#define LPFC_NODELIST_SIZE 8192 -#define LPFC_NODELIST_ENTRY_SIZE 120 - -/* dumpHBASlim output buffer size */ -#define LPFC_DUMPHBASLIM_SIZE 4096 - -/* dumpHostSlim output buffer size */ -#define LPFC_DUMPHOSTSLIM_SIZE 4096 - -/* hbqinfo output buffer size */ -#define LPFC_HBQINFO_SIZE 8192 - -struct lpfc_debug { - char *buffer; - int len; -}; - static atomic_t lpfc_debugfs_seq_trc_cnt = ATOMIC_INIT(0); static unsigned long lpfc_debugfs_start_time = 0L; +/* iDiag */ +static struct lpfc_idiag idiag; + /** * lpfc_debugfs_disc_trc_data - Dump discovery logging to a buffer * @vport: The vport to gather the log info from. @@ -996,8 +978,6 @@ lpfc_debugfs_dumpDataDif_write(struct file *file, const char __user *buf, return nbytes; } - - /** * lpfc_debugfs_nodelist_open - Open the nodelist debugfs file * @inode: The inode pointer that contains a vport pointer. @@ -1099,6 +1079,7 @@ lpfc_debugfs_read(struct file *file, char __user *buf, size_t nbytes, loff_t *ppos) { struct lpfc_debug *debug = file->private_data; + return simple_read_from_buffer(buf, nbytes, ppos, debug->buffer, debug->len); } @@ -1137,6 +1118,776 @@ lpfc_debugfs_dumpDataDif_release(struct inode *inode, struct file *file) return 0; } +/* + * iDiag debugfs file access methods + */ + +/* + * iDiag PCI config space register access methods: + * + * The PCI config space register accessees of read, write, read-modify-write + * for set bits, and read-modify-write for clear bits to SLI4 PCI functions + * are provided. In the proper SLI4 PCI function's debugfs iDiag directory, + * + * /sys/kernel/debug/lpfc/fn<#>/iDiag + * + * the access is through the debugfs entry pciCfg: + * + * 1. For PCI config space register read access, there are two read methods: + * A) read a single PCI config space register in the size of a byte + * (8 bits), a word (16 bits), or a dword (32 bits); or B) browse through + * the 4K extended PCI config space. + * + * A) Read a single PCI config space register consists of two steps: + * + * Step-1: Set up PCI config space register read command, the command + * syntax is, + * + * echo 1 <where> <count> > pciCfg + * + * where, 1 is the iDiag command for PCI config space read, <where> is the + * offset from the beginning of the device's PCI config space to read from, + * and <count> is the size of PCI config space register data to read back, + * it will be 1 for reading a byte (8 bits), 2 for reading a word (16 bits + * or 2 bytes), or 4 for reading a dword (32 bits or 4 bytes). + * + * Setp-2: Perform the debugfs read operation to execute the idiag command + * set up in Step-1, + * + * cat pciCfg + * + * Examples: + * To read PCI device's vendor-id and device-id from PCI config space, + * + * echo 1 0 4 > pciCfg + * cat pciCfg + * + * To read PCI device's currnt command from config space, + * + * echo 1 4 2 > pciCfg + * cat pciCfg + * + * B) Browse through the entire 4K extended PCI config space also consists + * of two steps: + * + * Step-1: Set up PCI config space register browsing command, the command + * syntax is, + * + * echo 1 0 4096 > pciCfg + * + * where, 1 is the iDiag command for PCI config space read, 0 must be used + * as the offset for PCI config space register browse, and 4096 must be + * used as the count for PCI config space register browse. + * + * Step-2: Repeately issue the debugfs read operation to browse through + * the entire PCI config space registers: + * + * cat pciCfg + * cat pciCfg + * cat pciCfg + * ... + * + * When browsing to the end of the 4K PCI config space, the browse method + * shall wrap around to start reading from beginning again, and again... + * + * 2. For PCI config space register write access, it supports a single PCI + * config space register write in the size of a byte (8 bits), a word + * (16 bits), or a dword (32 bits). The command syntax is, + * + * echo 2 <where> <count> <value> > pciCfg + * + * where, 2 is the iDiag command for PCI config space write, <where> is + * the offset from the beginning of the device's PCI config space to write + * into, <count> is the size of data to write into the PCI config space, + * it will be 1 for writing a byte (8 bits), 2 for writing a word (16 bits + * or 2 bytes), or 4 for writing a dword (32 bits or 4 bytes), and <value> + * is the data to be written into the PCI config space register at the + * offset. + * + * Examples: + * To disable PCI device's interrupt assertion, + * + * 1) Read in device's PCI config space register command field <cmd>: + * + * echo 1 4 2 > pciCfg + * cat pciCfg + * + * 2) Set bit 10 (Interrupt Disable bit) in the <cmd>: + * + * <cmd> = <cmd> | (1 < 10) + * + * 3) Write the modified command back: + * + * echo 2 4 2 <cmd> > pciCfg + * + * 3. For PCI config space register set bits access, it supports a single PCI + * config space register set bits in the size of a byte (8 bits), a word + * (16 bits), or a dword (32 bits). The command syntax is, + * + * echo 3 <where> <count> <bitmask> > pciCfg + * + * where, 3 is the iDiag command for PCI config space set bits, <where> is + * the offset from the beginning of the device's PCI config space to set + * bits into, <count> is the size of the bitmask to set into the PCI config + * space, it will be 1 for setting a byte (8 bits), 2 for setting a word + * (16 bits or 2 bytes), or 4 for setting a dword (32 bits or 4 bytes), and + * <bitmask> is the bitmask, indicating the bits to be set into the PCI + * config space register at the offset. The logic performed to the content + * of the PCI config space register, regval, is, + * + * regval |= <bitmask> + * + * 4. For PCI config space register clear bits access, it supports a single + * PCI config space register clear bits in the size of a byte (8 bits), + * a word (16 bits), or a dword (32 bits). The command syntax is, + * + * echo 4 <where> <count> <bitmask> > pciCfg + * + * where, 4 is the iDiag command for PCI config space clear bits, <where> + * is the offset from the beginning of the device's PCI config space to + * clear bits from, <count> is the size of the bitmask to set into the PCI + * config space, it will be 1 for setting a byte (8 bits), 2 for setting + * a word(16 bits or 2 bytes), or 4 for setting a dword (32 bits or 4 + * bytes), and <bitmask> is the bitmask, indicating the bits to be cleared + * from the PCI config space register at the offset. the logic performed + * to the content of the PCI config space register, regval, is, + * + * regval &= ~<bitmask> + * + * Note, for all single register read, write, set bits, or clear bits access, + * the offset (<where>) must be aligned with the size of the data: + * + * For data size of byte (8 bits), the offset must be aligned to the byte + * boundary; for data size of word (16 bits), the offset must be aligned + * to the word boundary; while for data size of dword (32 bits), the offset + * must be aligned to the dword boundary. Otherwise, the interface will + * return the error: + * + * "-bash: echo: write error: Invalid argument". + * + * For example: + * + * echo 1 2 4 > pciCfg + * -bash: echo: write error: Invalid argument + * + * Note also, all of the numbers in the command fields for all read, write, + * set bits, and clear bits PCI config space register command fields can be + * either decimal or hex. + * + * For example, + * echo 1 0 4096 > pciCfg + * + * will be the same as + * echo 1 0 0x1000 > pciCfg + * + * And, + * echo 2 155 1 10 > pciCfg + * + * will be + * echo 2 0x9b 1 0xa > pciCfg + */ + +/** + * lpfc_idiag_cmd_get - Get and parse idiag debugfs comands from user space + * @buf: The pointer to the user space buffer. + * @nbytes: The number of bytes in the user space buffer. + * @idiag_cmd: pointer to the idiag command struct. + * + * This routine reads data from debugfs user space buffer and parses the + * buffer for getting the idiag command and arguments. The while space in + * between the set of data is used as the parsing separator. + * + * This routine returns 0 when successful, it returns proper error code + * back to the user space in error conditions. + */ +static int lpfc_idiag_cmd_get(const char __user *buf, size_t nbytes, + struct lpfc_idiag_cmd *idiag_cmd) +{ + char mybuf[64]; + char *pbuf, *step_str; + int bsize, i; + + /* Protect copy from user */ + if (!access_ok(VERIFY_READ, buf, nbytes)) + return -EFAULT; + + memset(mybuf, 0, sizeof(mybuf)); + memset(idiag_cmd, 0, sizeof(*idiag_cmd)); + bsize = min(nbytes, (sizeof(mybuf)-1)); + + if (copy_from_user(mybuf, buf, bsize)) + return -EFAULT; + pbuf = &mybuf[0]; + step_str = strsep(&pbuf, "\t "); + + /* The opcode must present */ + if (!step_str) + return -EINVAL; + + idiag_cmd->opcode = simple_strtol(step_str, NULL, 0); + if (idiag_cmd->opcode == 0) + return -EINVAL; + + for (i = 0; i < LPFC_IDIAG_CMD_DATA_SIZE; i++) { + step_str = strsep(&pbuf, "\t "); + if (!step_str) + return 0; + idiag_cmd->data[i] = simple_strtol(step_str, NULL, 0); + } + return 0; +} + +/** + * lpfc_idiag_open - idiag open debugfs + * @inode: The inode pointer that contains a pointer to phba. + * @file: The file pointer to attach the file operation. + * + * Description: + * This routine is the entry point for the debugfs open file operation. It + * gets the reference to phba from the i_private field in @inode, it then + * allocates buffer for the file operation, performs the necessary PCI config + * space read into the allocated buffer according to the idiag user command + * setup, and then returns a pointer to buffer in the private_data field in + * @file. + * + * Returns: + * This function returns zero if successful. On error it will return an + * negative error value. + **/ +static int +lpfc_idiag_open(struct inode *inode, struct file *file) +{ + struct lpfc_debug *debug; + + debug = kmalloc(sizeof(*debug), GFP_KERNEL); + if (!debug) + return -ENOMEM; + + debug->i_private = inode->i_private; + debug->buffer = NULL; + file->private_data = debug; + + return 0; +} + +/** + * lpfc_idiag_release - Release idiag access file operation + * @inode: The inode pointer that contains a vport pointer. (unused) + * @file: The file pointer that contains the buffer to release. + * + * Description: + * This routine is the generic release routine for the idiag access file + * operation, it frees the buffer that was allocated when the debugfs file + * was opened. + * + * Returns: + * This function returns zero. + **/ +static int +lpfc_idiag_release(struct inode *inode, struct file *file) +{ + struct lpfc_debug *debug = file->private_data; + + /* Free the buffers to the file operation */ + kfree(debug->buffer); + kfree(debug); + + return 0; +} + +/** + * lpfc_idiag_cmd_release - Release idiag cmd access file operation + * @inode: The inode pointer that contains a vport pointer. (unused) + * @file: The file pointer that contains the buffer to release. + * + * Description: + * This routine frees the buffer that was allocated when the debugfs file + * was opened. It also reset the fields in the idiag command struct in the + * case the command is not continuous browsing of the data structure. + * + * Returns: + * This function returns zero. + **/ +static int +lpfc_idiag_cmd_release(struct inode *inode, struct file *file) +{ + struct lpfc_debug *debug = file->private_data; + + /* Read PCI config register, if not read all, clear command fields */ + if ((debug->op == LPFC_IDIAG_OP_RD) && + (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_RD)) + if ((idiag.cmd.data[1] == sizeof(uint8_t)) || + (idiag.cmd.data[1] == sizeof(uint16_t)) || + (idiag.cmd.data[1] == sizeof(uint32_t))) + memset(&idiag, 0, sizeof(idiag)); + + /* Write PCI config register, clear command fields */ + if ((debug->op == LPFC_IDIAG_OP_WR) && + (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_WR)) + memset(&idiag, 0, sizeof(idiag)); + + /* Free the buffers to the file operation */ + kfree(debug->buffer); + kfree(debug); + + return 0; +} + +/** + * lpfc_idiag_pcicfg_read - idiag debugfs read pcicfg + * @file: The file pointer to read from. + * @buf: The buffer to copy the data to. + * @nbytes: The number of bytes to read. + * @ppos: The position in the file to start reading from. + * + * Description: + * This routine reads data from the @phba pci config space according to the + * idiag command, and copies to user @buf. Depending on the PCI config space + * read command setup, it does either a single register read of a byte + * (8 bits), a word (16 bits), or a dword (32 bits) or browsing through all + * registers from the 4K extended PCI config space. + * + * Returns: + * This function returns the amount of data that was read (this could be less + * than @nbytes if the end of the file was reached) or a negative error value. + **/ +static ssize_t +lpfc_idiag_pcicfg_read(struct file *file, char __user *buf, size_t nbytes, + loff_t *ppos) +{ + struct lpfc_debug *debug = file->private_data; + struct lpfc_hba *phba = (struct lpfc_hba *)debug->i_private; + int offset_label, offset, len = 0, index = LPFC_PCI_CFG_RD_SIZE; + int where, count; + char *pbuffer; + struct pci_dev *pdev; + uint32_t u32val; + uint16_t u16val; + uint8_t u8val; + + pdev = phba->pcidev; + if (!pdev) + return 0; + + /* This is a user read operation */ + debug->op = LPFC_IDIAG_OP_RD; + + if (!debug->buffer) + debug->buffer = kmalloc(LPFC_PCI_CFG_SIZE, GFP_KERNEL); + if (!debug->buffer) + return 0; + pbuffer = debug->buffer; + + if (*ppos) + return 0; + + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_RD) { + where = idiag.cmd.data[0]; + count = idiag.cmd.data[1]; + } else + return 0; + + /* Read single PCI config space register */ + switch (count) { + case SIZE_U8: /* byte (8 bits) */ + pci_read_config_byte(pdev, where, &u8val); + len += snprintf(pbuffer+len, LPFC_PCI_CFG_SIZE-len, + "%03x: %02x\n", where, u8val); + break; + case SIZE_U16: /* word (16 bits) */ + pci_read_config_word(pdev, where, &u16val); + len += snprintf(pbuffer+len, LPFC_PCI_CFG_SIZE-len, + "%03x: %04x\n", where, u16val); + break; + case SIZE_U32: /* double word (32 bits) */ + pci_read_config_dword(pdev, where, &u32val); + len += snprintf(pbuffer+len, LPFC_PCI_CFG_SIZE-len, + "%03x: %08x\n", where, u32val); + break; + case LPFC_PCI_CFG_SIZE: /* browse all */ + goto pcicfg_browse; + break; + default: + /* illegal count */ + len = 0; + break; + } + return simple_read_from_buffer(buf, nbytes, ppos, pbuffer, len); + +pcicfg_browse: + + /* Browse all PCI config space registers */ + offset_label = idiag.offset.last_rd; + offset = offset_label; + + /* Read PCI config space */ + len += snprintf(pbuffer+len, LPFC_PCI_CFG_SIZE-len, + "%03x: ", offset_label); + while (index > 0) { + pci_read_config_dword(pdev, offset, &u32val); + len += snprintf(pbuffer+len, LPFC_PCI_CFG_SIZE-len, + "%08x ", u32val); + offset += sizeof(uint32_t); + index -= sizeof(uint32_t); + if (!index) + len += snprintf(pbuffer+len, LPFC_PCI_CFG_SIZE-len, + "\n"); + else if (!(index % (8 * sizeof(uint32_t)))) { + offset_label += (8 * sizeof(uint32_t)); + len += snprintf(pbuffer+len, LPFC_PCI_CFG_SIZE-len, + "\n%03x: ", offset_label); + } + } + + /* Set up the offset for next portion of pci cfg read */ + idiag.offset.last_rd += LPFC_PCI_CFG_RD_SIZE; + if (idiag.offset.last_rd >= LPFC_PCI_CFG_SIZE) + idiag.offset.last_rd = 0; + + return simple_read_from_buffer(buf, nbytes, ppos, pbuffer, len); +} + +/** + * lpfc_idiag_pcicfg_write - Syntax check and set up idiag pcicfg commands + * @file: The file pointer to read from. + * @buf: The buffer to copy the user data from. + * @nbytes: The number of bytes to get. + * @ppos: The position in the file to start reading from. + * + * This routine get the debugfs idiag command struct from user space and + * then perform the syntax check for PCI config space read or write command + * accordingly. In the case of PCI config space read command, it sets up + * the command in the idiag command struct for the debugfs read operation. + * In the case of PCI config space write operation, it executes the write + * operation into the PCI config space accordingly. + * + * It returns the @nbytges passing in from debugfs user space when successful. + * In case of error conditions, it returns proper error code back to the user + * space. + */ +static ssize_t +lpfc_idiag_pcicfg_write(struct file *file, const char __user *buf, + size_t nbytes, loff_t *ppos) +{ + struct lpfc_debug *debug = file->private_data; + struct lpfc_hba *phba = (struct lpfc_hba *)debug->i_private; + uint32_t where, value, count; + uint32_t u32val; + uint16_t u16val; + uint8_t u8val; + struct pci_dev *pdev; + int rc; + + pdev = phba->pcidev; + if (!pdev) + return -EFAULT; + + /* This is a user write operation */ + debug->op = LPFC_IDIAG_OP_WR; + + rc = lpfc_idiag_cmd_get(buf, nbytes, &idiag.cmd); + if (rc) + return rc; + + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_RD) { + /* Read command from PCI config space, set up command fields */ + where = idiag.cmd.data[0]; + count = idiag.cmd.data[1]; + if (count == LPFC_PCI_CFG_SIZE) { + if (where != 0) + goto error_out; + } else if ((count != sizeof(uint8_t)) && + (count != sizeof(uint16_t)) && + (count != sizeof(uint32_t))) + goto error_out; + if (count == sizeof(uint8_t)) { + if (where > LPFC_PCI_CFG_SIZE - sizeof(uint8_t)) + goto error_out; + if (where % sizeof(uint8_t)) + goto error_out; + } + if (count == sizeof(uint16_t)) { + if (where > LPFC_PCI_CFG_SIZE - sizeof(uint16_t)) + goto error_out; + if (where % sizeof(uint16_t)) + goto error_out; + } + if (count == sizeof(uint32_t)) { + if (where > LPFC_PCI_CFG_SIZE - sizeof(uint32_t)) + goto error_out; + if (where % sizeof(uint32_t)) + goto error_out; + } + } else if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_WR || + idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_ST || + idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_CL) { + /* Write command to PCI config space, read-modify-write */ + where = idiag.cmd.data[0]; + count = idiag.cmd.data[1]; + value = idiag.cmd.data[2]; + /* Sanity checks */ + if ((count != sizeof(uint8_t)) && + (count != sizeof(uint16_t)) && + (count != sizeof(uint32_t))) + goto error_out; + if (count == sizeof(uint8_t)) { + if (where > LPFC_PCI_CFG_SIZE - sizeof(uint8_t)) + goto error_out; + if (where % sizeof(uint8_t)) + goto error_out; + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_WR) + pci_write_config_byte(pdev, where, + (uint8_t)value); + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_ST) { + rc = pci_read_config_byte(pdev, where, &u8val); + if (!rc) { + u8val |= (uint8_t)value; + pci_write_config_byte(pdev, where, + u8val); + } + } + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_CL) { + rc = pci_read_config_byte(pdev, where, &u8val); + if (!rc) { + u8val &= (uint8_t)(~value); + pci_write_config_byte(pdev, where, + u8val); + } + } + } + if (count == sizeof(uint16_t)) { + if (where > LPFC_PCI_CFG_SIZE - sizeof(uint16_t)) + goto error_out; + if (where % sizeof(uint16_t)) + goto error_out; + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_WR) + pci_write_config_word(pdev, where, + (uint16_t)value); + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_ST) { + rc = pci_read_config_word(pdev, where, &u16val); + if (!rc) { + u16val |= (uint16_t)value; + pci_write_config_word(pdev, where, + u16val); + } + } + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_CL) { + rc = pci_read_config_word(pdev, where, &u16val); + if (!rc) { + u16val &= (uint16_t)(~value); + pci_write_config_word(pdev, where, + u16val); + } + } + } + if (count == sizeof(uint32_t)) { + if (where > LPFC_PCI_CFG_SIZE - sizeof(uint32_t)) + goto error_out; + if (where % sizeof(uint32_t)) + goto error_out; + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_WR) + pci_write_config_dword(pdev, where, value); + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_ST) { + rc = pci_read_config_dword(pdev, where, + &u32val); + if (!rc) { + u32val |= value; + pci_write_config_dword(pdev, where, + u32val); + } + } + if (idiag.cmd.opcode == LPFC_IDIAG_CMD_PCICFG_CL) { + rc = pci_read_config_dword(pdev, where, + &u32val); + if (!rc) { + u32val &= ~value; + pci_write_config_dword(pdev, where, + u32val); + } + } + } + } else + /* All other opecodes are illegal for now */ + goto error_out; + + return nbytes; +error_out: + memset(&idiag, 0, sizeof(idiag)); + return -EINVAL; +} + +/** + * lpfc_idiag_queinfo_read - idiag debugfs read queue information + * @file: The file pointer to read from. + * @buf: The buffer to copy the data to. + * @nbytes: The number of bytes to read. + * @ppos: The position in the file to start reading from. + * + * Description: + * This routine reads data from the @phba SLI4 PCI function queue information, + * and copies to user @buf. + * + * Returns: + * This function returns the amount of data that was read (this could be less + * than @nbytes if the end of the file was reached) or a negative error value. + **/ +static ssize_t +lpfc_idiag_queinfo_read(struct file *file, char __user *buf, size_t nbytes, + loff_t *ppos) +{ + struct lpfc_debug *debug = file->private_data; + struct lpfc_hba *phba = (struct lpfc_hba *)debug->i_private; + int len = 0, fcp_qidx; + char *pbuffer; + + if (!debug->buffer) + debug->buffer = kmalloc(LPFC_QUE_INFO_GET_BUF_SIZE, GFP_KERNEL); + if (!debug->buffer) + return 0; + pbuffer = debug->buffer; + + if (*ppos) + return 0; + + /* Get slow-path event queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Slow-path EQ information:\n"); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], EQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n\n", + phba->sli4_hba.sp_eq->queue_id, + phba->sli4_hba.sp_eq->entry_count, + phba->sli4_hba.sp_eq->host_index, + phba->sli4_hba.sp_eq->hba_index); + + /* Get fast-path event queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Fast-path EQ information:\n"); + for (fcp_qidx = 0; fcp_qidx < phba->cfg_fcp_eq_count; fcp_qidx++) { + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], EQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n", + phba->sli4_hba.fp_eq[fcp_qidx]->queue_id, + phba->sli4_hba.fp_eq[fcp_qidx]->entry_count, + phba->sli4_hba.fp_eq[fcp_qidx]->host_index, + phba->sli4_hba.fp_eq[fcp_qidx]->hba_index); + } + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, "\n"); + + /* Get mailbox complete queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Mailbox CQ information:\n"); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\t\tAssociated EQ-ID [%02d]:\n", + phba->sli4_hba.mbx_cq->assoc_qid); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], CQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n\n", + phba->sli4_hba.mbx_cq->queue_id, + phba->sli4_hba.mbx_cq->entry_count, + phba->sli4_hba.mbx_cq->host_index, + phba->sli4_hba.mbx_cq->hba_index); + + /* Get slow-path complete queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Slow-path CQ information:\n"); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\t\tAssociated EQ-ID [%02d]:\n", + phba->sli4_hba.els_cq->assoc_qid); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], CQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n\n", + phba->sli4_hba.els_cq->queue_id, + phba->sli4_hba.els_cq->entry_count, + phba->sli4_hba.els_cq->host_index, + phba->sli4_hba.els_cq->hba_index); + + /* Get fast-path complete queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Fast-path CQ information:\n"); + for (fcp_qidx = 0; fcp_qidx < phba->cfg_fcp_eq_count; fcp_qidx++) { + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\t\tAssociated EQ-ID [%02d]:\n", + phba->sli4_hba.fcp_cq[fcp_qidx]->assoc_qid); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], EQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n", + phba->sli4_hba.fcp_cq[fcp_qidx]->queue_id, + phba->sli4_hba.fcp_cq[fcp_qidx]->entry_count, + phba->sli4_hba.fcp_cq[fcp_qidx]->host_index, + phba->sli4_hba.fcp_cq[fcp_qidx]->hba_index); + } + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, "\n"); + + /* Get mailbox queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Mailbox MQ information:\n"); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\t\tAssociated CQ-ID [%02d]:\n", + phba->sli4_hba.mbx_wq->assoc_qid); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], MQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n\n", + phba->sli4_hba.mbx_wq->queue_id, + phba->sli4_hba.mbx_wq->entry_count, + phba->sli4_hba.mbx_wq->host_index, + phba->sli4_hba.mbx_wq->hba_index); + + /* Get slow-path work queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Slow-path WQ information:\n"); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\t\tAssociated CQ-ID [%02d]:\n", + phba->sli4_hba.els_wq->assoc_qid); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], WQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n\n", + phba->sli4_hba.els_wq->queue_id, + phba->sli4_hba.els_wq->entry_count, + phba->sli4_hba.els_wq->host_index, + phba->sli4_hba.els_wq->hba_index); + + /* Get fast-path work queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Fast-path WQ information:\n"); + for (fcp_qidx = 0; fcp_qidx < phba->cfg_fcp_wq_count; fcp_qidx++) { + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\t\tAssociated CQ-ID [%02d]:\n", + phba->sli4_hba.fcp_wq[fcp_qidx]->assoc_qid); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], WQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n", + phba->sli4_hba.fcp_wq[fcp_qidx]->queue_id, + phba->sli4_hba.fcp_wq[fcp_qidx]->entry_count, + phba->sli4_hba.fcp_wq[fcp_qidx]->host_index, + phba->sli4_hba.fcp_wq[fcp_qidx]->hba_index); + } + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, "\n"); + + /* Get receive queue information */ + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "Slow-path RQ information:\n"); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\t\tAssociated CQ-ID [%02d]:\n", + phba->sli4_hba.hdr_rq->assoc_qid); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], RHQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n", + phba->sli4_hba.hdr_rq->queue_id, + phba->sli4_hba.hdr_rq->entry_count, + phba->sli4_hba.hdr_rq->host_index, + phba->sli4_hba.hdr_rq->hba_index); + len += snprintf(pbuffer+len, LPFC_QUE_INFO_GET_BUF_SIZE-len, + "\tID [%02d], RDQE-COUNT [%04d], " + "HOST-INDEX [%04x], PORT-INDEX [%04x]\n", + phba->sli4_hba.dat_rq->queue_id, + phba->sli4_hba.dat_rq->entry_count, + phba->sli4_hba.dat_rq->host_index, + phba->sli4_hba.dat_rq->hba_index); + + return simple_read_from_buffer(buf, nbytes, ppos, pbuffer, len); +} + #undef lpfc_debugfs_op_disc_trc static const struct file_operations lpfc_debugfs_op_disc_trc = { .owner = THIS_MODULE, @@ -1213,6 +1964,28 @@ static const struct file_operations lpfc_debugfs_op_slow_ring_trc = { static struct dentry *lpfc_debugfs_root = NULL; static atomic_t lpfc_debugfs_hba_count; + +/* + * File operations for the iDiag debugfs + */ +#undef lpfc_idiag_op_pciCfg +static const struct file_operations lpfc_idiag_op_pciCfg = { + .owner = THIS_MODULE, + .open = lpfc_idiag_open, + .llseek = lpfc_debugfs_lseek, + .read = lpfc_idiag_pcicfg_read, + .write = lpfc_idiag_pcicfg_write, + .release = lpfc_idiag_cmd_release, +}; + +#undef lpfc_idiag_op_queInfo +static const struct file_operations lpfc_idiag_op_queInfo = { + .owner = THIS_MODULE, + .open = lpfc_idiag_open, + .read = lpfc_idiag_queinfo_read, + .release = lpfc_idiag_release, +}; + #endif /** @@ -1249,8 +2022,8 @@ lpfc_debugfs_initialize(struct lpfc_vport *vport) if (!lpfc_debugfs_start_time) lpfc_debugfs_start_time = jiffies; - /* Setup lpfcX directory for specific HBA */ - snprintf(name, sizeof(name), "lpfc%d", phba->brd_no); + /* Setup funcX directory for specific HBA PCI function */ + snprintf(name, sizeof(name), "fn%d", phba->brd_no); if (!phba->hba_debugfs_root) { phba->hba_debugfs_root = debugfs_create_dir(name, lpfc_debugfs_root); @@ -1275,28 +2048,38 @@ lpfc_debugfs_initialize(struct lpfc_vport *vport) } /* Setup dumpHBASlim */ - snprintf(name, sizeof(name), "dumpHBASlim"); - phba->debug_dumpHBASlim = - debugfs_create_file(name, S_IFREG|S_IRUGO|S_IWUSR, - phba->hba_debugfs_root, - phba, &lpfc_debugfs_op_dumpHBASlim); - if (!phba->debug_dumpHBASlim) { - lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, - "0413 Cannot create debugfs dumpHBASlim\n"); - goto debug_failed; - } + if (phba->sli_rev < LPFC_SLI_REV4) { + snprintf(name, sizeof(name), "dumpHBASlim"); + phba->debug_dumpHBASlim = + debugfs_create_file(name, + S_IFREG|S_IRUGO|S_IWUSR, + phba->hba_debugfs_root, + phba, &lpfc_debugfs_op_dumpHBASlim); + if (!phba->debug_dumpHBASlim) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, + "0413 Cannot create debugfs " + "dumpHBASlim\n"); + goto debug_failed; + } + } else + phba->debug_dumpHBASlim = NULL; /* Setup dumpHostSlim */ - snprintf(name, sizeof(name), "dumpHostSlim"); - phba->debug_dumpHostSlim = - debugfs_create_file(name, S_IFREG|S_IRUGO|S_IWUSR, - phba->hba_debugfs_root, - phba, &lpfc_debugfs_op_dumpHostSlim); - if (!phba->debug_dumpHostSlim) { - lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, - "0414 Cannot create debugfs dumpHostSlim\n"); - goto debug_failed; - } + if (phba->sli_rev < LPFC_SLI_REV4) { + snprintf(name, sizeof(name), "dumpHostSlim"); + phba->debug_dumpHostSlim = + debugfs_create_file(name, + S_IFREG|S_IRUGO|S_IWUSR, + phba->hba_debugfs_root, + phba, &lpfc_debugfs_op_dumpHostSlim); + if (!phba->debug_dumpHostSlim) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, + "0414 Cannot create debugfs " + "dumpHostSlim\n"); + goto debug_failed; + } + } else + phba->debug_dumpHBASlim = NULL; /* Setup dumpData */ snprintf(name, sizeof(name), "dumpData"); @@ -1322,8 +2105,6 @@ lpfc_debugfs_initialize(struct lpfc_vport *vport) goto debug_failed; } - - /* Setup slow ring trace */ if (lpfc_debugfs_max_slow_ring_trc) { num = lpfc_debugfs_max_slow_ring_trc - 1; @@ -1342,7 +2123,6 @@ lpfc_debugfs_initialize(struct lpfc_vport *vport) } } - snprintf(name, sizeof(name), "slow_ring_trace"); phba->debug_slow_ring_trc = debugfs_create_file(name, S_IFREG|S_IRUGO|S_IWUSR, @@ -1434,6 +2214,53 @@ lpfc_debugfs_initialize(struct lpfc_vport *vport) "0409 Cant create debugfs nodelist\n"); goto debug_failed; } + + /* + * iDiag debugfs root entry points for SLI4 device only + */ + if (phba->sli_rev < LPFC_SLI_REV4) + goto debug_failed; + + snprintf(name, sizeof(name), "iDiag"); + if (!phba->idiag_root) { + phba->idiag_root = + debugfs_create_dir(name, phba->hba_debugfs_root); + if (!phba->idiag_root) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, + "2922 Can't create idiag debugfs\n"); + goto debug_failed; + } + /* Initialize iDiag data structure */ + memset(&idiag, 0, sizeof(idiag)); + } + + /* iDiag read PCI config space */ + snprintf(name, sizeof(name), "pciCfg"); + if (!phba->idiag_pci_cfg) { + phba->idiag_pci_cfg = + debugfs_create_file(name, S_IFREG|S_IRUGO|S_IWUSR, + phba->idiag_root, phba, &lpfc_idiag_op_pciCfg); + if (!phba->idiag_pci_cfg) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, + "2923 Can't create idiag debugfs\n"); + goto debug_failed; + } + idiag.offset.last_rd = 0; + } + + /* iDiag get PCI function queue information */ + snprintf(name, sizeof(name), "queInfo"); + if (!phba->idiag_que_info) { + phba->idiag_que_info = + debugfs_create_file(name, S_IFREG|S_IRUGO, + phba->idiag_root, phba, &lpfc_idiag_op_queInfo); + if (!phba->idiag_que_info) { + lpfc_printf_vlog(vport, KERN_ERR, LOG_INIT, + "2924 Can't create idiag debugfs\n"); + goto debug_failed; + } + } + debug_failed: return; #endif @@ -1508,8 +2335,31 @@ lpfc_debugfs_terminate(struct lpfc_vport *vport) phba->debug_slow_ring_trc = NULL; } + /* + * iDiag release + */ + if (phba->sli_rev == LPFC_SLI_REV4) { + if (phba->idiag_que_info) { + /* iDiag queInfo */ + debugfs_remove(phba->idiag_que_info); + phba->idiag_que_info = NULL; + } + if (phba->idiag_pci_cfg) { + /* iDiag pciCfg */ + debugfs_remove(phba->idiag_pci_cfg); + phba->idiag_pci_cfg = NULL; + } + + /* Finally remove the iDiag debugfs root */ + if (phba->idiag_root) { + /* iDiag root */ + debugfs_remove(phba->idiag_root); + phba->idiag_root = NULL; + } + } + if (phba->hba_debugfs_root) { - debugfs_remove(phba->hba_debugfs_root); /* lpfcX */ + debugfs_remove(phba->hba_debugfs_root); /* fnX */ phba->hba_debugfs_root = NULL; atomic_dec(&lpfc_debugfs_hba_count); } diff --git a/drivers/scsi/lpfc/lpfc_debugfs.h b/drivers/scsi/lpfc/lpfc_debugfs.h index 03c7313a1012..91b9a9427cda 100644 --- a/drivers/scsi/lpfc/lpfc_debugfs.h +++ b/drivers/scsi/lpfc/lpfc_debugfs.h @@ -1,7 +1,7 @@ /******************************************************************* * This file is part of the Emulex Linux Device Driver for * * Fibre Channel Host Bus Adapters. * - * Copyright (C) 2007 Emulex. All rights reserved. * + * Copyright (C) 2007-2011 Emulex. All rights reserved. * * EMULEX and SLI are trademarks of Emulex. * * www.emulex.com * * * @@ -22,6 +22,44 @@ #define _H_LPFC_DEBUG_FS #ifdef CONFIG_SCSI_LPFC_DEBUG_FS + +/* size of output line, for discovery_trace and slow_ring_trace */ +#define LPFC_DEBUG_TRC_ENTRY_SIZE 100 + +/* nodelist output buffer size */ +#define LPFC_NODELIST_SIZE 8192 +#define LPFC_NODELIST_ENTRY_SIZE 120 + +/* dumpHBASlim output buffer size */ +#define LPFC_DUMPHBASLIM_SIZE 4096 + +/* dumpHostSlim output buffer size */ +#define LPFC_DUMPHOSTSLIM_SIZE 4096 + +/* hbqinfo output buffer size */ +#define LPFC_HBQINFO_SIZE 8192 + +/* rdPciConf output buffer size */ +#define LPFC_PCI_CFG_SIZE 4096 +#define LPFC_PCI_CFG_RD_BUF_SIZE (LPFC_PCI_CFG_SIZE/2) +#define LPFC_PCI_CFG_RD_SIZE (LPFC_PCI_CFG_SIZE/4) + +/* queue info output buffer size */ +#define LPFC_QUE_INFO_GET_BUF_SIZE 2048 + +#define SIZE_U8 sizeof(uint8_t) +#define SIZE_U16 sizeof(uint16_t) +#define SIZE_U32 sizeof(uint32_t) + +struct lpfc_debug { + char *i_private; + char op; +#define LPFC_IDIAG_OP_RD 1 +#define LPFC_IDIAG_OP_WR 2 + char *buffer; + int len; +}; + struct lpfc_debugfs_trc { char *fmt; uint32_t data1; @@ -30,6 +68,26 @@ struct lpfc_debugfs_trc { uint32_t seq_cnt; unsigned long jif; }; + +struct lpfc_idiag_offset { + uint32_t last_rd; +}; + +#define LPFC_IDIAG_CMD_DATA_SIZE 4 +struct lpfc_idiag_cmd { + uint32_t opcode; +#define LPFC_IDIAG_CMD_PCICFG_RD 0x00000001 +#define LPFC_IDIAG_CMD_PCICFG_WR 0x00000002 +#define LPFC_IDIAG_CMD_PCICFG_ST 0x00000003 +#define LPFC_IDIAG_CMD_PCICFG_CL 0x00000004 + uint32_t data[LPFC_IDIAG_CMD_DATA_SIZE]; +}; + +struct lpfc_idiag { + uint32_t active; + struct lpfc_idiag_cmd cmd; + struct lpfc_idiag_offset offset; +}; #endif /* Mask for discovery_trace */ diff --git a/drivers/scsi/lpfc/lpfc_els.c b/drivers/scsi/lpfc/lpfc_els.c index c62d567cc845..8e28edf9801e 100644 --- a/drivers/scsi/lpfc/lpfc_els.c +++ b/drivers/scsi/lpfc/lpfc_els.c @@ -1,7 +1,7 @@ /******************************************************************* * This file is part of the Emulex Linux Device Driver for * * Fibre Channel Host Bus Adapters. * - * Copyright (C) 2004-2009 Emulex. All rights reserved. * + * Copyright (C) 2004-2011 Emulex. All rights reserved. * * EMULEX and SLI are trademarks of Emulex. * * www.emulex.com * * Portions Copyright (C) 2004-2005 Christoph Hellwig * @@ -485,6 +485,59 @@ fail: } /** + * lpfc_check_clean_addr_bit - Check whether assigned FCID is clean. + * @vport: pointer to a host virtual N_Port data structure. + * @sp: pointer to service parameter data structure. + * + * This routine is called from FLOGI/FDISC completion handler functions. + * lpfc_check_clean_addr_bit return 1 when FCID/Fabric portname/ Fabric + * node nodename is changed in the completion service parameter else return + * 0. This function also set flag in the vport data structure to delay + * NP_Port discovery after the FLOGI/FDISC completion if Clean address bit + * in FLOGI/FDISC response is cleared and FCID/Fabric portname/ Fabric + * node nodename is changed in the completion service parameter. + * + * Return code + * 0 - FCID and Fabric Nodename and Fabric portname is not changed. + * 1 - FCID or Fabric Nodename or Fabric portname is changed. + * + **/ +static uint8_t +lpfc_check_clean_addr_bit(struct lpfc_vport *vport, + struct serv_parm *sp) +{ + uint8_t fabric_param_changed = 0; + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + + if ((vport->fc_prevDID != vport->fc_myDID) || + memcmp(&vport->fabric_portname, &sp->portName, + sizeof(struct lpfc_name)) || + memcmp(&vport->fabric_nodename, &sp->nodeName, + sizeof(struct lpfc_name))) + fabric_param_changed = 1; + + /* + * Word 1 Bit 31 in common service parameter is overloaded. + * Word 1 Bit 31 in FLOGI request is multiple NPort request + * Word 1 Bit 31 in FLOGI response is clean address bit + * + * If fabric parameter is changed and clean address bit is + * cleared delay nport discovery if + * - vport->fc_prevDID != 0 (not initial discovery) OR + * - lpfc_delay_discovery module parameter is set. + */ + if (fabric_param_changed && !sp->cmn.clean_address_bit && + (vport->fc_prevDID || lpfc_delay_discovery)) { + spin_lock_irq(shost->host_lock); + vport->fc_flag |= FC_DISC_DELAYED; + spin_unlock_irq(shost->host_lock); + } + + return fabric_param_changed; +} + + +/** * lpfc_cmpl_els_flogi_fabric - Completion function for flogi to a fabric port * @vport: pointer to a host virtual N_Port data structure. * @ndlp: pointer to a node-list data structure. @@ -512,6 +565,7 @@ lpfc_cmpl_els_flogi_fabric(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, struct lpfc_hba *phba = vport->phba; struct lpfc_nodelist *np; struct lpfc_nodelist *next_np; + uint8_t fabric_param_changed; spin_lock_irq(shost->host_lock); vport->fc_flag |= FC_FABRIC; @@ -544,6 +598,12 @@ lpfc_cmpl_els_flogi_fabric(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, ndlp->nlp_class_sup |= FC_COS_CLASS4; ndlp->nlp_maxframe = ((sp->cmn.bbRcvSizeMsb & 0x0F) << 8) | sp->cmn.bbRcvSizeLsb; + + fabric_param_changed = lpfc_check_clean_addr_bit(vport, sp); + memcpy(&vport->fabric_portname, &sp->portName, + sizeof(struct lpfc_name)); + memcpy(&vport->fabric_nodename, &sp->nodeName, + sizeof(struct lpfc_name)); memcpy(&phba->fc_fabparam, sp, sizeof(struct serv_parm)); if (phba->sli3_options & LPFC_SLI3_NPIV_ENABLED) { @@ -565,7 +625,7 @@ lpfc_cmpl_els_flogi_fabric(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, } } - if ((vport->fc_prevDID != vport->fc_myDID) && + if (fabric_param_changed && !(vport->fc_flag & FC_VPORT_NEEDS_REG_VPI)) { /* If our NportID changed, we need to ensure all @@ -2203,6 +2263,7 @@ lpfc_cmpl_els_logo(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, struct Scsi_Host *shost = lpfc_shost_from_vport(vport); IOCB_t *irsp; struct lpfc_sli *psli; + struct lpfcMboxq *mbox; psli = &phba->sli; /* we pass cmdiocb to state machine which needs rspiocb as well */ @@ -2260,6 +2321,21 @@ lpfc_cmpl_els_logo(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, NLP_EVT_CMPL_LOGO); out: lpfc_els_free_iocb(phba, cmdiocb); + /* If we are in pt2pt mode, we could rcv new S_ID on PLOGI */ + if ((vport->fc_flag & FC_PT2PT) && + !(vport->fc_flag & FC_PT2PT_PLOGI)) { + phba->pport->fc_myDID = 0; + mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); + if (mbox) { + lpfc_config_link(phba, mbox); + mbox->mbox_cmpl = lpfc_sli_def_mbox_cmpl; + mbox->vport = vport; + if (lpfc_sli_issue_mbox(phba, mbox, MBX_NOWAIT) == + MBX_NOT_FINISHED) { + mempool_free(mbox, phba->mbox_mem_pool); + } + } + } return; } @@ -2745,7 +2821,8 @@ lpfc_els_retry_delay_handler(struct lpfc_nodelist *ndlp) } break; case ELS_CMD_FDISC: - lpfc_issue_els_fdisc(vport, ndlp, retry); + if (!(vport->fc_flag & FC_VPORT_NEEDS_INIT_VPI)) + lpfc_issue_els_fdisc(vport, ndlp, retry); break; } return; @@ -2815,9 +2892,17 @@ lpfc_els_retry(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, switch (irsp->ulpStatus) { case IOSTAT_FCP_RSP_ERROR: + break; case IOSTAT_REMOTE_STOP: + if (phba->sli_rev == LPFC_SLI_REV4) { + /* This IO was aborted by the target, we don't + * know the rxid and because we did not send the + * ABTS we cannot generate and RRQ. + */ + lpfc_set_rrq_active(phba, ndlp, + cmdiocb->sli4_xritag, 0, 0); + } break; - case IOSTAT_LOCAL_REJECT: switch ((irsp->un.ulpWord[4] & 0xff)) { case IOERR_LOOP_OPEN_FAILURE: @@ -4013,28 +4098,34 @@ lpfc_els_clear_rrq(struct lpfc_vport *vport, uint8_t *pcmd; struct RRQ *rrq; uint16_t rxid; + uint16_t xri; struct lpfc_node_rrq *prrq; pcmd = (uint8_t *) (((struct lpfc_dmabuf *) iocb->context2)->virt); pcmd += sizeof(uint32_t); rrq = (struct RRQ *)pcmd; - rxid = bf_get(rrq_oxid, rrq); + rrq->rrq_exchg = be32_to_cpu(rrq->rrq_exchg); + rxid = be16_to_cpu(bf_get(rrq_rxid, rrq)); lpfc_printf_vlog(vport, KERN_INFO, LOG_ELS, "2883 Clear RRQ for SID:x%x OXID:x%x RXID:x%x" " x%x x%x\n", - bf_get(rrq_did, rrq), - bf_get(rrq_oxid, rrq), + be32_to_cpu(bf_get(rrq_did, rrq)), + be16_to_cpu(bf_get(rrq_oxid, rrq)), rxid, iocb->iotag, iocb->iocb.ulpContext); lpfc_debugfs_disc_trc(vport, LPFC_DISC_TRC_ELS_RSP, "Clear RRQ: did:x%x flg:x%x exchg:x%.08x", ndlp->nlp_DID, ndlp->nlp_flag, rrq->rrq_exchg); - prrq = lpfc_get_active_rrq(vport, rxid, ndlp->nlp_DID); + if (vport->fc_myDID == be32_to_cpu(bf_get(rrq_did, rrq))) + xri = be16_to_cpu(bf_get(rrq_oxid, rrq)); + else + xri = rxid; + prrq = lpfc_get_active_rrq(vport, xri, ndlp->nlp_DID); if (prrq) - lpfc_clr_rrq_active(phba, rxid, prrq); + lpfc_clr_rrq_active(phba, xri, prrq); return; } @@ -6166,6 +6257,11 @@ lpfc_els_unsol_buffer(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, if (vport->load_flag & FC_UNLOADING) goto dropit; + /* If NPort discovery is delayed drop incoming ELS */ + if ((vport->fc_flag & FC_DISC_DELAYED) && + (cmd != ELS_CMD_PLOGI)) + goto dropit; + ndlp = lpfc_findnode_did(vport, did); if (!ndlp) { /* Cannot find existing Fabric ndlp, so allocate a new one */ @@ -6218,6 +6314,12 @@ lpfc_els_unsol_buffer(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, ndlp = lpfc_plogi_confirm_nport(phba, payload, ndlp); lpfc_send_els_event(vport, ndlp, payload); + + /* If Nport discovery is delayed, reject PLOGIs */ + if (vport->fc_flag & FC_DISC_DELAYED) { + rjt_err = LSRJT_UNABLE_TPC; + break; + } if (vport->port_state < LPFC_DISC_AUTH) { if (!(phba->pport->fc_flag & FC_PT2PT) || (phba->pport->fc_flag & FC_PT2PT_PLOGI)) { @@ -6596,6 +6698,21 @@ void lpfc_do_scr_ns_plogi(struct lpfc_hba *phba, struct lpfc_vport *vport) { struct lpfc_nodelist *ndlp, *ndlp_fdmi; + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); + + /* + * If lpfc_delay_discovery parameter is set and the clean address + * bit is cleared and fc fabric parameters chenged, delay FC NPort + * discovery. + */ + spin_lock_irq(shost->host_lock); + if (vport->fc_flag & FC_DISC_DELAYED) { + spin_unlock_irq(shost->host_lock); + mod_timer(&vport->delayed_disc_tmo, + jiffies + HZ * phba->fc_ratov); + return; + } + spin_unlock_irq(shost->host_lock); ndlp = lpfc_findnode_did(vport, NameServer_DID); if (!ndlp) { @@ -6938,6 +7055,9 @@ lpfc_cmpl_els_fdisc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, struct lpfc_nodelist *next_np; IOCB_t *irsp = &rspiocb->iocb; struct lpfc_iocbq *piocb; + struct lpfc_dmabuf *pcmd = cmdiocb->context2, *prsp; + struct serv_parm *sp; + uint8_t fabric_param_changed; lpfc_printf_vlog(vport, KERN_INFO, LOG_ELS, "0123 FDISC completes. x%x/x%x prevDID: x%x\n", @@ -6981,7 +7101,14 @@ lpfc_cmpl_els_fdisc(struct lpfc_hba *phba, struct lpfc_iocbq *cmdiocb, vport->fc_myDID = irsp->un.ulpWord[4] & Mask_DID; lpfc_vport_set_state(vport, FC_VPORT_ACTIVE); - if ((vport->fc_prevDID != vport->fc_myDID) && + prsp = list_get_first(&pcmd->list, struct lpfc_dmabuf, list); + sp = prsp->virt + sizeof(uint32_t); + fabric_param_changed = lpfc_check_clean_addr_bit(vport, sp); + memcpy(&vport->fabric_portname, &sp->portName, + sizeof(struct lpfc_name)); + memcpy(&vport->fabric_nodename, &sp->nodeName, + sizeof(struct lpfc_name)); + if (fabric_param_changed && !(vport->fc_flag & FC_VPORT_NEEDS_REG_VPI)) { /* If our NportID changed, we need to ensure all * remaining NPORTs get unreg_login'ed so we can @@ -7582,6 +7709,32 @@ void lpfc_fabric_abort_hba(struct lpfc_hba *phba) } /** + * lpfc_sli4_vport_delete_els_xri_aborted -Remove all ndlp references for vport + * @vport: pointer to lpfc vport data structure. + * + * This routine is invoked by the vport cleanup for deletions and the cleanup + * for an ndlp on removal. + **/ +void +lpfc_sli4_vport_delete_els_xri_aborted(struct lpfc_vport *vport) +{ + struct lpfc_hba *phba = vport->phba; + struct lpfc_sglq *sglq_entry = NULL, *sglq_next = NULL; + unsigned long iflag = 0; + + spin_lock_irqsave(&phba->hbalock, iflag); + spin_lock(&phba->sli4_hba.abts_sgl_list_lock); + list_for_each_entry_safe(sglq_entry, sglq_next, + &phba->sli4_hba.lpfc_abts_els_sgl_list, list) { + if (sglq_entry->ndlp && sglq_entry->ndlp->vport == vport) + sglq_entry->ndlp = NULL; + } + spin_unlock(&phba->sli4_hba.abts_sgl_list_lock); + spin_unlock_irqrestore(&phba->hbalock, iflag); + return; +} + +/** * lpfc_sli4_els_xri_aborted - Slow-path process of els xri abort * @phba: pointer to lpfc hba data structure. * @axri: pointer to the els xri abort wcqe structure. diff --git a/drivers/scsi/lpfc/lpfc_hbadisc.c b/drivers/scsi/lpfc/lpfc_hbadisc.c index bb015960dbc9..154c715fb3af 100644 --- a/drivers/scsi/lpfc/lpfc_hbadisc.c +++ b/drivers/scsi/lpfc/lpfc_hbadisc.c @@ -658,6 +658,8 @@ lpfc_work_done(struct lpfc_hba *phba) lpfc_ramp_down_queue_handler(phba); if (work_port_events & WORKER_RAMP_UP_QUEUE) lpfc_ramp_up_queue_handler(phba); + if (work_port_events & WORKER_DELAYED_DISC_TMO) + lpfc_delayed_disc_timeout_handler(vport); } lpfc_destroy_vport_work_array(phba, vports); @@ -838,6 +840,11 @@ lpfc_linkdown_port(struct lpfc_vport *vport) lpfc_port_link_failure(vport); + /* Stop delayed Nport discovery */ + spin_lock_irq(shost->host_lock); + vport->fc_flag &= ~FC_DISC_DELAYED; + spin_unlock_irq(shost->host_lock); + del_timer_sync(&vport->delayed_disc_tmo); } int @@ -3160,7 +3167,7 @@ lpfc_mbx_cmpl_unreg_vpi(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmb) spin_unlock_irq(shost->host_lock); vport->unreg_vpi_cmpl = VPORT_OK; mempool_free(pmb, phba->mbox_mem_pool); - lpfc_cleanup_vports_rrqs(vport); + lpfc_cleanup_vports_rrqs(vport, NULL); /* * This shost reference might have been taken at the beginning of * lpfc_vport_delete() @@ -3900,6 +3907,8 @@ lpfc_drop_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) if (ndlp->nlp_state == NLP_STE_UNUSED_NODE) return; lpfc_nlp_set_state(vport, ndlp, NLP_STE_UNUSED_NODE); + if (vport->phba->sli_rev == LPFC_SLI_REV4) + lpfc_cleanup_vports_rrqs(vport, ndlp); lpfc_nlp_put(ndlp); return; } @@ -4289,7 +4298,7 @@ lpfc_cleanup_node(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) list_del_init(&ndlp->els_retry_evt.evt_listp); list_del_init(&ndlp->dev_loss_evt.evt_listp); - + lpfc_cleanup_vports_rrqs(vport, ndlp); lpfc_unreg_rpi(vport, ndlp); return 0; @@ -4426,10 +4435,11 @@ lpfc_findnode_did(struct lpfc_vport *vport, uint32_t did) { struct Scsi_Host *shost = lpfc_shost_from_vport(vport); struct lpfc_nodelist *ndlp; + unsigned long iflags; - spin_lock_irq(shost->host_lock); + spin_lock_irqsave(shost->host_lock, iflags); ndlp = __lpfc_findnode_did(vport, did); - spin_unlock_irq(shost->host_lock); + spin_unlock_irqrestore(shost->host_lock, iflags); return ndlp; } diff --git a/drivers/scsi/lpfc/lpfc_hw.h b/drivers/scsi/lpfc/lpfc_hw.h index 96ed3ba6ba95..94ae37c5111a 100644 --- a/drivers/scsi/lpfc/lpfc_hw.h +++ b/drivers/scsi/lpfc/lpfc_hw.h @@ -341,6 +341,12 @@ struct csp { uint8_t bbCreditMsb; uint8_t bbCreditlsb; /* FC Word 0, byte 3 */ +/* + * Word 1 Bit 31 in common service parameter is overloaded. + * Word 1 Bit 31 in FLOGI request is multiple NPort request + * Word 1 Bit 31 in FLOGI response is clean address bit + */ +#define clean_address_bit request_multiple_Nport /* Word 1, bit 31 */ #ifdef __BIG_ENDIAN_BITFIELD uint16_t request_multiple_Nport:1; /* FC Word 1, bit 31 */ uint16_t randomOffset:1; /* FC Word 1, bit 30 */ @@ -3198,7 +3204,10 @@ typedef struct { #define IOERR_SLER_RRQ_RJT_ERR 0x4C #define IOERR_SLER_RRQ_RETRY_ERR 0x4D #define IOERR_SLER_ABTS_ERR 0x4E - +#define IOERR_ELXSEC_KEY_UNWRAP_ERROR 0xF0 +#define IOERR_ELXSEC_KEY_UNWRAP_COMPARE_ERROR 0xF1 +#define IOERR_ELXSEC_CRYPTO_ERROR 0xF2 +#define IOERR_ELXSEC_CRYPTO_COMPARE_ERROR 0xF3 #define IOERR_DRVR_MASK 0x100 #define IOERR_SLI_DOWN 0x101 /* ulpStatus - Driver defined */ #define IOERR_SLI_BRESET 0x102 diff --git a/drivers/scsi/lpfc/lpfc_hw4.h b/drivers/scsi/lpfc/lpfc_hw4.h index 94c1aa1136de..c7178d60c7bf 100644 --- a/drivers/scsi/lpfc/lpfc_hw4.h +++ b/drivers/scsi/lpfc/lpfc_hw4.h @@ -778,6 +778,7 @@ struct mbox_header { #define LPFC_MBOX_OPCODE_QUERY_FW_CFG 0x3A #define LPFC_MBOX_OPCODE_FUNCTION_RESET 0x3D #define LPFC_MBOX_OPCODE_MQ_CREATE_EXT 0x5A +#define LPFC_MBOX_OPCODE_GET_SLI4_PARAMETERS 0xB5 /* FCoE Opcodes */ #define LPFC_MBOX_OPCODE_FCOE_WQ_CREATE 0x01 @@ -1852,6 +1853,9 @@ struct lpfc_mbx_request_features { #define lpfc_mbx_rq_ftr_rq_ifip_SHIFT 7 #define lpfc_mbx_rq_ftr_rq_ifip_MASK 0x00000001 #define lpfc_mbx_rq_ftr_rq_ifip_WORD word2 +#define lpfc_mbx_rq_ftr_rq_perfh_SHIFT 11 +#define lpfc_mbx_rq_ftr_rq_perfh_MASK 0x00000001 +#define lpfc_mbx_rq_ftr_rq_perfh_WORD word2 uint32_t word3; #define lpfc_mbx_rq_ftr_rsp_iaab_SHIFT 0 #define lpfc_mbx_rq_ftr_rsp_iaab_MASK 0x00000001 @@ -1877,6 +1881,9 @@ struct lpfc_mbx_request_features { #define lpfc_mbx_rq_ftr_rsp_ifip_SHIFT 7 #define lpfc_mbx_rq_ftr_rsp_ifip_MASK 0x00000001 #define lpfc_mbx_rq_ftr_rsp_ifip_WORD word3 +#define lpfc_mbx_rq_ftr_rsp_perfh_SHIFT 11 +#define lpfc_mbx_rq_ftr_rsp_perfh_MASK 0x00000001 +#define lpfc_mbx_rq_ftr_rsp_perfh_WORD word3 }; struct lpfc_mbx_supp_pages { @@ -1935,7 +1942,7 @@ struct lpfc_mbx_supp_pages { #define LPFC_SLI4_PARAMETERS 2 }; -struct lpfc_mbx_sli4_params { +struct lpfc_mbx_pc_sli4_params { uint32_t word1; #define qs_SHIFT 0 #define qs_MASK 0x00000001 @@ -2051,6 +2058,88 @@ struct lpfc_mbx_sli4_params { uint32_t rsvd_13_63[51]; }; +struct lpfc_sli4_parameters { + uint32_t word0; +#define cfg_prot_type_SHIFT 0 +#define cfg_prot_type_MASK 0x000000FF +#define cfg_prot_type_WORD word0 + uint32_t word1; +#define cfg_ft_SHIFT 0 +#define cfg_ft_MASK 0x00000001 +#define cfg_ft_WORD word1 +#define cfg_sli_rev_SHIFT 4 +#define cfg_sli_rev_MASK 0x0000000f +#define cfg_sli_rev_WORD word1 +#define cfg_sli_family_SHIFT 8 +#define cfg_sli_family_MASK 0x0000000f +#define cfg_sli_family_WORD word1 +#define cfg_if_type_SHIFT 12 +#define cfg_if_type_MASK 0x0000000f +#define cfg_if_type_WORD word1 +#define cfg_sli_hint_1_SHIFT 16 +#define cfg_sli_hint_1_MASK 0x000000ff +#define cfg_sli_hint_1_WORD word1 +#define cfg_sli_hint_2_SHIFT 24 +#define cfg_sli_hint_2_MASK 0x0000001f +#define cfg_sli_hint_2_WORD word1 + uint32_t word2; + uint32_t word3; + uint32_t word4; +#define cfg_cqv_SHIFT 14 +#define cfg_cqv_MASK 0x00000003 +#define cfg_cqv_WORD word4 + uint32_t word5; + uint32_t word6; +#define cfg_mqv_SHIFT 14 +#define cfg_mqv_MASK 0x00000003 +#define cfg_mqv_WORD word6 + uint32_t word7; + uint32_t word8; +#define cfg_wqv_SHIFT 14 +#define cfg_wqv_MASK 0x00000003 +#define cfg_wqv_WORD word8 + uint32_t word9; + uint32_t word10; +#define cfg_rqv_SHIFT 14 +#define cfg_rqv_MASK 0x00000003 +#define cfg_rqv_WORD word10 + uint32_t word11; +#define cfg_rq_db_window_SHIFT 28 +#define cfg_rq_db_window_MASK 0x0000000f +#define cfg_rq_db_window_WORD word11 + uint32_t word12; +#define cfg_fcoe_SHIFT 0 +#define cfg_fcoe_MASK 0x00000001 +#define cfg_fcoe_WORD word12 +#define cfg_phwq_SHIFT 15 +#define cfg_phwq_MASK 0x00000001 +#define cfg_phwq_WORD word12 +#define cfg_loopbk_scope_SHIFT 28 +#define cfg_loopbk_scope_MASK 0x0000000f +#define cfg_loopbk_scope_WORD word12 + uint32_t sge_supp_len; + uint32_t word14; +#define cfg_sgl_page_cnt_SHIFT 0 +#define cfg_sgl_page_cnt_MASK 0x0000000f +#define cfg_sgl_page_cnt_WORD word14 +#define cfg_sgl_page_size_SHIFT 8 +#define cfg_sgl_page_size_MASK 0x000000ff +#define cfg_sgl_page_size_WORD word14 +#define cfg_sgl_pp_align_SHIFT 16 +#define cfg_sgl_pp_align_MASK 0x000000ff +#define cfg_sgl_pp_align_WORD word14 + uint32_t word15; + uint32_t word16; + uint32_t word17; + uint32_t word18; + uint32_t word19; +}; + +struct lpfc_mbx_get_sli4_parameters { + struct mbox_header header; + struct lpfc_sli4_parameters sli4_parameters; +}; + /* Mailbox Completion Queue Error Messages */ #define MB_CQE_STATUS_SUCCESS 0x0 #define MB_CQE_STATUS_INSUFFICIENT_PRIVILEGES 0x1 @@ -2103,7 +2192,8 @@ struct lpfc_mqe { struct lpfc_mbx_post_hdr_tmpl hdr_tmpl; struct lpfc_mbx_query_fw_cfg query_fw_cfg; struct lpfc_mbx_supp_pages supp_pages; - struct lpfc_mbx_sli4_params sli4_params; + struct lpfc_mbx_pc_sli4_params sli4_params; + struct lpfc_mbx_get_sli4_parameters get_sli4_parameters; struct lpfc_mbx_nop nop; } un; }; @@ -2381,6 +2471,10 @@ struct wqe_common { #define wqe_wqes_SHIFT 15 #define wqe_wqes_MASK 0x00000001 #define wqe_wqes_WORD word10 +/* Note that this field overlaps above fields */ +#define wqe_wqid_SHIFT 1 +#define wqe_wqid_MASK 0x0000007f +#define wqe_wqid_WORD word10 #define wqe_pri_SHIFT 16 #define wqe_pri_MASK 0x00000007 #define wqe_pri_WORD word10 @@ -2599,7 +2693,8 @@ struct fcp_iwrite64_wqe { uint32_t total_xfer_len; uint32_t initial_xfer_len; struct wqe_common wqe_com; /* words 6-11 */ - uint32_t rsvd_12_15[4]; /* word 12-15 */ + uint32_t rsrvd12; + struct ulp_bde64 ph_bde; /* words 13-15 */ }; struct fcp_iread64_wqe { @@ -2608,7 +2703,8 @@ struct fcp_iread64_wqe { uint32_t total_xfer_len; /* word 4 */ uint32_t rsrvd5; /* word 5 */ struct wqe_common wqe_com; /* words 6-11 */ - uint32_t rsvd_12_15[4]; /* word 12-15 */ + uint32_t rsrvd12; + struct ulp_bde64 ph_bde; /* words 13-15 */ }; struct fcp_icmnd64_wqe { diff --git a/drivers/scsi/lpfc/lpfc_init.c b/drivers/scsi/lpfc/lpfc_init.c index 6d0b36aa3389..35665cfb5689 100644 --- a/drivers/scsi/lpfc/lpfc_init.c +++ b/drivers/scsi/lpfc/lpfc_init.c @@ -460,7 +460,7 @@ lpfc_config_port_post(struct lpfc_hba *phba) || ((phba->cfg_link_speed == LPFC_USER_LINK_SPEED_16G) && !(phba->lmt & LMT_16Gb))) { /* Reset link speed to auto */ - lpfc_printf_log(phba, KERN_WARNING, LOG_LINK_EVENT, + lpfc_printf_log(phba, KERN_ERR, LOG_LINK_EVENT, "1302 Invalid speed for this board: " "Reset link speed to auto: x%x\n", phba->cfg_link_speed); @@ -945,17 +945,13 @@ static void lpfc_rrq_timeout(unsigned long ptr) { struct lpfc_hba *phba; - uint32_t tmo_posted; unsigned long iflag; phba = (struct lpfc_hba *)ptr; spin_lock_irqsave(&phba->pport->work_port_lock, iflag); - tmo_posted = phba->hba_flag & HBA_RRQ_ACTIVE; - if (!tmo_posted) - phba->hba_flag |= HBA_RRQ_ACTIVE; + phba->hba_flag |= HBA_RRQ_ACTIVE; spin_unlock_irqrestore(&phba->pport->work_port_lock, iflag); - if (!tmo_posted) - lpfc_worker_wake_up(phba); + lpfc_worker_wake_up(phba); } /** @@ -2280,6 +2276,7 @@ lpfc_cleanup(struct lpfc_vport *vport) /* Wait for any activity on ndlps to settle */ msleep(10); } + lpfc_cleanup_vports_rrqs(vport, NULL); } /** @@ -2295,6 +2292,7 @@ lpfc_stop_vport_timers(struct lpfc_vport *vport) { del_timer_sync(&vport->els_tmofunc); del_timer_sync(&vport->fc_fdmitmo); + del_timer_sync(&vport->delayed_disc_tmo); lpfc_can_disctmo(vport); return; } @@ -2355,6 +2353,10 @@ lpfc_stop_hba_timers(struct lpfc_hba *phba) del_timer_sync(&phba->fabric_block_timer); del_timer_sync(&phba->eratt_poll); del_timer_sync(&phba->hb_tmofunc); + if (phba->sli_rev == LPFC_SLI_REV4) { + del_timer_sync(&phba->rrq_tmr); + phba->hba_flag &= ~HBA_RRQ_ACTIVE; + } phba->hb_outstanding = 0; switch (phba->pci_dev_grp) { @@ -2732,6 +2734,11 @@ lpfc_create_port(struct lpfc_hba *phba, int instance, struct device *dev) init_timer(&vport->els_tmofunc); vport->els_tmofunc.function = lpfc_els_timeout; vport->els_tmofunc.data = (unsigned long)vport; + + init_timer(&vport->delayed_disc_tmo); + vport->delayed_disc_tmo.function = lpfc_delayed_disc_tmo; + vport->delayed_disc_tmo.data = (unsigned long)vport; + error = scsi_add_host_with_dma(shost, dev, &phba->pcidev->dev); if (error) goto out_put_shost; @@ -4283,36 +4290,37 @@ lpfc_sli4_driver_resource_setup(struct lpfc_hba *phba) goto out_free_bsmbx; } - /* Get the Supported Pages. It is always available. */ + /* Get the Supported Pages if PORT_CAPABILITIES is supported by port. */ lpfc_supported_pages(mboxq); rc = lpfc_sli_issue_mbox(phba, mboxq, MBX_POLL); - if (unlikely(rc)) { - rc = -EIO; - mempool_free(mboxq, phba->mbox_mem_pool); - goto out_free_bsmbx; - } - - mqe = &mboxq->u.mqe; - memcpy(&pn_page[0], ((uint8_t *)&mqe->un.supp_pages.word3), - LPFC_MAX_SUPPORTED_PAGES); - for (i = 0; i < LPFC_MAX_SUPPORTED_PAGES; i++) { - switch (pn_page[i]) { - case LPFC_SLI4_PARAMETERS: - phba->sli4_hba.pc_sli4_params.supported = 1; - break; - default: - break; + if (!rc) { + mqe = &mboxq->u.mqe; + memcpy(&pn_page[0], ((uint8_t *)&mqe->un.supp_pages.word3), + LPFC_MAX_SUPPORTED_PAGES); + for (i = 0; i < LPFC_MAX_SUPPORTED_PAGES; i++) { + switch (pn_page[i]) { + case LPFC_SLI4_PARAMETERS: + phba->sli4_hba.pc_sli4_params.supported = 1; + break; + default: + break; + } + } + /* Read the port's SLI4 Parameters capabilities if supported. */ + if (phba->sli4_hba.pc_sli4_params.supported) + rc = lpfc_pc_sli4_params_get(phba, mboxq); + if (rc) { + mempool_free(mboxq, phba->mbox_mem_pool); + rc = -EIO; + goto out_free_bsmbx; } } - - /* Read the port's SLI4 Parameters capabilities if supported. */ - if (phba->sli4_hba.pc_sli4_params.supported) - rc = lpfc_pc_sli4_params_get(phba, mboxq); + /* + * Get sli4 parameters that override parameters from Port capabilities. + * If this call fails it is not a critical error so continue loading. + */ + lpfc_get_sli4_parameters(phba, mboxq); mempool_free(mboxq, phba->mbox_mem_pool); - if (rc) { - rc = -EIO; - goto out_free_bsmbx; - } /* Create all the SLI4 queues */ rc = lpfc_sli4_queue_create(phba); if (rc) @@ -7810,7 +7818,7 @@ lpfc_pc_sli4_params_get(struct lpfc_hba *phba, LPFC_MBOXQ_t *mboxq) mqe = &mboxq->u.mqe; /* Read the port's SLI4 Parameters port capabilities */ - lpfc_sli4_params(mboxq); + lpfc_pc_sli4_params(mboxq); if (!phba->sli4_hba.intr_enable) rc = lpfc_sli_issue_mbox(phba, mboxq, MBX_POLL); else { @@ -7854,6 +7862,66 @@ lpfc_pc_sli4_params_get(struct lpfc_hba *phba, LPFC_MBOXQ_t *mboxq) } /** + * lpfc_get_sli4_parameters - Get the SLI4 Config PARAMETERS. + * @phba: Pointer to HBA context object. + * @mboxq: Pointer to the mailboxq memory for the mailbox command response. + * + * This function is called in the SLI4 code path to read the port's + * sli4 capabilities. + * + * This function may be be called from any context that can block-wait + * for the completion. The expectation is that this routine is called + * typically from probe_one or from the online routine. + **/ +int +lpfc_get_sli4_parameters(struct lpfc_hba *phba, LPFC_MBOXQ_t *mboxq) +{ + int rc; + struct lpfc_mqe *mqe = &mboxq->u.mqe; + struct lpfc_pc_sli4_params *sli4_params; + int length; + struct lpfc_sli4_parameters *mbx_sli4_parameters; + + /* Read the port's SLI4 Config Parameters */ + length = (sizeof(struct lpfc_mbx_get_sli4_parameters) - + sizeof(struct lpfc_sli4_cfg_mhdr)); + lpfc_sli4_config(phba, mboxq, LPFC_MBOX_SUBSYSTEM_COMMON, + LPFC_MBOX_OPCODE_GET_SLI4_PARAMETERS, + length, LPFC_SLI4_MBX_EMBED); + if (!phba->sli4_hba.intr_enable) + rc = lpfc_sli_issue_mbox(phba, mboxq, MBX_POLL); + else + rc = lpfc_sli_issue_mbox_wait(phba, mboxq, + lpfc_mbox_tmo_val(phba, MBX_SLI4_CONFIG)); + if (unlikely(rc)) + return rc; + sli4_params = &phba->sli4_hba.pc_sli4_params; + mbx_sli4_parameters = &mqe->un.get_sli4_parameters.sli4_parameters; + sli4_params->if_type = bf_get(cfg_if_type, mbx_sli4_parameters); + sli4_params->sli_rev = bf_get(cfg_sli_rev, mbx_sli4_parameters); + sli4_params->sli_family = bf_get(cfg_sli_family, mbx_sli4_parameters); + sli4_params->featurelevel_1 = bf_get(cfg_sli_hint_1, + mbx_sli4_parameters); + sli4_params->featurelevel_2 = bf_get(cfg_sli_hint_2, + mbx_sli4_parameters); + if (bf_get(cfg_phwq, mbx_sli4_parameters)) + phba->sli3_options |= LPFC_SLI4_PHWQ_ENABLED; + else + phba->sli3_options &= ~LPFC_SLI4_PHWQ_ENABLED; + sli4_params->sge_supp_len = mbx_sli4_parameters->sge_supp_len; + sli4_params->loopbk_scope = bf_get(loopbk_scope, mbx_sli4_parameters); + sli4_params->cqv = bf_get(cfg_cqv, mbx_sli4_parameters); + sli4_params->mqv = bf_get(cfg_mqv, mbx_sli4_parameters); + sli4_params->wqv = bf_get(cfg_wqv, mbx_sli4_parameters); + sli4_params->rqv = bf_get(cfg_rqv, mbx_sli4_parameters); + sli4_params->sgl_pages_max = bf_get(cfg_sgl_page_cnt, + mbx_sli4_parameters); + sli4_params->sgl_pp_align = bf_get(cfg_sgl_pp_align, + mbx_sli4_parameters); + return 0; +} + +/** * lpfc_pci_probe_one_s3 - PCI probe func to reg SLI-3 device to PCI subsystem. * @pdev: pointer to PCI device * @pid: pointer to PCI device identifier diff --git a/drivers/scsi/lpfc/lpfc_mbox.c b/drivers/scsi/lpfc/lpfc_mbox.c index 23403c650207..dba32dfdb59b 100644 --- a/drivers/scsi/lpfc/lpfc_mbox.c +++ b/drivers/scsi/lpfc/lpfc_mbox.c @@ -1263,7 +1263,8 @@ lpfc_config_port(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmb) if (phba->sli_rev == LPFC_SLI_REV3 && phba->vpd.sli3Feat.cerbm) { if (phba->cfg_enable_bg) mb->un.varCfgPort.cbg = 1; /* configure BlockGuard */ - mb->un.varCfgPort.cdss = 1; /* Configure Security */ + if (phba->cfg_enable_dss) + mb->un.varCfgPort.cdss = 1; /* Configure Security */ mb->un.varCfgPort.cerbm = 1; /* Request HBQs */ mb->un.varCfgPort.ccrp = 1; /* Command Ring Polling */ mb->un.varCfgPort.max_hbq = lpfc_sli_hbq_count(); @@ -1692,7 +1693,7 @@ lpfc_sli4_mbox_cmd_free(struct lpfc_hba *phba, struct lpfcMboxq *mbox) * @mbox: pointer to lpfc mbox command. * @subsystem: The sli4 config sub mailbox subsystem. * @opcode: The sli4 config sub mailbox command opcode. - * @length: Length of the sli4 config mailbox command. + * @length: Length of the sli4 config mailbox command (including sub-header). * * This routine sets up the header fields of SLI4 specific mailbox command * for sending IOCTL command. @@ -1723,14 +1724,14 @@ lpfc_sli4_config(struct lpfc_hba *phba, struct lpfcMboxq *mbox, if (emb) { /* Set up main header fields */ bf_set(lpfc_mbox_hdr_emb, &sli4_config->header.cfg_mhdr, 1); - sli4_config->header.cfg_mhdr.payload_length = - LPFC_MBX_CMD_HDR_LENGTH + length; + sli4_config->header.cfg_mhdr.payload_length = length; /* Set up sub-header fields following main header */ bf_set(lpfc_mbox_hdr_opcode, &sli4_config->header.cfg_shdr.request, opcode); bf_set(lpfc_mbox_hdr_subsystem, &sli4_config->header.cfg_shdr.request, subsystem); - sli4_config->header.cfg_shdr.request.request_length = length; + sli4_config->header.cfg_shdr.request.request_length = + length - LPFC_MBX_CMD_HDR_LENGTH; return length; } @@ -1902,6 +1903,7 @@ lpfc_request_features(struct lpfc_hba *phba, struct lpfcMboxq *mboxq) /* Set up host requested features. */ bf_set(lpfc_mbx_rq_ftr_rq_fcpi, &mboxq->u.mqe.un.req_ftrs, 1); + bf_set(lpfc_mbx_rq_ftr_rq_perfh, &mboxq->u.mqe.un.req_ftrs, 1); /* Enable DIF (block guard) only if configured to do so. */ if (phba->cfg_enable_bg) @@ -2159,17 +2161,16 @@ lpfc_supported_pages(struct lpfcMboxq *mbox) } /** - * lpfc_sli4_params - Initialize the PORT_CAPABILITIES SLI4 Params - * mailbox command. + * lpfc_pc_sli4_params - Initialize the PORT_CAPABILITIES SLI4 Params mbox cmd. * @mbox: pointer to lpfc mbox command to initialize. * * The PORT_CAPABILITIES SLI4 parameters mailbox command is issued to * retrieve the particular SLI4 features supported by the port. **/ void -lpfc_sli4_params(struct lpfcMboxq *mbox) +lpfc_pc_sli4_params(struct lpfcMboxq *mbox) { - struct lpfc_mbx_sli4_params *sli4_params; + struct lpfc_mbx_pc_sli4_params *sli4_params; memset(mbox, 0, sizeof(*mbox)); sli4_params = &mbox->u.mqe.un.sli4_params; diff --git a/drivers/scsi/lpfc/lpfc_nportdisc.c b/drivers/scsi/lpfc/lpfc_nportdisc.c index d85a7423a694..52b35159fc35 100644 --- a/drivers/scsi/lpfc/lpfc_nportdisc.c +++ b/drivers/scsi/lpfc/lpfc_nportdisc.c @@ -350,7 +350,11 @@ lpfc_rcv_plogi(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, ndlp->nlp_maxframe = ((sp->cmn.bbRcvSizeMsb & 0x0F) << 8) | sp->cmn.bbRcvSizeLsb; - /* no need to reg_login if we are already in one of these states */ + /* + * Need to unreg_login if we are already in one of these states and + * change to NPR state. This will block the port until after the ACC + * completes and the reg_login is issued and completed. + */ switch (ndlp->nlp_state) { case NLP_STE_NPR_NODE: if (!(ndlp->nlp_flag & NLP_NPR_ADISC)) @@ -359,8 +363,9 @@ lpfc_rcv_plogi(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, case NLP_STE_PRLI_ISSUE: case NLP_STE_UNMAPPED_NODE: case NLP_STE_MAPPED_NODE: - lpfc_els_rsp_acc(vport, ELS_CMD_PLOGI, cmdiocb, ndlp, NULL); - return 1; + lpfc_unreg_rpi(vport, ndlp); + ndlp->nlp_prev_state = ndlp->nlp_state; + lpfc_nlp_set_state(vport, ndlp, NLP_STE_NPR_NODE); } if ((vport->fc_flag & FC_PT2PT) && diff --git a/drivers/scsi/lpfc/lpfc_scsi.c b/drivers/scsi/lpfc/lpfc_scsi.c index c97751c95d77..bf34178b80bf 100644 --- a/drivers/scsi/lpfc/lpfc_scsi.c +++ b/drivers/scsi/lpfc/lpfc_scsi.c @@ -609,6 +609,32 @@ lpfc_new_scsi_buf_s3(struct lpfc_vport *vport, int num_to_alloc) } /** + * lpfc_sli4_vport_delete_fcp_xri_aborted -Remove all ndlp references for vport + * @vport: pointer to lpfc vport data structure. + * + * This routine is invoked by the vport cleanup for deletions and the cleanup + * for an ndlp on removal. + **/ +void +lpfc_sli4_vport_delete_fcp_xri_aborted(struct lpfc_vport *vport) +{ + struct lpfc_hba *phba = vport->phba; + struct lpfc_scsi_buf *psb, *next_psb; + unsigned long iflag = 0; + + spin_lock_irqsave(&phba->hbalock, iflag); + spin_lock(&phba->sli4_hba.abts_scsi_buf_list_lock); + list_for_each_entry_safe(psb, next_psb, + &phba->sli4_hba.lpfc_abts_scsi_buf_list, list) { + if (psb->rdata && psb->rdata->pnode + && psb->rdata->pnode->vport == vport) + psb->rdata = NULL; + } + spin_unlock(&phba->sli4_hba.abts_scsi_buf_list_lock); + spin_unlock_irqrestore(&phba->hbalock, iflag); +} + +/** * lpfc_sli4_fcp_xri_aborted - Fast-path process of fcp xri abort * @phba: pointer to lpfc hba data structure. * @axri: pointer to the fcp xri abort wcqe structure. @@ -640,7 +666,11 @@ lpfc_sli4_fcp_xri_aborted(struct lpfc_hba *phba, psb->status = IOSTAT_SUCCESS; spin_unlock( &phba->sli4_hba.abts_scsi_buf_list_lock); - ndlp = psb->rdata->pnode; + if (psb->rdata && psb->rdata->pnode) + ndlp = psb->rdata->pnode; + else + ndlp = NULL; + rrq_empty = list_empty(&phba->active_rrq_list); spin_unlock_irqrestore(&phba->hbalock, iflag); if (ndlp) @@ -964,36 +994,29 @@ lpfc_get_scsi_buf_s3(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp) static struct lpfc_scsi_buf* lpfc_get_scsi_buf_s4(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp) { - struct lpfc_scsi_buf *lpfc_cmd = NULL; - struct lpfc_scsi_buf *start_lpfc_cmd = NULL; - struct list_head *scsi_buf_list = &phba->lpfc_scsi_buf_list; + struct lpfc_scsi_buf *lpfc_cmd ; unsigned long iflag = 0; int found = 0; spin_lock_irqsave(&phba->scsi_buf_list_lock, iflag); - list_remove_head(scsi_buf_list, lpfc_cmd, struct lpfc_scsi_buf, list); - spin_unlock_irqrestore(&phba->scsi_buf_list_lock, iflag); - while (!found && lpfc_cmd) { + list_for_each_entry(lpfc_cmd, &phba->lpfc_scsi_buf_list, + list) { if (lpfc_test_rrq_active(phba, ndlp, - lpfc_cmd->cur_iocbq.sli4_xritag)) { - lpfc_release_scsi_buf_s4(phba, lpfc_cmd); - spin_lock_irqsave(&phba->scsi_buf_list_lock, iflag); - list_remove_head(scsi_buf_list, lpfc_cmd, - struct lpfc_scsi_buf, list); - spin_unlock_irqrestore(&phba->scsi_buf_list_lock, - iflag); - if (lpfc_cmd == start_lpfc_cmd) { - lpfc_cmd = NULL; - break; - } else - continue; - } + lpfc_cmd->cur_iocbq.sli4_xritag)) + continue; + list_del(&lpfc_cmd->list); found = 1; lpfc_cmd->seg_cnt = 0; lpfc_cmd->nonsg_phys = 0; lpfc_cmd->prot_seg_cnt = 0; + break; } - return lpfc_cmd; + spin_unlock_irqrestore(&phba->scsi_buf_list_lock, + iflag); + if (!found) + return NULL; + else + return lpfc_cmd; } /** * lpfc_get_scsi_buf - Get a scsi buffer from lpfc_scsi_buf_list of the HBA @@ -1981,12 +2004,14 @@ lpfc_scsi_prep_dma_buf_s4(struct lpfc_hba *phba, struct lpfc_scsi_buf *lpfc_cmd) struct scatterlist *sgel = NULL; struct fcp_cmnd *fcp_cmnd = lpfc_cmd->fcp_cmnd; struct sli4_sge *sgl = (struct sli4_sge *)lpfc_cmd->fcp_bpl; + struct sli4_sge *first_data_sgl; IOCB_t *iocb_cmd = &lpfc_cmd->cur_iocbq.iocb; dma_addr_t physaddr; uint32_t num_bde = 0; uint32_t dma_len; uint32_t dma_offset = 0; int nseg; + struct ulp_bde64 *bde; /* * There are three possibilities here - use scatter-gather segment, use @@ -2011,7 +2036,7 @@ lpfc_scsi_prep_dma_buf_s4(struct lpfc_hba *phba, struct lpfc_scsi_buf *lpfc_cmd) bf_set(lpfc_sli4_sge_last, sgl, 0); sgl->word2 = cpu_to_le32(sgl->word2); sgl += 1; - + first_data_sgl = sgl; lpfc_cmd->seg_cnt = nseg; if (lpfc_cmd->seg_cnt > phba->cfg_sg_seg_cnt) { lpfc_printf_log(phba, KERN_ERR, LOG_BG, "9074 BLKGRD:" @@ -2047,6 +2072,17 @@ lpfc_scsi_prep_dma_buf_s4(struct lpfc_hba *phba, struct lpfc_scsi_buf *lpfc_cmd) dma_offset += dma_len; sgl++; } + /* setup the performance hint (first data BDE) if enabled */ + if (phba->sli3_options & LPFC_SLI4_PERFH_ENABLED) { + bde = (struct ulp_bde64 *) + &(iocb_cmd->unsli3.sli3Words[5]); + bde->addrLow = first_data_sgl->addr_lo; + bde->addrHigh = first_data_sgl->addr_hi; + bde->tus.f.bdeSize = + le32_to_cpu(first_data_sgl->sge_len); + bde->tus.f.bdeFlags = BUFF_TYPE_BDE_64; + bde->tus.w = cpu_to_le32(bde->tus.w); + } } else { sgl += 1; /* clear the last flag in the fcp_rsp map entry */ @@ -2471,6 +2507,16 @@ lpfc_scsi_cmd_iocb_cmpl(struct lpfc_hba *phba, struct lpfc_iocbq *pIocbIn, lpfc_worker_wake_up(phba); break; case IOSTAT_LOCAL_REJECT: + case IOSTAT_REMOTE_STOP: + if (lpfc_cmd->result == IOERR_ELXSEC_KEY_UNWRAP_ERROR || + lpfc_cmd->result == + IOERR_ELXSEC_KEY_UNWRAP_COMPARE_ERROR || + lpfc_cmd->result == IOERR_ELXSEC_CRYPTO_ERROR || + lpfc_cmd->result == + IOERR_ELXSEC_CRYPTO_COMPARE_ERROR) { + cmd->result = ScsiResult(DID_NO_CONNECT, 0); + break; + } if (lpfc_cmd->result == IOERR_INVALID_RPI || lpfc_cmd->result == IOERR_NO_RESOURCES || lpfc_cmd->result == IOERR_ABORT_REQUESTED || @@ -2478,7 +2524,6 @@ lpfc_scsi_cmd_iocb_cmpl(struct lpfc_hba *phba, struct lpfc_iocbq *pIocbIn, cmd->result = ScsiResult(DID_REQUEUE, 0); break; } - if ((lpfc_cmd->result == IOERR_RX_DMA_FAILED || lpfc_cmd->result == IOERR_TX_DMA_FAILED) && pIocbOut->iocb.unsli3.sli3_bg.bgstat) { @@ -2497,7 +2542,17 @@ lpfc_scsi_cmd_iocb_cmpl(struct lpfc_hba *phba, struct lpfc_iocbq *pIocbIn, "on unprotected cmd\n"); } } - + if ((lpfc_cmd->status == IOSTAT_REMOTE_STOP) + && (phba->sli_rev == LPFC_SLI_REV4) + && (pnode && NLP_CHK_NODE_ACT(pnode))) { + /* This IO was aborted by the target, we don't + * know the rxid and because we did not send the + * ABTS we cannot generate and RRQ. + */ + lpfc_set_rrq_active(phba, pnode, + lpfc_cmd->cur_iocbq.sli4_xritag, + 0, 0); + } /* else: fall through */ default: cmd->result = ScsiResult(DID_ERROR, 0); @@ -2508,9 +2563,8 @@ lpfc_scsi_cmd_iocb_cmpl(struct lpfc_hba *phba, struct lpfc_iocbq *pIocbIn, || (pnode->nlp_state != NLP_STE_MAPPED_NODE)) cmd->result = ScsiResult(DID_TRANSPORT_DISRUPTED, SAM_STAT_BUSY); - } else { + } else cmd->result = ScsiResult(DID_OK, 0); - } if (cmd->result || lpfc_cmd->fcp_rsp->rspSnsLen) { uint32_t *lp = (uint32_t *)cmd->sense_buffer; @@ -3004,11 +3058,11 @@ lpfc_queuecommand_lck(struct scsi_cmnd *cmnd, void (*done) (struct scsi_cmnd *)) * transport is still transitioning. */ if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) { - cmnd->result = ScsiResult(DID_TRANSPORT_DISRUPTED, 0); + cmnd->result = ScsiResult(DID_IMM_RETRY, 0); goto out_fail_command; } if (atomic_read(&ndlp->cmd_pending) >= ndlp->cmd_qdepth) - goto out_host_busy; + goto out_tgt_busy; lpfc_cmd = lpfc_get_scsi_buf(phba, ndlp); if (lpfc_cmd == NULL) { @@ -3125,6 +3179,9 @@ lpfc_queuecommand_lck(struct scsi_cmnd *cmnd, void (*done) (struct scsi_cmnd *)) out_host_busy: return SCSI_MLQUEUE_HOST_BUSY; + out_tgt_busy: + return SCSI_MLQUEUE_TARGET_BUSY; + out_fail_command: done(cmnd); return 0; diff --git a/drivers/scsi/lpfc/lpfc_sli.c b/drivers/scsi/lpfc/lpfc_sli.c index a359d2b873ce..2ee0374a9908 100644 --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -96,7 +96,8 @@ lpfc_sli4_wq_put(struct lpfc_queue *q, union lpfc_wqe *wqe) /* set consumption flag every once in a while */ if (!((q->host_index + 1) % LPFC_RELEASE_NOTIFICATION_INTERVAL)) bf_set(wqe_wqec, &wqe->generic.wqe_com, 1); - + if (q->phba->sli3_options & LPFC_SLI4_PHWQ_ENABLED) + bf_set(wqe_wqid, &wqe->generic.wqe_com, q->queue_id); lpfc_sli_pcimem_bcopy(wqe, temp_wqe, q->entry_size); /* Update the host index before invoking device */ @@ -534,15 +535,35 @@ __lpfc_set_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, uint16_t adj_xri; struct lpfc_node_rrq *rrq; int empty; + uint32_t did = 0; + + + if (!ndlp) + return -EINVAL; + + if (!phba->cfg_enable_rrq) + return -EINVAL; + + if (phba->pport->load_flag & FC_UNLOADING) { + phba->hba_flag &= ~HBA_RRQ_ACTIVE; + goto out; + } + did = ndlp->nlp_DID; /* * set the active bit even if there is no mem available. */ adj_xri = xritag - phba->sli4_hba.max_cfg_param.xri_base; - if (!ndlp) - return -EINVAL; + + if (NLP_CHK_FREE_REQ(ndlp)) + goto out; + + if (ndlp->vport && (ndlp->vport->load_flag & FC_UNLOADING)) + goto out; + if (test_and_set_bit(adj_xri, ndlp->active_rrqs.xri_bitmap)) - return -EINVAL; + goto out; + rrq = mempool_alloc(phba->rrq_pool, GFP_KERNEL); if (rrq) { rrq->send_rrq = send_rrq; @@ -553,14 +574,7 @@ __lpfc_set_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, rrq->vport = ndlp->vport; rrq->rxid = rxid; empty = list_empty(&phba->active_rrq_list); - if (phba->cfg_enable_rrq && send_rrq) - /* - * We need the xri before we can add this to the - * phba active rrq list. - */ - rrq->send_rrq = send_rrq; - else - rrq->send_rrq = 0; + rrq->send_rrq = send_rrq; list_add_tail(&rrq->list, &phba->active_rrq_list); if (!(phba->hba_flag & HBA_RRQ_ACTIVE)) { phba->hba_flag |= HBA_RRQ_ACTIVE; @@ -569,40 +583,49 @@ __lpfc_set_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, } return 0; } - return -ENOMEM; +out: + lpfc_printf_log(phba, KERN_INFO, LOG_SLI, + "2921 Can't set rrq active xri:0x%x rxid:0x%x" + " DID:0x%x Send:%d\n", + xritag, rxid, did, send_rrq); + return -EINVAL; } /** - * __lpfc_clr_rrq_active - Clears RRQ active bit in xri_bitmap. + * lpfc_clr_rrq_active - Clears RRQ active bit in xri_bitmap. * @phba: Pointer to HBA context object. * @xritag: xri used in this exchange. * @rrq: The RRQ to be cleared. * - * This function is called with hbalock held. This function **/ -static void -__lpfc_clr_rrq_active(struct lpfc_hba *phba, - uint16_t xritag, - struct lpfc_node_rrq *rrq) +void +lpfc_clr_rrq_active(struct lpfc_hba *phba, + uint16_t xritag, + struct lpfc_node_rrq *rrq) { uint16_t adj_xri; - struct lpfc_nodelist *ndlp; + struct lpfc_nodelist *ndlp = NULL; - ndlp = lpfc_findnode_did(rrq->vport, rrq->nlp_DID); + if ((rrq->vport) && NLP_CHK_NODE_ACT(rrq->ndlp)) + ndlp = lpfc_findnode_did(rrq->vport, rrq->nlp_DID); /* The target DID could have been swapped (cable swap) * we should use the ndlp from the findnode if it is * available. */ - if (!ndlp) + if ((!ndlp) && rrq->ndlp) ndlp = rrq->ndlp; + if (!ndlp) + goto out; + adj_xri = xritag - phba->sli4_hba.max_cfg_param.xri_base; if (test_and_clear_bit(adj_xri, ndlp->active_rrqs.xri_bitmap)) { rrq->send_rrq = 0; rrq->xritag = 0; rrq->rrq_stop_time = 0; } +out: mempool_free(rrq, phba->rrq_pool); } @@ -627,34 +650,34 @@ lpfc_handle_rrq_active(struct lpfc_hba *phba) struct lpfc_node_rrq *nextrrq; unsigned long next_time; unsigned long iflags; + LIST_HEAD(send_rrq); spin_lock_irqsave(&phba->hbalock, iflags); phba->hba_flag &= ~HBA_RRQ_ACTIVE; next_time = jiffies + HZ * (phba->fc_ratov + 1); list_for_each_entry_safe(rrq, nextrrq, - &phba->active_rrq_list, list) { - if (time_after(jiffies, rrq->rrq_stop_time)) { - list_del(&rrq->list); - if (!rrq->send_rrq) - /* this call will free the rrq */ - __lpfc_clr_rrq_active(phba, rrq->xritag, rrq); - else { - /* if we send the rrq then the completion handler - * will clear the bit in the xribitmap. - */ - spin_unlock_irqrestore(&phba->hbalock, iflags); - if (lpfc_send_rrq(phba, rrq)) { - lpfc_clr_rrq_active(phba, rrq->xritag, - rrq); - } - spin_lock_irqsave(&phba->hbalock, iflags); - } - } else if (time_before(rrq->rrq_stop_time, next_time)) + &phba->active_rrq_list, list) { + if (time_after(jiffies, rrq->rrq_stop_time)) + list_move(&rrq->list, &send_rrq); + else if (time_before(rrq->rrq_stop_time, next_time)) next_time = rrq->rrq_stop_time; } spin_unlock_irqrestore(&phba->hbalock, iflags); if (!list_empty(&phba->active_rrq_list)) mod_timer(&phba->rrq_tmr, next_time); + list_for_each_entry_safe(rrq, nextrrq, &send_rrq, list) { + list_del(&rrq->list); + if (!rrq->send_rrq) + /* this call will free the rrq */ + lpfc_clr_rrq_active(phba, rrq->xritag, rrq); + else if (lpfc_send_rrq(phba, rrq)) { + /* if we send the rrq then the completion handler + * will clear the bit in the xribitmap. + */ + lpfc_clr_rrq_active(phba, rrq->xritag, + rrq); + } + } } /** @@ -692,29 +715,37 @@ lpfc_get_active_rrq(struct lpfc_vport *vport, uint16_t xri, uint32_t did) /** * lpfc_cleanup_vports_rrqs - Remove and clear the active RRQ for this vport. * @vport: Pointer to vport context object. - * - * Remove all active RRQs for this vport from the phba->active_rrq_list and - * clear the rrq. + * @ndlp: Pointer to the lpfc_node_list structure. + * If ndlp is NULL Remove all active RRQs for this vport from the + * phba->active_rrq_list and clear the rrq. + * If ndlp is not NULL then only remove rrqs for this vport & this ndlp. **/ void -lpfc_cleanup_vports_rrqs(struct lpfc_vport *vport) +lpfc_cleanup_vports_rrqs(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp) { struct lpfc_hba *phba = vport->phba; struct lpfc_node_rrq *rrq; struct lpfc_node_rrq *nextrrq; unsigned long iflags; + LIST_HEAD(rrq_list); if (phba->sli_rev != LPFC_SLI_REV4) return; - spin_lock_irqsave(&phba->hbalock, iflags); - list_for_each_entry_safe(rrq, nextrrq, &phba->active_rrq_list, list) { - if (rrq->vport == vport) { - list_del(&rrq->list); - __lpfc_clr_rrq_active(phba, rrq->xritag, rrq); - } + if (!ndlp) { + lpfc_sli4_vport_delete_els_xri_aborted(vport); + lpfc_sli4_vport_delete_fcp_xri_aborted(vport); } + spin_lock_irqsave(&phba->hbalock, iflags); + list_for_each_entry_safe(rrq, nextrrq, &phba->active_rrq_list, list) + if ((rrq->vport == vport) && (!ndlp || rrq->ndlp == ndlp)) + list_move(&rrq->list, &rrq_list); spin_unlock_irqrestore(&phba->hbalock, iflags); + + list_for_each_entry_safe(rrq, nextrrq, &rrq_list, list) { + list_del(&rrq->list); + lpfc_clr_rrq_active(phba, rrq->xritag, rrq); + } } /** @@ -732,24 +763,27 @@ lpfc_cleanup_wt_rrqs(struct lpfc_hba *phba) struct lpfc_node_rrq *nextrrq; unsigned long next_time; unsigned long iflags; + LIST_HEAD(rrq_list); if (phba->sli_rev != LPFC_SLI_REV4) return; spin_lock_irqsave(&phba->hbalock, iflags); phba->hba_flag &= ~HBA_RRQ_ACTIVE; next_time = jiffies + HZ * (phba->fc_ratov * 2); - list_for_each_entry_safe(rrq, nextrrq, &phba->active_rrq_list, list) { + list_splice_init(&phba->active_rrq_list, &rrq_list); + spin_unlock_irqrestore(&phba->hbalock, iflags); + + list_for_each_entry_safe(rrq, nextrrq, &rrq_list, list) { list_del(&rrq->list); - __lpfc_clr_rrq_active(phba, rrq->xritag, rrq); + lpfc_clr_rrq_active(phba, rrq->xritag, rrq); } - spin_unlock_irqrestore(&phba->hbalock, iflags); if (!list_empty(&phba->active_rrq_list)) mod_timer(&phba->rrq_tmr, next_time); } /** - * __lpfc_test_rrq_active - Test RRQ bit in xri_bitmap. + * lpfc_test_rrq_active - Test RRQ bit in xri_bitmap. * @phba: Pointer to HBA context object. * @ndlp: Targets nodelist pointer for this exchange. * @xritag the xri in the bitmap to test. @@ -758,8 +792,8 @@ lpfc_cleanup_wt_rrqs(struct lpfc_hba *phba) * returns 0 = rrq not active for this xri * 1 = rrq is valid for this xri. **/ -static int -__lpfc_test_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, +int +lpfc_test_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, uint16_t xritag) { uint16_t adj_xri; @@ -802,52 +836,6 @@ lpfc_set_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, } /** - * lpfc_clr_rrq_active - Clears RRQ active bit in xri_bitmap. - * @phba: Pointer to HBA context object. - * @xritag: xri used in this exchange. - * @rrq: The RRQ to be cleared. - * - * This function is takes the hbalock. - **/ -void -lpfc_clr_rrq_active(struct lpfc_hba *phba, - uint16_t xritag, - struct lpfc_node_rrq *rrq) -{ - unsigned long iflags; - - spin_lock_irqsave(&phba->hbalock, iflags); - __lpfc_clr_rrq_active(phba, xritag, rrq); - spin_unlock_irqrestore(&phba->hbalock, iflags); - return; -} - - - -/** - * lpfc_test_rrq_active - Test RRQ bit in xri_bitmap. - * @phba: Pointer to HBA context object. - * @ndlp: Targets nodelist pointer for this exchange. - * @xritag the xri in the bitmap to test. - * - * This function takes the hbalock. - * returns 0 = rrq not active for this xri - * 1 = rrq is valid for this xri. - **/ -int -lpfc_test_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, - uint16_t xritag) -{ - int ret; - unsigned long iflags; - - spin_lock_irqsave(&phba->hbalock, iflags); - ret = __lpfc_test_rrq_active(phba, ndlp, xritag); - spin_unlock_irqrestore(&phba->hbalock, iflags); - return ret; -} - -/** * __lpfc_sli_get_sglq - Allocates an iocb object from sgl pool * @phba: Pointer to HBA context object. * @piocb: Pointer to the iocbq. @@ -884,7 +872,7 @@ __lpfc_sli_get_sglq(struct lpfc_hba *phba, struct lpfc_iocbq *piocbq) return NULL; adj_xri = sglq->sli4_xritag - phba->sli4_hba.max_cfg_param.xri_base; - if (__lpfc_test_rrq_active(phba, ndlp, sglq->sli4_xritag)) { + if (lpfc_test_rrq_active(phba, ndlp, sglq->sli4_xritag)) { /* This xri has an rrq outstanding for this DID. * put it back in the list and get another xri. */ @@ -969,7 +957,8 @@ __lpfc_sli_release_iocbq_s4(struct lpfc_hba *phba, struct lpfc_iocbq *iocbq) } else { sglq->state = SGL_FREED; sglq->ndlp = NULL; - list_add(&sglq->list, &phba->sli4_hba.lpfc_sgl_list); + list_add_tail(&sglq->list, + &phba->sli4_hba.lpfc_sgl_list); /* Check if TXQ queue needs to be serviced */ if (pring->txq_cnt) @@ -4817,7 +4806,10 @@ lpfc_sli4_hba_setup(struct lpfc_hba *phba) "0378 No support for fcpi mode.\n"); ftr_rsp++; } - + if (bf_get(lpfc_mbx_rq_ftr_rsp_perfh, &mqe->un.req_ftrs)) + phba->sli3_options |= LPFC_SLI4_PERFH_ENABLED; + else + phba->sli3_options &= ~LPFC_SLI4_PERFH_ENABLED; /* * If the port cannot support the host's requested features * then turn off the global config parameters to disable the @@ -5004,7 +4996,8 @@ lpfc_sli4_hba_setup(struct lpfc_hba *phba) spin_lock_irq(&phba->hbalock); phba->link_state = LPFC_LINK_DOWN; spin_unlock_irq(&phba->hbalock); - rc = phba->lpfc_hba_init_link(phba, MBX_NOWAIT); + if (phba->cfg_suppress_link_up == LPFC_INITIALIZE_LINK) + rc = phba->lpfc_hba_init_link(phba, MBX_NOWAIT); out_unset_queue: /* Unset all the queues set up in this routine when error out */ if (rc) @@ -10478,6 +10471,7 @@ lpfc_cq_create(struct lpfc_hba *phba, struct lpfc_queue *cq, cq->type = type; cq->subtype = subtype; cq->queue_id = bf_get(lpfc_mbx_cq_create_q_id, &cq_create->u.response); + cq->assoc_qid = eq->queue_id; cq->host_index = 0; cq->hba_index = 0; @@ -10672,6 +10666,7 @@ lpfc_mq_create(struct lpfc_hba *phba, struct lpfc_queue *mq, goto out; } mq->type = LPFC_MQ; + mq->assoc_qid = cq->queue_id; mq->subtype = subtype; mq->host_index = 0; mq->hba_index = 0; @@ -10759,6 +10754,7 @@ lpfc_wq_create(struct lpfc_hba *phba, struct lpfc_queue *wq, goto out; } wq->type = LPFC_WQ; + wq->assoc_qid = cq->queue_id; wq->subtype = subtype; wq->host_index = 0; wq->hba_index = 0; @@ -10876,6 +10872,7 @@ lpfc_rq_create(struct lpfc_hba *phba, struct lpfc_queue *hrq, goto out; } hrq->type = LPFC_HRQ; + hrq->assoc_qid = cq->queue_id; hrq->subtype = subtype; hrq->host_index = 0; hrq->hba_index = 0; @@ -10936,6 +10933,7 @@ lpfc_rq_create(struct lpfc_hba *phba, struct lpfc_queue *hrq, goto out; } drq->type = LPFC_DRQ; + drq->assoc_qid = cq->queue_id; drq->subtype = subtype; drq->host_index = 0; drq->hba_index = 0; @@ -11189,7 +11187,7 @@ lpfc_rq_destroy(struct lpfc_hba *phba, struct lpfc_queue *hrq, if (!mbox) return -ENOMEM; length = (sizeof(struct lpfc_mbx_rq_destroy) - - sizeof(struct mbox_header)); + sizeof(struct lpfc_sli4_cfg_mhdr)); lpfc_sli4_config(phba, mbox, LPFC_MBOX_SUBSYSTEM_FCOE, LPFC_MBOX_OPCODE_FCOE_RQ_DESTROY, length, LPFC_SLI4_MBX_EMBED); @@ -11279,7 +11277,7 @@ lpfc_sli4_post_sgl(struct lpfc_hba *phba, lpfc_sli4_config(phba, mbox, LPFC_MBOX_SUBSYSTEM_FCOE, LPFC_MBOX_OPCODE_FCOE_POST_SGL_PAGES, sizeof(struct lpfc_mbx_post_sgl_pages) - - sizeof(struct mbox_header), LPFC_SLI4_MBX_EMBED); + sizeof(struct lpfc_sli4_cfg_mhdr), LPFC_SLI4_MBX_EMBED); post_sgl_pages = (struct lpfc_mbx_post_sgl_pages *) &mbox->u.mqe.un.post_sgl_pages; @@ -12402,7 +12400,8 @@ lpfc_sli4_post_rpi_hdr(struct lpfc_hba *phba, struct lpfc_rpi_hdr *rpi_page) lpfc_sli4_config(phba, mboxq, LPFC_MBOX_SUBSYSTEM_FCOE, LPFC_MBOX_OPCODE_FCOE_POST_HDR_TEMPLATE, sizeof(struct lpfc_mbx_post_hdr_tmpl) - - sizeof(struct mbox_header), LPFC_SLI4_MBX_EMBED); + sizeof(struct lpfc_sli4_cfg_mhdr), + LPFC_SLI4_MBX_EMBED); bf_set(lpfc_mbx_post_hdr_tmpl_page_cnt, hdr_tmpl, rpi_page->page_count); bf_set(lpfc_mbx_post_hdr_tmpl_rpi_offset, hdr_tmpl, diff --git a/drivers/scsi/lpfc/lpfc_sli4.h b/drivers/scsi/lpfc/lpfc_sli4.h index c7217d579e0f..595056b89608 100644 --- a/drivers/scsi/lpfc/lpfc_sli4.h +++ b/drivers/scsi/lpfc/lpfc_sli4.h @@ -125,9 +125,9 @@ struct lpfc_queue { uint32_t entry_count; /* Number of entries to support on the queue */ uint32_t entry_size; /* Size of each queue entry. */ uint32_t queue_id; /* Queue ID assigned by the hardware */ + uint32_t assoc_qid; /* Queue ID associated with, for CQ/WQ/MQ */ struct list_head page_list; uint32_t page_count; /* Number of pages allocated for this queue */ - uint32_t host_index; /* The host's index for putting or getting */ uint32_t hba_index; /* The last known hba index for get or put */ union sli4_qe qe[1]; /* array to index entries (must be last) */ @@ -359,6 +359,10 @@ struct lpfc_pc_sli4_params { uint32_t hdr_pp_align; uint32_t sgl_pages_max; uint32_t sgl_pp_align; + uint8_t cqv; + uint8_t mqv; + uint8_t wqv; + uint8_t rqv; }; /* SLI4 HBA data structure entries */ @@ -562,6 +566,8 @@ void lpfc_sli4_fcp_xri_aborted(struct lpfc_hba *, struct sli4_wcqe_xri_aborted *); void lpfc_sli4_els_xri_aborted(struct lpfc_hba *, struct sli4_wcqe_xri_aborted *); +void lpfc_sli4_vport_delete_els_xri_aborted(struct lpfc_vport *); +void lpfc_sli4_vport_delete_fcp_xri_aborted(struct lpfc_vport *); int lpfc_sli4_brdreset(struct lpfc_hba *); int lpfc_sli4_add_fcf_record(struct lpfc_hba *, struct fcf_record *); void lpfc_sli_remove_dflt_fcf(struct lpfc_hba *); diff --git a/drivers/scsi/lpfc/lpfc_version.h b/drivers/scsi/lpfc/lpfc_version.h index 386cf92de492..0a4d376dbca5 100644 --- a/drivers/scsi/lpfc/lpfc_version.h +++ b/drivers/scsi/lpfc/lpfc_version.h @@ -1,7 +1,7 @@ /******************************************************************* * This file is part of the Emulex Linux Device Driver for * * Fibre Channel Host Bus Adapters. * - * Copyright (C) 2004-2010 Emulex. All rights reserved. * + * Copyright (C) 2004-2011 Emulex. All rights reserved. * * EMULEX and SLI are trademarks of Emulex. * * www.emulex.com * * * @@ -18,7 +18,7 @@ * included with this package. * *******************************************************************/ -#define LPFC_DRIVER_VERSION "8.3.20" +#define LPFC_DRIVER_VERSION "8.3.21" #define LPFC_DRIVER_NAME "lpfc" #define LPFC_SP_DRIVER_HANDLER_NAME "lpfc:sp" #define LPFC_FP_DRIVER_HANDLER_NAME "lpfc:fp" diff --git a/drivers/scsi/lpfc/lpfc_vport.c b/drivers/scsi/lpfc/lpfc_vport.c index 6b8d2952e32f..30ba5440c67a 100644 --- a/drivers/scsi/lpfc/lpfc_vport.c +++ b/drivers/scsi/lpfc/lpfc_vport.c @@ -464,6 +464,7 @@ disable_vport(struct fc_vport *fc_vport) struct lpfc_hba *phba = vport->phba; struct lpfc_nodelist *ndlp = NULL, *next_ndlp = NULL; long timeout; + struct Scsi_Host *shost = lpfc_shost_from_vport(vport); ndlp = lpfc_findnode_did(vport, Fabric_DID); if (ndlp && NLP_CHK_NODE_ACT(ndlp) @@ -498,6 +499,9 @@ disable_vport(struct fc_vport *fc_vport) * scsi_host_put() to release the vport. */ lpfc_mbx_unreg_vpi(vport); + spin_lock_irq(shost->host_lock); + vport->fc_flag |= FC_VPORT_NEEDS_INIT_VPI; + spin_unlock_irq(shost->host_lock); lpfc_vport_set_state(vport, FC_VPORT_DISABLED); lpfc_printf_vlog(vport, KERN_ERR, LOG_VPORT, diff --git a/drivers/scsi/megaraid/megaraid_sas.h b/drivers/scsi/megaraid/megaraid_sas.h index 1b5e375732c0..635b228c3ead 100644 --- a/drivers/scsi/megaraid/megaraid_sas.h +++ b/drivers/scsi/megaraid/megaraid_sas.h @@ -33,9 +33,9 @@ /* * MegaRAID SAS Driver meta data */ -#define MEGASAS_VERSION "00.00.05.29-rc1" -#define MEGASAS_RELDATE "Dec. 7, 2010" -#define MEGASAS_EXT_VERSION "Tue. Dec. 7 17:00:00 PDT 2010" +#define MEGASAS_VERSION "00.00.05.34-rc1" +#define MEGASAS_RELDATE "Feb. 24, 2011" +#define MEGASAS_EXT_VERSION "Thu. Feb. 24 17:00:00 PDT 2011" /* * Device IDs @@ -723,6 +723,7 @@ struct megasas_ctrl_info { MEGASAS_MAX_DEV_PER_CHANNEL) #define MEGASAS_MAX_SECTORS (2*1024) +#define MEGASAS_MAX_SECTORS_IEEE (2*128) #define MEGASAS_DBG_LVL 1 #define MEGASAS_FW_BUSY 1 @@ -1477,4 +1478,7 @@ struct megasas_mgmt_info { int max_index; }; +#define msi_control_reg(base) (base + PCI_MSI_FLAGS) +#define PCI_MSIX_FLAGS_ENABLE (1 << 15) + #endif /*LSI_MEGARAID_SAS_H */ diff --git a/drivers/scsi/megaraid/megaraid_sas_base.c b/drivers/scsi/megaraid/megaraid_sas_base.c index 5d6d07bd1cd0..f875e818905f 100644 --- a/drivers/scsi/megaraid/megaraid_sas_base.c +++ b/drivers/scsi/megaraid/megaraid_sas_base.c @@ -18,12 +18,13 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * FILE: megaraid_sas_base.c - * Version : v00.00.05.29-rc1 + * Version : v00.00.05.34-rc1 * * Authors: LSI Corporation * Sreenivas Bagalkote * Sumant Patro * Bo Yang + * Adam Radford <linuxraid@lsi.com> * * Send feedback to: <megaraidlinux@lsi.com> * @@ -134,7 +135,11 @@ spinlock_t poll_aen_lock; void megasas_complete_cmd(struct megasas_instance *instance, struct megasas_cmd *cmd, u8 alt_status); - +static u32 +megasas_read_fw_status_reg_gen2(struct megasas_register_set __iomem *regs); +static int +megasas_adp_reset_gen2(struct megasas_instance *instance, + struct megasas_register_set __iomem *reg_set); static irqreturn_t megasas_isr(int irq, void *devp); static u32 megasas_init_adapter_mfi(struct megasas_instance *instance); @@ -554,6 +559,8 @@ static int megasas_clear_intr_skinny(struct megasas_register_set __iomem *regs) { u32 status; + u32 mfiStatus = 0; + /* * Check if it is our interrupt */ @@ -564,6 +571,15 @@ megasas_clear_intr_skinny(struct megasas_register_set __iomem *regs) } /* + * Check if it is our interrupt + */ + if ((megasas_read_fw_status_reg_gen2(regs) & MFI_STATE_MASK) == + MFI_STATE_FAULT) { + mfiStatus = MFI_INTR_FLAG_FIRMWARE_STATE_CHANGE; + } else + mfiStatus = MFI_INTR_FLAG_REPLY_MESSAGE; + + /* * Clear the interrupt by writing back the same value */ writel(status, ®s->outbound_intr_status); @@ -573,7 +589,7 @@ megasas_clear_intr_skinny(struct megasas_register_set __iomem *regs) */ readl(®s->outbound_intr_status); - return 1; + return mfiStatus; } /** @@ -597,17 +613,6 @@ megasas_fire_cmd_skinny(struct megasas_instance *instance, } /** - * megasas_adp_reset_skinny - For controller reset - * @regs: MFI register set - */ -static int -megasas_adp_reset_skinny(struct megasas_instance *instance, - struct megasas_register_set __iomem *regs) -{ - return 0; -} - -/** * megasas_check_reset_skinny - For controller reset check * @regs: MFI register set */ @@ -625,7 +630,7 @@ static struct megasas_instance_template megasas_instance_template_skinny = { .disable_intr = megasas_disable_intr_skinny, .clear_intr = megasas_clear_intr_skinny, .read_fw_status_reg = megasas_read_fw_status_reg_skinny, - .adp_reset = megasas_adp_reset_skinny, + .adp_reset = megasas_adp_reset_gen2, .check_reset = megasas_check_reset_skinny, .service_isr = megasas_isr, .tasklet = megasas_complete_cmd_dpc, @@ -740,20 +745,28 @@ megasas_adp_reset_gen2(struct megasas_instance *instance, { u32 retry = 0 ; u32 HostDiag; + u32 *seq_offset = ®_set->seq_offset; + u32 *hostdiag_offset = ®_set->host_diag; + + if (instance->instancet == &megasas_instance_template_skinny) { + seq_offset = ®_set->fusion_seq_offset; + hostdiag_offset = ®_set->fusion_host_diag; + } + + writel(0, seq_offset); + writel(4, seq_offset); + writel(0xb, seq_offset); + writel(2, seq_offset); + writel(7, seq_offset); + writel(0xd, seq_offset); - writel(0, ®_set->seq_offset); - writel(4, ®_set->seq_offset); - writel(0xb, ®_set->seq_offset); - writel(2, ®_set->seq_offset); - writel(7, ®_set->seq_offset); - writel(0xd, ®_set->seq_offset); msleep(1000); - HostDiag = (u32)readl(®_set->host_diag); + HostDiag = (u32)readl(hostdiag_offset); while ( !( HostDiag & DIAG_WRITE_ENABLE) ) { msleep(100); - HostDiag = (u32)readl(®_set->host_diag); + HostDiag = (u32)readl(hostdiag_offset); printk(KERN_NOTICE "RESETGEN2: retry=%x, hostdiag=%x\n", retry, HostDiag); @@ -764,14 +777,14 @@ megasas_adp_reset_gen2(struct megasas_instance *instance, printk(KERN_NOTICE "ADP_RESET_GEN2: HostDiag=%x\n", HostDiag); - writel((HostDiag | DIAG_RESET_ADAPTER), ®_set->host_diag); + writel((HostDiag | DIAG_RESET_ADAPTER), hostdiag_offset); ssleep(10); - HostDiag = (u32)readl(®_set->host_diag); + HostDiag = (u32)readl(hostdiag_offset); while ( ( HostDiag & DIAG_RESET_ADAPTER) ) { msleep(100); - HostDiag = (u32)readl(®_set->host_diag); + HostDiag = (u32)readl(hostdiag_offset); printk(KERN_NOTICE "RESET_GEN2: retry=%x, hostdiag=%x\n", retry, HostDiag); @@ -2503,7 +2516,9 @@ megasas_deplete_reply_queue(struct megasas_instance *instance, if ((mfiStatus = instance->instancet->clear_intr( instance->reg_set) ) == 0) { - return IRQ_NONE; + /* Hardware may not set outbound_intr_status in MSI-X mode */ + if (!instance->msi_flag) + return IRQ_NONE; } instance->mfiStatus = mfiStatus; @@ -2611,7 +2626,9 @@ megasas_transition_to_ready(struct megasas_instance* instance) case MFI_STATE_FAULT: printk(KERN_DEBUG "megasas: FW in FAULT state!!\n"); - return -ENODEV; + max_wait = MEGASAS_RESET_WAIT_TIME; + cur_state = MFI_STATE_FAULT; + break; case MFI_STATE_WAIT_HANDSHAKE: /* @@ -3424,7 +3441,6 @@ fail_reply_queue: megasas_free_cmds(instance); fail_alloc_cmds: - iounmap(instance->reg_set); return 1; } @@ -3494,7 +3510,7 @@ static int megasas_init_fw(struct megasas_instance *instance) /* Get operational params, sge flags, send init cmd to controller */ if (instance->instancet->init_adapter(instance)) - return -ENODEV; + goto fail_init_adapter; printk(KERN_ERR "megasas: INIT adapter done\n"); @@ -3543,7 +3559,7 @@ static int megasas_init_fw(struct megasas_instance *instance) * Setup tasklet for cmd completion */ - tasklet_init(&instance->isr_tasklet, megasas_complete_cmd_dpc, + tasklet_init(&instance->isr_tasklet, instance->instancet->tasklet, (unsigned long)instance); /* Initialize the cmd completion timer */ @@ -3553,6 +3569,7 @@ static int megasas_init_fw(struct megasas_instance *instance) MEGASAS_COMPLETION_TIMER_INTERVAL); return 0; +fail_init_adapter: fail_ready_state: iounmap(instance->reg_set); @@ -3820,6 +3837,10 @@ static int megasas_io_attach(struct megasas_instance *instance) instance->max_fw_cmds - MEGASAS_INT_CMDS; host->this_id = instance->init_id; host->sg_tablesize = instance->max_num_sge; + + if (instance->fw_support_ieee) + instance->max_sectors_per_req = MEGASAS_MAX_SECTORS_IEEE; + /* * Check if the module parameter value for max_sectors can be used */ @@ -3899,9 +3920,26 @@ fail_set_dma_mask: static int __devinit megasas_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) { - int rval; + int rval, pos; struct Scsi_Host *host; struct megasas_instance *instance; + u16 control = 0; + + /* Reset MSI-X in the kdump kernel */ + if (reset_devices) { + pos = pci_find_capability(pdev, PCI_CAP_ID_MSIX); + if (pos) { + pci_read_config_word(pdev, msi_control_reg(pos), + &control); + if (control & PCI_MSIX_FLAGS_ENABLE) { + dev_info(&pdev->dev, "resetting MSI-X\n"); + pci_write_config_word(pdev, + msi_control_reg(pos), + control & + ~PCI_MSIX_FLAGS_ENABLE); + } + } + } /* * Announce PCI information @@ -4039,12 +4077,6 @@ megasas_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) else INIT_WORK(&instance->work_init, process_fw_state_change_wq); - /* - * Initialize MFI Firmware - */ - if (megasas_init_fw(instance)) - goto fail_init_mfi; - /* Try to enable MSI-X */ if ((instance->pdev->device != PCI_DEVICE_ID_LSI_SAS1078R) && (instance->pdev->device != PCI_DEVICE_ID_LSI_SAS1078DE) && @@ -4054,6 +4086,12 @@ megasas_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) instance->msi_flag = 1; /* + * Initialize MFI Firmware + */ + if (megasas_init_fw(instance)) + goto fail_init_mfi; + + /* * Register IRQ */ if (request_irq(instance->msi_flag ? instance->msixentry.vector : @@ -4105,24 +4143,23 @@ megasas_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) instance->instancet->disable_intr(instance->reg_set); free_irq(instance->msi_flag ? instance->msixentry.vector : instance->pdev->irq, instance); +fail_irq: + if (instance->pdev->device == PCI_DEVICE_ID_LSI_FUSION) + megasas_release_fusion(instance); + else + megasas_release_mfi(instance); + fail_init_mfi: if (instance->msi_flag) pci_disable_msix(instance->pdev); - - fail_irq: - fail_init_mfi: fail_alloc_dma_buf: if (instance->evt_detail) pci_free_consistent(pdev, sizeof(struct megasas_evt_detail), instance->evt_detail, instance->evt_detail_h); - if (instance->producer) { + if (instance->producer) pci_free_consistent(pdev, sizeof(u32), instance->producer, instance->producer_h); - megasas_release_mfi(instance); - } else { - megasas_release_fusion(instance); - } if (instance->consumer) pci_free_consistent(pdev, sizeof(u32), instance->consumer, instance->consumer_h); @@ -4242,9 +4279,8 @@ megasas_suspend(struct pci_dev *pdev, pm_message_t state) /* cancel the delayed work if this work still in queue */ if (instance->ev != NULL) { struct megasas_aen_event *ev = instance->ev; - cancel_delayed_work( + cancel_delayed_work_sync( (struct delayed_work *)&ev->hotplug_work); - flush_scheduled_work(); instance->ev = NULL; } @@ -4297,6 +4333,10 @@ megasas_resume(struct pci_dev *pdev) if (megasas_set_dma_mask(pdev)) goto fail_set_dma_mask; + /* Now re-enable MSI-X */ + if (instance->msi_flag) + pci_enable_msix(instance->pdev, &instance->msixentry, 1); + /* * Initialize MFI Firmware */ @@ -4333,10 +4373,6 @@ megasas_resume(struct pci_dev *pdev) tasklet_init(&instance->isr_tasklet, instance->instancet->tasklet, (unsigned long)instance); - /* Now re-enable MSI-X */ - if (instance->msi_flag) - pci_enable_msix(instance->pdev, &instance->msixentry, 1); - /* * Register IRQ */ @@ -4417,9 +4453,8 @@ static void __devexit megasas_detach_one(struct pci_dev *pdev) /* cancel the delayed work if this work still in queue*/ if (instance->ev != NULL) { struct megasas_aen_event *ev = instance->ev; - cancel_delayed_work( + cancel_delayed_work_sync( (struct delayed_work *)&ev->hotplug_work); - flush_scheduled_work(); instance->ev = NULL; } @@ -4611,6 +4646,9 @@ megasas_mgmt_fw_ioctl(struct megasas_instance *instance, * For each user buffer, create a mirror buffer and copy in */ for (i = 0; i < ioc->sge_count; i++) { + if (!ioc->sgl[i].iov_len) + continue; + kbuff_arr[i] = dma_alloc_coherent(&instance->pdev->dev, ioc->sgl[i].iov_len, &buf_handle, GFP_KERNEL); @@ -5177,6 +5215,7 @@ megasas_aen_polling(struct work_struct *work) break; case MR_EVT_LD_OFFLINE: + case MR_EVT_CFG_CLEARED: case MR_EVT_LD_DELETED: megasas_get_ld_list(instance); for (i = 0; i < MEGASAS_MAX_LD_CHANNELS; i++) { diff --git a/drivers/scsi/megaraid/megaraid_sas_fusion.c b/drivers/scsi/megaraid/megaraid_sas_fusion.c index c1e09d5a6196..905c1e9317b6 100644 --- a/drivers/scsi/megaraid/megaraid_sas_fusion.c +++ b/drivers/scsi/megaraid/megaraid_sas_fusion.c @@ -82,6 +82,10 @@ u16 MR_TargetIdToLdGet(u32 ldTgtId, struct MR_FW_RAID_MAP_ALL *map); struct MR_LD_RAID *MR_LdRaidGet(u32 ld, struct MR_FW_RAID_MAP_ALL *map); u16 MR_GetLDTgtId(u32 ld, struct MR_FW_RAID_MAP_ALL *map); + +void +megasas_check_and_restore_queue_depth(struct megasas_instance *instance); + u8 MR_ValidateMapInfo(struct MR_FW_RAID_MAP_ALL *map, struct LD_LOAD_BALANCE_INFO *lbInfo); u16 get_updated_dev_handle(struct LD_LOAD_BALANCE_INFO *lbInfo, @@ -984,13 +988,15 @@ megasas_init_adapter_fusion(struct megasas_instance *instance) return 0; -fail_alloc_cmds: -fail_alloc_mfi_cmds: fail_map_info: if (i == 1) dma_free_coherent(&instance->pdev->dev, fusion->map_sz, fusion->ld_map[0], fusion->ld_map_phys[0]); fail_ioc_init: + megasas_free_cmds_fusion(instance); +fail_alloc_cmds: + megasas_free_cmds(instance); +fail_alloc_mfi_cmds: return 1; } @@ -1432,8 +1438,7 @@ megasas_build_dcdb_fusion(struct megasas_instance *instance, local_map_ptr = fusion->ld_map[(instance->map_id & 1)]; /* Check if this is a system PD I/O */ - if ((instance->pd_list[pd_index].driveState == MR_PD_STATE_SYSTEM) && - (instance->pd_list[pd_index].driveType == TYPE_DISK)) { + if (instance->pd_list[pd_index].driveState == MR_PD_STATE_SYSTEM) { io_request->Function = 0; io_request->DevHandle = local_map_ptr->raidMap.devHndlInfo[device_id].curDevHdl; @@ -1456,7 +1461,7 @@ megasas_build_dcdb_fusion(struct megasas_instance *instance, MEGASAS_REQ_DESCRIPT_FLAGS_TYPE_SHIFT); } io_request->RaidContext.VirtualDiskTgtId = device_id; - io_request->LUN[0] = scmd->device->lun; + io_request->LUN[1] = scmd->device->lun; io_request->DataLength = scsi_bufflen(scmd); } @@ -1480,7 +1485,7 @@ megasas_build_io_fusion(struct megasas_instance *instance, device_id = MEGASAS_DEV_INDEX(instance, scp); /* Zero out some fields so they don't get reused */ - io_request->LUN[0] = 0; + io_request->LUN[1] = 0; io_request->CDB.EEDP32.PrimaryReferenceTag = 0; io_request->CDB.EEDP32.PrimaryApplicationTagMask = 0; io_request->EEDPFlags = 0; @@ -1744,7 +1749,7 @@ complete_cmd_fusion(struct megasas_instance *instance) wmb(); writel(fusion->last_reply_idx, &instance->reg_set->reply_post_host_index); - + megasas_check_and_restore_queue_depth(instance); return IRQ_HANDLED; } diff --git a/drivers/scsi/mpt2sas/mpi/mpi2.h b/drivers/scsi/mpt2sas/mpi/mpi2.h index 8be75e65f763..a3e60385787f 100644 --- a/drivers/scsi/mpt2sas/mpi/mpi2.h +++ b/drivers/scsi/mpt2sas/mpi/mpi2.h @@ -8,7 +8,7 @@ * scatter/gather formats. * Creation Date: June 21, 2006 * - * mpi2.h Version: 02.00.16 + * mpi2.h Version: 02.00.17 * * Version History * --------------- @@ -63,6 +63,7 @@ * function codes, 0xF0 to 0xFF. * 05-12-10 02.00.16 Bumped MPI2_HEADER_VERSION_UNIT. * Added alternative defines for the SGE Direction bit. + * 08-11-10 02.00.17 Bumped MPI2_HEADER_VERSION_UNIT. * -------------------------------------------------------------------------- */ @@ -88,7 +89,7 @@ #define MPI2_VERSION_02_00 (0x0200) /* versioning for this MPI header set */ -#define MPI2_HEADER_VERSION_UNIT (0x10) +#define MPI2_HEADER_VERSION_UNIT (0x11) #define MPI2_HEADER_VERSION_DEV (0x00) #define MPI2_HEADER_VERSION_UNIT_MASK (0xFF00) #define MPI2_HEADER_VERSION_UNIT_SHIFT (8) diff --git a/drivers/scsi/mpt2sas/mpi/mpi2_cnfg.h b/drivers/scsi/mpt2sas/mpi/mpi2_cnfg.h index d76a65847603..f5b9c766e28f 100644 --- a/drivers/scsi/mpt2sas/mpi/mpi2_cnfg.h +++ b/drivers/scsi/mpt2sas/mpi/mpi2_cnfg.h @@ -6,7 +6,7 @@ * Title: MPI Configuration messages and pages * Creation Date: November 10, 2006 * - * mpi2_cnfg.h Version: 02.00.15 + * mpi2_cnfg.h Version: 02.00.16 * * Version History * --------------- @@ -125,6 +125,8 @@ * define. * Added MPI2_PHYSDISK0_INCOMPATIBLE_MEDIA_TYPE define. * Added MPI2_SAS_NEG_LINK_RATE_UNSUPPORTED_PHY define. + * 08-11-10 02.00.16 Removed IO Unit Page 1 device path (multi-pathing) + * defines. * -------------------------------------------------------------------------- */ @@ -745,8 +747,6 @@ typedef struct _MPI2_CONFIG_PAGE_IO_UNIT_1 #define MPI2_IOUNITPAGE1_DISABLE_IR (0x00000040) #define MPI2_IOUNITPAGE1_DISABLE_TASK_SET_FULL_HANDLING (0x00000020) #define MPI2_IOUNITPAGE1_IR_USE_STATIC_VOLUME_ID (0x00000004) -#define MPI2_IOUNITPAGE1_MULTI_PATHING (0x00000002) -#define MPI2_IOUNITPAGE1_SINGLE_PATHING (0x00000000) /* IO Unit Page 3 */ diff --git a/drivers/scsi/mpt2sas/mpi/mpi2_history.txt b/drivers/scsi/mpt2sas/mpi/mpi2_history.txt deleted file mode 100644 index b1e88f26b748..000000000000 --- a/drivers/scsi/mpt2sas/mpi/mpi2_history.txt +++ /dev/null @@ -1,384 +0,0 @@ - ============================== - Fusion-MPT MPI 2.0 Header File Change History - ============================== - - Copyright (c) 2000-2010 LSI Corporation. - - --------------------------------------- - Header Set Release Version: 02.00.14 - Header Set Release Date: 10-28-09 - --------------------------------------- - - Filename Current version Prior version - ---------- --------------- ------------- - mpi2.h 02.00.14 02.00.13 - mpi2_cnfg.h 02.00.13 02.00.12 - mpi2_init.h 02.00.08 02.00.07 - mpi2_ioc.h 02.00.13 02.00.12 - mpi2_raid.h 02.00.04 02.00.04 - mpi2_sas.h 02.00.03 02.00.02 - mpi2_targ.h 02.00.03 02.00.03 - mpi2_tool.h 02.00.04 02.00.04 - mpi2_type.h 02.00.00 02.00.00 - mpi2_ra.h 02.00.00 02.00.00 - mpi2_hbd.h 02.00.00 - mpi2_history.txt 02.00.14 02.00.13 - - - * Date Version Description - * -------- -------- ------------------------------------------------------ - -mpi2.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 06-04-07 02.00.01 Bumped MPI2_HEADER_VERSION_UNIT. - * 06-26-07 02.00.02 Bumped MPI2_HEADER_VERSION_UNIT. - * 08-31-07 02.00.03 Bumped MPI2_HEADER_VERSION_UNIT. - * Moved ReplyPostHostIndex register to offset 0x6C of the - * MPI2_SYSTEM_INTERFACE_REGS and modified the define for - * MPI2_REPLY_POST_HOST_INDEX_OFFSET. - * Added union of request descriptors. - * Added union of reply descriptors. - * 10-31-07 02.00.04 Bumped MPI2_HEADER_VERSION_UNIT. - * Added define for MPI2_VERSION_02_00. - * Fixed the size of the FunctionDependent5 field in the - * MPI2_DEFAULT_REPLY structure. - * 12-18-07 02.00.05 Bumped MPI2_HEADER_VERSION_UNIT. - * Removed the MPI-defined Fault Codes and extended the - * product specific codes up to 0xEFFF. - * Added a sixth key value for the WriteSequence register - * and changed the flush value to 0x0. - * Added message function codes for Diagnostic Buffer Post - * and Diagnsotic Release. - * New IOCStatus define: MPI2_IOCSTATUS_DIAGNOSTIC_RELEASED - * Moved MPI2_VERSION_UNION from mpi2_ioc.h. - * 02-29-08 02.00.06 Bumped MPI2_HEADER_VERSION_UNIT. - * 03-03-08 02.00.07 Bumped MPI2_HEADER_VERSION_UNIT. - * 05-21-08 02.00.08 Bumped MPI2_HEADER_VERSION_UNIT. - * Added #defines for marking a reply descriptor as unused. - * 06-27-08 02.00.09 Bumped MPI2_HEADER_VERSION_UNIT. - * 10-02-08 02.00.10 Bumped MPI2_HEADER_VERSION_UNIT. - * Moved LUN field defines from mpi2_init.h. - * 01-19-09 02.00.11 Bumped MPI2_HEADER_VERSION_UNIT. - * 05-06-09 02.00.12 Bumped MPI2_HEADER_VERSION_UNIT. - * In all request and reply descriptors, replaced VF_ID - * field with MSIxIndex field. - * Removed DevHandle field from - * MPI2_SCSI_IO_SUCCESS_REPLY_DESCRIPTOR and made those - * bytes reserved. - * Added RAID Accelerator functionality. - * 07-30-09 02.00.13 Bumped MPI2_HEADER_VERSION_UNIT. - * 10-28-09 02.00.14 Bumped MPI2_HEADER_VERSION_UNIT. - * Added MSI-x index mask and shift for Reply Post Host - * Index register. - * Added function code for Host Based Discovery Action. - * -------------------------------------------------------------------------- - -mpi2_cnfg.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 06-04-07 02.00.01 Added defines for SAS IO Unit Page 2 PhyFlags. - * Added Manufacturing Page 11. - * Added MPI2_SAS_EXPANDER0_FLAGS_CONNECTOR_END_DEVICE - * define. - * 06-26-07 02.00.02 Adding generic structure for product-specific - * Manufacturing pages: MPI2_CONFIG_PAGE_MANUFACTURING_PS. - * Rework of BIOS Page 2 configuration page. - * Fixed MPI2_BIOSPAGE2_BOOT_DEVICE to be a union of the - * forms. - * Added configuration pages IOC Page 8 and Driver - * Persistent Mapping Page 0. - * 08-31-07 02.00.03 Modified configuration pages dealing with Integrated - * RAID (Manufacturing Page 4, RAID Volume Pages 0 and 1, - * RAID Physical Disk Pages 0 and 1, RAID Configuration - * Page 0). - * Added new value for AccessStatus field of SAS Device - * Page 0 (_SATA_NEEDS_INITIALIZATION). - * 10-31-07 02.00.04 Added missing SEPDevHandle field to - * MPI2_CONFIG_PAGE_SAS_ENCLOSURE_0. - * 12-18-07 02.00.05 Modified IO Unit Page 0 to use 32-bit version fields for - * NVDATA. - * Modified IOC Page 7 to use masks and added field for - * SASBroadcastPrimitiveMasks. - * Added MPI2_CONFIG_PAGE_BIOS_4. - * Added MPI2_CONFIG_PAGE_LOG_0. - * 02-29-08 02.00.06 Modified various names to make them 32-character unique. - * Added SAS Device IDs. - * Updated Integrated RAID configuration pages including - * Manufacturing Page 4, IOC Page 6, and RAID Configuration - * Page 0. - * 05-21-08 02.00.07 Added define MPI2_MANPAGE4_MIX_SSD_SAS_SATA. - * Added define MPI2_MANPAGE4_PHYSDISK_128MB_COERCION. - * Fixed define MPI2_IOCPAGE8_FLAGS_ENCLOSURE_SLOT_MAPPING. - * Added missing MaxNumRoutedSasAddresses field to - * MPI2_CONFIG_PAGE_EXPANDER_0. - * Added SAS Port Page 0. - * Modified structure layout for - * MPI2_CONFIG_PAGE_DRIVER_MAPPING_0. - * 06-27-08 02.00.08 Changed MPI2_CONFIG_PAGE_RD_PDISK_1 to use - * MPI2_RAID_PHYS_DISK1_PATH_MAX to size the array. - * 10-02-08 02.00.09 Changed MPI2_RAID_PGAD_CONFIGNUM_MASK from 0x0000FFFF - * to 0x000000FF. - * Added two new values for the Physical Disk Coercion Size - * bits in the Flags field of Manufacturing Page 4. - * Added product-specific Manufacturing pages 16 to 31. - * Modified Flags bits for controlling write cache on SATA - * drives in IO Unit Page 1. - * Added new bit to AdditionalControlFlags of SAS IO Unit - * Page 1 to control Invalid Topology Correction. - * Added SupportedPhysDisks field to RAID Volume Page 1 and - * added related defines. - * Added additional defines for RAID Volume Page 0 - * VolumeStatusFlags field. - * Modified meaning of RAID Volume Page 0 VolumeSettings - * define for auto-configure of hot-swap drives. - * Added PhysDiskAttributes field (and related defines) to - * RAID Physical Disk Page 0. - * Added MPI2_SAS_PHYINFO_PHY_VACANT define. - * Added three new DiscoveryStatus bits for SAS IO Unit - * Page 0 and SAS Expander Page 0. - * Removed multiplexing information from SAS IO Unit pages. - * Added BootDeviceWaitTime field to SAS IO Unit Page 4. - * Removed Zone Address Resolved bit from PhyInfo and from - * Expander Page 0 Flags field. - * Added two new AccessStatus values to SAS Device Page 0 - * for indicating routing problems. Added 3 reserved words - * to this page. - * 01-19-09 02.00.10 Fixed defines for GPIOVal field of IO Unit Page 3. - * Inserted missing reserved field into structure for IOC - * Page 6. - * Added more pending task bits to RAID Volume Page 0 - * VolumeStatusFlags defines. - * Added MPI2_PHYSDISK0_STATUS_FLAG_NOT_CERTIFIED define. - * Added a new DiscoveryStatus bit for SAS IO Unit Page 0 - * and SAS Expander Page 0 to flag a downstream initiator - * when in simplified routing mode. - * Removed SATA Init Failure defines for DiscoveryStatus - * fields of SAS IO Unit Page 0 and SAS Expander Page 0. - * Added MPI2_SAS_DEVICE0_ASTATUS_DEVICE_BLOCKED define. - * Added PortGroups, DmaGroup, and ControlGroup fields to - * SAS Device Page 0. - * 05-06-09 02.00.11 Added structures and defines for IO Unit Page 5 and IO - * Unit Page 6. - * Added expander reduced functionality data to SAS - * Expander Page 0. - * Added SAS PHY Page 2 and SAS PHY Page 3. - * 07-30-09 02.00.12 Added IO Unit Page 7. - * Added new device ids. - * Added SAS IO Unit Page 5. - * Added partial and slumber power management capable flags - * to SAS Device Page 0 Flags field. - * Added PhyInfo defines for power condition. - * Added Ethernet configuration pages. - * 10-28-09 02.00.13 Added MPI2_IOUNITPAGE1_ENABLE_HOST_BASED_DISCOVERY. - * Added SAS PHY Page 4 structure and defines. - * -------------------------------------------------------------------------- - -mpi2_init.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 10-31-07 02.00.01 Fixed name for pMpi2SCSITaskManagementRequest_t. - * 12-18-07 02.00.02 Modified Task Management Target Reset Method defines. - * 02-29-08 02.00.03 Added Query Task Set and Query Unit Attention. - * 03-03-08 02.00.04 Fixed name of struct _MPI2_SCSI_TASK_MANAGE_REPLY. - * 05-21-08 02.00.05 Fixed typo in name of Mpi2SepRequest_t. - * 10-02-08 02.00.06 Removed Untagged and No Disconnect values from SCSI IO - * Control field Task Attribute flags. - * Moved LUN field defines to mpi2.h becasue they are - * common to many structures. - * 05-06-09 02.00.07 Changed task management type of Query Unit Attention to - * Query Asynchronous Event. - * Defined two new bits in the SlotStatus field of the SCSI - * Enclosure Processor Request and Reply. - * 10-28-09 02.00.08 Added defines for decoding the ResponseInfo bytes for - * both SCSI IO Error Reply and SCSI Task Management Reply. - * Added ResponseInfo field to MPI2_SCSI_TASK_MANAGE_REPLY. - * Added MPI2_SCSITASKMGMT_RSP_TM_OVERLAPPED_TAG define. - * -------------------------------------------------------------------------- - -mpi2_ioc.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 06-04-07 02.00.01 In IOCFacts Reply structure, renamed MaxDevices to - * MaxTargets. - * Added TotalImageSize field to FWDownload Request. - * Added reserved words to FWUpload Request. - * 06-26-07 02.00.02 Added IR Configuration Change List Event. - * 08-31-07 02.00.03 Removed SystemReplyQueueDepth field from the IOCInit - * request and replaced it with - * ReplyDescriptorPostQueueDepth and ReplyFreeQueueDepth. - * Replaced the MinReplyQueueDepth field of the IOCFacts - * reply with MaxReplyDescriptorPostQueueDepth. - * Added MPI2_RDPQ_DEPTH_MIN define to specify the minimum - * depth for the Reply Descriptor Post Queue. - * Added SASAddress field to Initiator Device Table - * Overflow Event data. - * 10-31-07 02.00.04 Added ReasonCode MPI2_EVENT_SAS_INIT_RC_NOT_RESPONDING - * for SAS Initiator Device Status Change Event data. - * Modified Reason Code defines for SAS Topology Change - * List Event data, including adding a bit for PHY Vacant - * status, and adding a mask for the Reason Code. - * Added define for - * MPI2_EVENT_SAS_TOPO_ES_DELAY_NOT_RESPONDING. - * Added define for MPI2_EXT_IMAGE_TYPE_MEGARAID. - * 12-18-07 02.00.05 Added Boot Status defines for the IOCExceptions field of - * the IOCFacts Reply. - * Removed MPI2_IOCFACTS_CAPABILITY_EXTENDED_BUFFER define. - * Moved MPI2_VERSION_UNION to mpi2.h. - * Changed MPI2_EVENT_NOTIFICATION_REQUEST to use masks - * instead of enables, and added SASBroadcastPrimitiveMasks - * field. - * Added Log Entry Added Event and related structure. - * 02-29-08 02.00.06 Added define MPI2_IOCFACTS_CAPABILITY_INTEGRATED_RAID. - * Removed define MPI2_IOCFACTS_PROTOCOL_SMP_TARGET. - * Added MaxVolumes and MaxPersistentEntries fields to - * IOCFacts reply. - * Added ProtocalFlags and IOCCapabilities fields to - * MPI2_FW_IMAGE_HEADER. - * Removed MPI2_PORTENABLE_FLAGS_ENABLE_SINGLE_PORT. - * 03-03-08 02.00.07 Fixed MPI2_FW_IMAGE_HEADER by changing Reserved26 to - * a U16 (from a U32). - * Removed extra 's' from EventMasks name. - * 06-27-08 02.00.08 Fixed an offset in a comment. - * 10-02-08 02.00.09 Removed SystemReplyFrameSize from MPI2_IOC_INIT_REQUEST. - * Removed CurReplyFrameSize from MPI2_IOC_FACTS_REPLY and - * renamed MinReplyFrameSize to ReplyFrameSize. - * Added MPI2_IOCFACTS_EXCEPT_IR_FOREIGN_CONFIG_MAX. - * Added two new RAIDOperation values for Integrated RAID - * Operations Status Event data. - * Added four new IR Configuration Change List Event data - * ReasonCode values. - * Added two new ReasonCode defines for SAS Device Status - * Change Event data. - * Added three new DiscoveryStatus bits for the SAS - * Discovery event data. - * Added Multiplexing Status Change bit to the PhyStatus - * field of the SAS Topology Change List event data. - * Removed define for MPI2_INIT_IMAGE_BOOTFLAGS_XMEMCOPY. - * BootFlags are now product-specific. - * Added defines for the indivdual signature bytes - * for MPI2_INIT_IMAGE_FOOTER. - * 01-19-09 02.00.10 Added MPI2_IOCFACTS_CAPABILITY_EVENT_REPLAY define. - * Added MPI2_EVENT_SAS_DISC_DS_DOWNSTREAM_INITIATOR - * define. - * Added MPI2_EVENT_SAS_DEV_STAT_RC_SATA_INIT_FAILURE - * define. - * Removed MPI2_EVENT_SAS_DISC_DS_SATA_INIT_FAILURE define. - * 05-06-09 02.00.11 Added MPI2_IOCFACTS_CAPABILITY_RAID_ACCELERATOR define. - * Added MPI2_IOCFACTS_CAPABILITY_MSI_X_INDEX define. - * Added two new reason codes for SAS Device Status Change - * Event. - * Added new event: SAS PHY Counter. - * 07-30-09 02.00.12 Added GPIO Interrupt event define and structure. - * Added MPI2_IOCFACTS_CAPABILITY_EXTENDED_BUFFER define. - * Added new product id family for 2208. - * 10-28-09 02.00.13 Added HostMSIxVectors field to MPI2_IOC_INIT_REQUEST. - * Added MaxMSIxVectors field to MPI2_IOC_FACTS_REPLY. - * Added MinDevHandle field to MPI2_IOC_FACTS_REPLY. - * Added MPI2_IOCFACTS_CAPABILITY_HOST_BASED_DISCOVERY. - * Added MPI2_EVENT_HOST_BASED_DISCOVERY_PHY define. - * Added MPI2_EVENT_SAS_TOPO_ES_NO_EXPANDER define. - * Added Host Based Discovery Phy Event data. - * Added defines for ProductID Product field - * (MPI2_FW_HEADER_PID_). - * Modified values for SAS ProductID Family - * (MPI2_FW_HEADER_PID_FAMILY_). - * -------------------------------------------------------------------------- - -mpi2_raid.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 08-31-07 02.00.01 Modifications to RAID Action request and reply, - * including the Actions and ActionData. - * 02-29-08 02.00.02 Added MPI2_RAID_ACTION_ADATA_DISABL_FULL_REBUILD. - * 05-21-08 02.00.03 Added MPI2_RAID_VOL_CREATION_NUM_PHYSDISKS so that - * the PhysDisk array in MPI2_RAID_VOLUME_CREATION_STRUCT - * can be sized by the build environment. - * 07-30-09 02.00.04 Added proper define for the Use Default Settings bit of - * VolumeCreationFlags and marked the old one as obsolete. - * 05-12-10 02.00.05 Added MPI2_RAID_VOL_FLAGS_OP_MDC define. - * -------------------------------------------------------------------------- - -mpi2_sas.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 06-26-07 02.00.01 Added Clear All Persistent Operation to SAS IO Unit - * Control Request. - * 10-02-08 02.00.02 Added Set IOC Parameter Operation to SAS IO Unit Control - * Request. - * 10-28-09 02.00.03 Changed the type of SGL in MPI2_SATA_PASSTHROUGH_REQUEST - * to MPI2_SGE_IO_UNION since it supports chained SGLs. - * 05-12-10 02.00.04 Modified some comments. - * -------------------------------------------------------------------------- - -mpi2_targ.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 08-31-07 02.00.01 Added Command Buffer Data Location Address Space bits to - * BufferPostFlags field of CommandBufferPostBase Request. - * 02-29-08 02.00.02 Modified various names to make them 32-character unique. - * 10-02-08 02.00.03 Removed NextCmdBufferOffset from - * MPI2_TARGET_CMD_BUF_POST_BASE_REQUEST. - * Target Status Send Request only takes a single SGE for - * response data. - * -------------------------------------------------------------------------- - -mpi2_tool.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * 12-18-07 02.00.01 Added Diagnostic Buffer Post and Diagnostic Release - * structures and defines. - * 02-29-08 02.00.02 Modified various names to make them 32-character unique. - * 05-06-09 02.00.03 Added ISTWI Read Write Tool and Diagnostic CLI Tool. - * 07-30-09 02.00.04 Added ExtendedType field to DiagnosticBufferPost request - * and reply messages. - * Added MPI2_DIAG_BUF_TYPE_EXTENDED. - * Incremented MPI2_DIAG_BUF_TYPE_COUNT. - * 05-12-10 02.00.05 Added Diagnostic Data Upload tool. - * -------------------------------------------------------------------------- - -mpi2_type.h - * 04-30-07 02.00.00 Corresponds to Fusion-MPT MPI Specification Rev A. - * -------------------------------------------------------------------------- - -mpi2_ra.h - * 05-06-09 02.00.00 Initial version. - * -------------------------------------------------------------------------- - -mpi2_hbd.h - * 10-28-09 02.00.00 Initial version. - * -------------------------------------------------------------------------- - - -mpi2_history.txt Parts list history - -Filename 02.00.14 02.00.13 02.00.12 ----------- -------- -------- -------- -mpi2.h 02.00.14 02.00.13 02.00.12 -mpi2_cnfg.h 02.00.13 02.00.12 02.00.11 -mpi2_init.h 02.00.08 02.00.07 02.00.07 -mpi2_ioc.h 02.00.13 02.00.12 02.00.11 -mpi2_raid.h 02.00.04 02.00.04 02.00.03 -mpi2_sas.h 02.00.03 02.00.02 02.00.02 -mpi2_targ.h 02.00.03 02.00.03 02.00.03 -mpi2_tool.h 02.00.04 02.00.04 02.00.03 -mpi2_type.h 02.00.00 02.00.00 02.00.00 -mpi2_ra.h 02.00.00 02.00.00 02.00.00 -mpi2_hbd.h 02.00.00 - -Filename 02.00.11 02.00.10 02.00.09 02.00.08 02.00.07 02.00.06 ----------- -------- -------- -------- -------- -------- -------- -mpi2.h 02.00.11 02.00.10 02.00.09 02.00.08 02.00.07 02.00.06 -mpi2_cnfg.h 02.00.10 02.00.09 02.00.08 02.00.07 02.00.06 02.00.06 -mpi2_init.h 02.00.06 02.00.06 02.00.05 02.00.05 02.00.04 02.00.03 -mpi2_ioc.h 02.00.10 02.00.09 02.00.08 02.00.07 02.00.07 02.00.06 -mpi2_raid.h 02.00.03 02.00.03 02.00.03 02.00.03 02.00.02 02.00.02 -mpi2_sas.h 02.00.02 02.00.02 02.00.01 02.00.01 02.00.01 02.00.01 -mpi2_targ.h 02.00.03 02.00.03 02.00.02 02.00.02 02.00.02 02.00.02 -mpi2_tool.h 02.00.02 02.00.02 02.00.02 02.00.02 02.00.02 02.00.02 -mpi2_type.h 02.00.00 02.00.00 02.00.00 02.00.00 02.00.00 02.00.00 - -Filename 02.00.05 02.00.04 02.00.03 02.00.02 02.00.01 02.00.00 ----------- -------- -------- -------- -------- -------- -------- -mpi2.h 02.00.05 02.00.04 02.00.03 02.00.02 02.00.01 02.00.00 -mpi2_cnfg.h 02.00.05 02.00.04 02.00.03 02.00.02 02.00.01 02.00.00 -mpi2_init.h 02.00.02 02.00.01 02.00.00 02.00.00 02.00.00 02.00.00 -mpi2_ioc.h 02.00.05 02.00.04 02.00.03 02.00.02 02.00.01 02.00.00 -mpi2_raid.h 02.00.01 02.00.01 02.00.01 02.00.00 02.00.00 02.00.00 -mpi2_sas.h 02.00.01 02.00.01 02.00.01 02.00.01 02.00.00 02.00.00 -mpi2_targ.h 02.00.01 02.00.01 02.00.01 02.00.00 02.00.00 02.00.00 -mpi2_tool.h 02.00.01 02.00.00 02.00.00 02.00.00 02.00.00 02.00.00 -mpi2_type.h 02.00.00 02.00.00 02.00.00 02.00.00 02.00.00 02.00.00 - diff --git a/drivers/scsi/mpt2sas/mpi/mpi2_sas.h b/drivers/scsi/mpt2sas/mpi/mpi2_sas.h index 608f6d6e6fca..fdffde1ebc0f 100644 --- a/drivers/scsi/mpt2sas/mpi/mpi2_sas.h +++ b/drivers/scsi/mpt2sas/mpi/mpi2_sas.h @@ -6,7 +6,7 @@ * Title: MPI Serial Attached SCSI structures and definitions * Creation Date: February 9, 2007 * - * mpi2_sas.h Version: 02.00.04 + * mpi2_sas.h Version: 02.00.05 * * Version History * --------------- @@ -21,6 +21,7 @@ * 10-28-09 02.00.03 Changed the type of SGL in MPI2_SATA_PASSTHROUGH_REQUEST * to MPI2_SGE_IO_UNION since it supports chained SGLs. * 05-12-10 02.00.04 Modified some comments. + * 08-11-10 02.00.05 Added NCQ operations to SAS IO Unit Control. * -------------------------------------------------------------------------- */ @@ -163,7 +164,7 @@ typedef struct _MPI2_SATA_PASSTHROUGH_REQUEST U32 Reserved4; /* 0x14 */ U32 DataLength; /* 0x18 */ U8 CommandFIS[20]; /* 0x1C */ - MPI2_SGE_IO_UNION SGL; /* 0x20 */ + MPI2_SGE_IO_UNION SGL; /* 0x30 */ } MPI2_SATA_PASSTHROUGH_REQUEST, MPI2_POINTER PTR_MPI2_SATA_PASSTHROUGH_REQUEST, Mpi2SataPassthroughRequest_t, MPI2_POINTER pMpi2SataPassthroughRequest_t; @@ -246,6 +247,8 @@ typedef struct _MPI2_SAS_IOUNIT_CONTROL_REQUEST #define MPI2_SAS_OP_REMOVE_DEVICE (0x0D) #define MPI2_SAS_OP_LOOKUP_MAPPING (0x0E) #define MPI2_SAS_OP_SET_IOC_PARAMETER (0x0F) +#define MPI2_SAS_OP_DEV_ENABLE_NCQ (0x14) +#define MPI2_SAS_OP_DEV_DISABLE_NCQ (0x15) #define MPI2_SAS_OP_PRODUCT_SPECIFIC_MIN (0x80) /* values for the PrimFlags field */ diff --git a/drivers/scsi/mpt2sas/mpi/mpi2_tool.h b/drivers/scsi/mpt2sas/mpi/mpi2_tool.h index 5c6e3a67bb94..2a4bceda364b 100644 --- a/drivers/scsi/mpt2sas/mpi/mpi2_tool.h +++ b/drivers/scsi/mpt2sas/mpi/mpi2_tool.h @@ -6,7 +6,7 @@ * Title: MPI diagnostic tool structures and definitions * Creation Date: March 26, 2007 * - * mpi2_tool.h Version: 02.00.05 + * mpi2_tool.h Version: 02.00.06 * * Version History * --------------- @@ -23,6 +23,8 @@ * Added MPI2_DIAG_BUF_TYPE_EXTENDED. * Incremented MPI2_DIAG_BUF_TYPE_COUNT. * 05-12-10 02.00.05 Added Diagnostic Data Upload tool. + * 08-11-10 02.00.06 Added defines that were missing for Diagnostic Buffer + * Post Request. * -------------------------------------------------------------------------- */ @@ -354,6 +356,10 @@ typedef struct _MPI2_DIAG_BUFFER_POST_REQUEST /* count of the number of buffer types */ #define MPI2_DIAG_BUF_TYPE_COUNT (0x03) +/* values for the Flags field */ +#define MPI2_DIAG_BUF_FLAG_RELEASE_ON_FULL (0x00000002) +#define MPI2_DIAG_BUF_FLAG_IMMEDIATE_RELEASE (0x00000001) + /**************************************************************************** * Diagnostic Buffer Post reply diff --git a/drivers/scsi/mpt2sas/mpt2sas_base.c b/drivers/scsi/mpt2sas/mpt2sas_base.c index 9ead0399808a..e8a6f1cf1e4b 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_base.c +++ b/drivers/scsi/mpt2sas/mpt2sas_base.c @@ -752,20 +752,19 @@ static u8 _base_get_cb_idx(struct MPT2SAS_ADAPTER *ioc, u16 smid) { int i; - u8 cb_idx = 0xFF; - - if (smid >= ioc->hi_priority_smid) { - if (smid < ioc->internal_smid) { - i = smid - ioc->hi_priority_smid; - cb_idx = ioc->hpr_lookup[i].cb_idx; - } else if (smid <= ioc->hba_queue_depth) { - i = smid - ioc->internal_smid; - cb_idx = ioc->internal_lookup[i].cb_idx; - } - } else { + u8 cb_idx; + + if (smid < ioc->hi_priority_smid) { i = smid - 1; cb_idx = ioc->scsi_lookup[i].cb_idx; - } + } else if (smid < ioc->internal_smid) { + i = smid - ioc->hi_priority_smid; + cb_idx = ioc->hpr_lookup[i].cb_idx; + } else if (smid <= ioc->hba_queue_depth) { + i = smid - ioc->internal_smid; + cb_idx = ioc->internal_lookup[i].cb_idx; + } else + cb_idx = 0xFF; return cb_idx; } @@ -1430,7 +1429,7 @@ mpt2sas_base_get_smid_scsiio(struct MPT2SAS_ADAPTER *ioc, u8 cb_idx, struct scsi_cmnd *scmd) { unsigned long flags; - struct request_tracker *request; + struct scsiio_tracker *request; u16 smid; spin_lock_irqsave(&ioc->scsi_lookup_lock, flags); @@ -1442,7 +1441,7 @@ mpt2sas_base_get_smid_scsiio(struct MPT2SAS_ADAPTER *ioc, u8 cb_idx, } request = list_entry(ioc->free_list.next, - struct request_tracker, tracker_list); + struct scsiio_tracker, tracker_list); request->scmd = scmd; request->cb_idx = cb_idx; smid = request->smid; @@ -1496,48 +1495,47 @@ mpt2sas_base_free_smid(struct MPT2SAS_ADAPTER *ioc, u16 smid) struct chain_tracker *chain_req, *next; spin_lock_irqsave(&ioc->scsi_lookup_lock, flags); - if (smid >= ioc->hi_priority_smid) { - if (smid < ioc->internal_smid) { - /* hi-priority */ - i = smid - ioc->hi_priority_smid; - ioc->hpr_lookup[i].cb_idx = 0xFF; - list_add_tail(&ioc->hpr_lookup[i].tracker_list, - &ioc->hpr_free_list); - } else { - /* internal queue */ - i = smid - ioc->internal_smid; - ioc->internal_lookup[i].cb_idx = 0xFF; - list_add_tail(&ioc->internal_lookup[i].tracker_list, - &ioc->internal_free_list); + if (smid < ioc->hi_priority_smid) { + /* scsiio queue */ + i = smid - 1; + if (!list_empty(&ioc->scsi_lookup[i].chain_list)) { + list_for_each_entry_safe(chain_req, next, + &ioc->scsi_lookup[i].chain_list, tracker_list) { + list_del_init(&chain_req->tracker_list); + list_add_tail(&chain_req->tracker_list, + &ioc->free_chain_list); + } } + ioc->scsi_lookup[i].cb_idx = 0xFF; + ioc->scsi_lookup[i].scmd = NULL; + list_add_tail(&ioc->scsi_lookup[i].tracker_list, + &ioc->free_list); spin_unlock_irqrestore(&ioc->scsi_lookup_lock, flags); - return; - } - /* scsiio queue */ - i = smid - 1; - if (!list_empty(&ioc->scsi_lookup[i].chain_list)) { - list_for_each_entry_safe(chain_req, next, - &ioc->scsi_lookup[i].chain_list, tracker_list) { - list_del_init(&chain_req->tracker_list); - list_add_tail(&chain_req->tracker_list, - &ioc->free_chain_list); + /* + * See _wait_for_commands_to_complete() call with regards + * to this code. + */ + if (ioc->shost_recovery && ioc->pending_io_count) { + if (ioc->pending_io_count == 1) + wake_up(&ioc->reset_wq); + ioc->pending_io_count--; } + return; + } else if (smid < ioc->internal_smid) { + /* hi-priority */ + i = smid - ioc->hi_priority_smid; + ioc->hpr_lookup[i].cb_idx = 0xFF; + list_add_tail(&ioc->hpr_lookup[i].tracker_list, + &ioc->hpr_free_list); + } else if (smid <= ioc->hba_queue_depth) { + /* internal queue */ + i = smid - ioc->internal_smid; + ioc->internal_lookup[i].cb_idx = 0xFF; + list_add_tail(&ioc->internal_lookup[i].tracker_list, + &ioc->internal_free_list); } - ioc->scsi_lookup[i].cb_idx = 0xFF; - ioc->scsi_lookup[i].scmd = NULL; - list_add_tail(&ioc->scsi_lookup[i].tracker_list, - &ioc->free_list); spin_unlock_irqrestore(&ioc->scsi_lookup_lock, flags); - - /* - * See _wait_for_commands_to_complete() call with regards to this code. - */ - if (ioc->shost_recovery && ioc->pending_io_count) { - if (ioc->pending_io_count == 1) - wake_up(&ioc->reset_wq); - ioc->pending_io_count--; - } } /** @@ -1725,6 +1723,31 @@ _base_display_dell_branding(struct MPT2SAS_ADAPTER *ioc) } /** + * _base_display_intel_branding - Display branding string + * @ioc: per adapter object + * + * Return nothing. + */ +static void +_base_display_intel_branding(struct MPT2SAS_ADAPTER *ioc) +{ + if (ioc->pdev->subsystem_vendor == PCI_VENDOR_ID_INTEL && + ioc->pdev->device == MPI2_MFGPAGE_DEVID_SAS2008) { + + switch (ioc->pdev->subsystem_device) { + case MPT2SAS_INTEL_RMS2LL080_SSDID: + printk(MPT2SAS_INFO_FMT "%s\n", ioc->name, + MPT2SAS_INTEL_RMS2LL080_BRANDING); + break; + case MPT2SAS_INTEL_RMS2LL040_SSDID: + printk(MPT2SAS_INFO_FMT "%s\n", ioc->name, + MPT2SAS_INTEL_RMS2LL040_BRANDING); + break; + } + } +} + +/** * _base_display_ioc_capabilities - Disply IOC's capabilities. * @ioc: per adapter object * @@ -1754,6 +1777,7 @@ _base_display_ioc_capabilities(struct MPT2SAS_ADAPTER *ioc) ioc->bios_pg3.BiosVersion & 0x000000FF); _base_display_dell_branding(ioc); + _base_display_intel_branding(ioc); printk(MPT2SAS_INFO_FMT "Protocol=(", ioc->name); @@ -2252,9 +2276,9 @@ _base_allocate_memory_pools(struct MPT2SAS_ADAPTER *ioc, int sleep_flag) ioc->name, (unsigned long long) ioc->request_dma)); total_sz += sz; - sz = ioc->scsiio_depth * sizeof(struct request_tracker); + sz = ioc->scsiio_depth * sizeof(struct scsiio_tracker); ioc->scsi_lookup_pages = get_order(sz); - ioc->scsi_lookup = (struct request_tracker *)__get_free_pages( + ioc->scsi_lookup = (struct scsiio_tracker *)__get_free_pages( GFP_KERNEL, ioc->scsi_lookup_pages); if (!ioc->scsi_lookup) { printk(MPT2SAS_ERR_FMT "scsi_lookup: get_free_pages failed, " diff --git a/drivers/scsi/mpt2sas/mpt2sas_base.h b/drivers/scsi/mpt2sas/mpt2sas_base.h index 283568c6fb04..a3f8aa9baea4 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_base.h +++ b/drivers/scsi/mpt2sas/mpt2sas_base.h @@ -69,8 +69,8 @@ #define MPT2SAS_DRIVER_NAME "mpt2sas" #define MPT2SAS_AUTHOR "LSI Corporation <DL-MPTFusionLinux@lsi.com>" #define MPT2SAS_DESCRIPTION "LSI MPT Fusion SAS 2.0 Device Driver" -#define MPT2SAS_DRIVER_VERSION "07.100.00.00" -#define MPT2SAS_MAJOR_VERSION 07 +#define MPT2SAS_DRIVER_VERSION "08.100.00.00" +#define MPT2SAS_MAJOR_VERSION 08 #define MPT2SAS_MINOR_VERSION 100 #define MPT2SAS_BUILD_VERSION 00 #define MPT2SAS_RELEASE_VERSION 00 @@ -101,7 +101,8 @@ #define MPT_NAME_LENGTH 32 /* generic length of strings */ #define MPT_STRING_LENGTH 64 -#define MPT_MAX_CALLBACKS 16 +#define MPT_MAX_CALLBACKS 16 + #define CAN_SLEEP 1 #define NO_SLEEP 0 @@ -154,6 +155,20 @@ #define MPT2SAS_DELL_6GBPS_SAS_SSDID 0x1F22 /* + * Intel HBA branding + */ +#define MPT2SAS_INTEL_RMS2LL080_BRANDING \ + "Intel Integrated RAID Module RMS2LL080" +#define MPT2SAS_INTEL_RMS2LL040_BRANDING \ + "Intel Integrated RAID Module RMS2LL040" + +/* + * Intel HBA SSDIDs + */ +#define MPT2SAS_INTEL_RMS2LL080_SSDID 0x350E +#define MPT2SAS_INTEL_RMS2LL040_SSDID 0x350F + +/* * per target private data */ #define MPT_TARGET_FLAGS_RAID_COMPONENT 0x01 @@ -431,14 +446,14 @@ struct chain_tracker { }; /** - * struct request_tracker - firmware request tracker + * struct scsiio_tracker - scsi mf request tracker * @smid: system message id * @scmd: scsi request pointer * @cb_idx: callback index * @chain_list: list of chains associated to this IO * @tracker_list: list of free request (ioc->free_list) */ -struct request_tracker { +struct scsiio_tracker { u16 smid; struct scsi_cmnd *scmd; u8 cb_idx; @@ -447,6 +462,19 @@ struct request_tracker { }; /** + * struct request_tracker - misc mf request tracker + * @smid: system message id + * @scmd: scsi request pointer + * @cb_idx: callback index + * @tracker_list: list of free request (ioc->free_list) + */ +struct request_tracker { + u16 smid; + u8 cb_idx; + struct list_head tracker_list; +}; + +/** * struct _tr_list - target reset list * @handle: device handle * @state: state machine @@ -709,7 +737,7 @@ struct MPT2SAS_ADAPTER { u8 *request; dma_addr_t request_dma; u32 request_dma_sz; - struct request_tracker *scsi_lookup; + struct scsiio_tracker *scsi_lookup; ulong scsi_lookup_pages; spinlock_t scsi_lookup_lock; struct list_head free_list; diff --git a/drivers/scsi/mpt2sas/mpt2sas_scsih.c b/drivers/scsi/mpt2sas/mpt2sas_scsih.c index 5ded3db6e316..6ceb7759bfe5 100644 --- a/drivers/scsi/mpt2sas/mpt2sas_scsih.c +++ b/drivers/scsi/mpt2sas/mpt2sas_scsih.c @@ -6975,7 +6975,6 @@ _scsih_suspend(struct pci_dev *pdev, pm_message_t state) u32 device_state; mpt2sas_base_stop_watchdog(ioc); - flush_scheduled_work(); scsi_block_requests(shost); device_state = pci_choose_state(pdev, state); printk(MPT2SAS_INFO_FMT "pdev=0x%p, slot=%s, entering " diff --git a/drivers/scsi/osd/osd_initiator.c b/drivers/scsi/osd/osd_initiator.c index b37c8a3c1bb0..86afb13f1e79 100644 --- a/drivers/scsi/osd/osd_initiator.c +++ b/drivers/scsi/osd/osd_initiator.c @@ -1005,11 +1005,23 @@ int osd_req_read_sg(struct osd_request *or, const struct osd_sg_entry *sglist, unsigned numentries) { u64 len; - int ret = _add_sg_continuation_descriptor(or, sglist, numentries, &len); + u64 off; + int ret; - if (ret) - return ret; - osd_req_read(or, obj, 0, bio, len); + if (numentries > 1) { + off = 0; + ret = _add_sg_continuation_descriptor(or, sglist, numentries, + &len); + if (ret) + return ret; + } else { + /* Optimize the case of single segment, read_sg is a + * bidi operation. + */ + len = sglist->len; + off = sglist->offset; + } + osd_req_read(or, obj, off, bio, len); return 0; } diff --git a/drivers/scsi/pm8001/pm8001_hwi.c b/drivers/scsi/pm8001/pm8001_hwi.c index d8db0137c0c7..18b6c55cd08c 100644 --- a/drivers/scsi/pm8001/pm8001_hwi.c +++ b/drivers/scsi/pm8001/pm8001_hwi.c @@ -1382,53 +1382,50 @@ static u32 mpi_msg_consume(struct pm8001_hba_info *pm8001_ha, return MPI_IO_STATUS_BUSY; } -static void pm8001_work_queue(struct work_struct *work) +static void pm8001_work_fn(struct work_struct *work) { - struct delayed_work *dw = container_of(work, struct delayed_work, work); - struct pm8001_wq *wq = container_of(dw, struct pm8001_wq, work_q); + struct pm8001_work *pw = container_of(work, struct pm8001_work, work); struct pm8001_device *pm8001_dev; - struct domain_device *dev; + struct domain_device *dev; - switch (wq->handler) { + switch (pw->handler) { case IO_OPEN_CNX_ERROR_IT_NEXUS_LOSS: - pm8001_dev = wq->data; + pm8001_dev = pw->data; dev = pm8001_dev->sas_device; pm8001_I_T_nexus_reset(dev); break; case IO_OPEN_CNX_ERROR_STP_RESOURCES_BUSY: - pm8001_dev = wq->data; + pm8001_dev = pw->data; dev = pm8001_dev->sas_device; pm8001_I_T_nexus_reset(dev); break; case IO_DS_IN_ERROR: - pm8001_dev = wq->data; + pm8001_dev = pw->data; dev = pm8001_dev->sas_device; pm8001_I_T_nexus_reset(dev); break; case IO_DS_NON_OPERATIONAL: - pm8001_dev = wq->data; + pm8001_dev = pw->data; dev = pm8001_dev->sas_device; pm8001_I_T_nexus_reset(dev); break; } - list_del(&wq->entry); - kfree(wq); + kfree(pw); } static int pm8001_handle_event(struct pm8001_hba_info *pm8001_ha, void *data, int handler) { - struct pm8001_wq *wq; + struct pm8001_work *pw; int ret = 0; - wq = kmalloc(sizeof(struct pm8001_wq), GFP_ATOMIC); - if (wq) { - wq->pm8001_ha = pm8001_ha; - wq->data = data; - wq->handler = handler; - INIT_DELAYED_WORK(&wq->work_q, pm8001_work_queue); - list_add_tail(&wq->entry, &pm8001_ha->wq_list); - schedule_delayed_work(&wq->work_q, 0); + pw = kmalloc(sizeof(struct pm8001_work), GFP_ATOMIC); + if (pw) { + pw->pm8001_ha = pm8001_ha; + pw->data = data; + pw->handler = handler; + INIT_WORK(&pw->work, pm8001_work_fn); + queue_work(pm8001_wq, &pw->work); } else ret = -ENOMEM; diff --git a/drivers/scsi/pm8001/pm8001_init.c b/drivers/scsi/pm8001/pm8001_init.c index b95285f3383f..002360da01e3 100644 --- a/drivers/scsi/pm8001/pm8001_init.c +++ b/drivers/scsi/pm8001/pm8001_init.c @@ -51,6 +51,8 @@ static int pm8001_id; LIST_HEAD(hba_list); +struct workqueue_struct *pm8001_wq; + /** * The main structure which LLDD must register for scsi core. */ @@ -134,7 +136,6 @@ static void __devinit pm8001_phy_init(struct pm8001_hba_info *pm8001_ha, static void pm8001_free(struct pm8001_hba_info *pm8001_ha) { int i; - struct pm8001_wq *wq; if (!pm8001_ha) return; @@ -150,8 +151,7 @@ static void pm8001_free(struct pm8001_hba_info *pm8001_ha) PM8001_CHIP_DISP->chip_iounmap(pm8001_ha); if (pm8001_ha->shost) scsi_host_put(pm8001_ha->shost); - list_for_each_entry(wq, &pm8001_ha->wq_list, entry) - cancel_delayed_work(&wq->work_q); + flush_workqueue(pm8001_wq); kfree(pm8001_ha->tags); kfree(pm8001_ha); } @@ -381,7 +381,6 @@ pm8001_pci_alloc(struct pci_dev *pdev, u32 chip_id, struct Scsi_Host *shost) pm8001_ha->sas = sha; pm8001_ha->shost = shost; pm8001_ha->id = pm8001_id++; - INIT_LIST_HEAD(&pm8001_ha->wq_list); pm8001_ha->logging_level = 0x01; sprintf(pm8001_ha->name, "%s%d", DRV_NAME, pm8001_ha->id); #ifdef PM8001_USE_TASKLET @@ -758,7 +757,7 @@ static int pm8001_pci_suspend(struct pci_dev *pdev, pm_message_t state) int i , pos; u32 device_state; pm8001_ha = sha->lldd_ha; - flush_scheduled_work(); + flush_workqueue(pm8001_wq); scsi_block_requests(pm8001_ha->shost); pos = pci_find_capability(pdev, PCI_CAP_ID_PM); if (pos == 0) { @@ -870,17 +869,26 @@ static struct pci_driver pm8001_pci_driver = { */ static int __init pm8001_init(void) { - int rc; + int rc = -ENOMEM; + + pm8001_wq = alloc_workqueue("pm8001", 0, 0); + if (!pm8001_wq) + goto err; + pm8001_id = 0; pm8001_stt = sas_domain_attach_transport(&pm8001_transport_ops); if (!pm8001_stt) - return -ENOMEM; + goto err_wq; rc = pci_register_driver(&pm8001_pci_driver); if (rc) - goto err_out; + goto err_tp; return 0; -err_out: + +err_tp: sas_release_transport(pm8001_stt); +err_wq: + destroy_workqueue(pm8001_wq); +err: return rc; } @@ -888,6 +896,7 @@ static void __exit pm8001_exit(void) { pci_unregister_driver(&pm8001_pci_driver); sas_release_transport(pm8001_stt); + destroy_workqueue(pm8001_wq); } module_init(pm8001_init); diff --git a/drivers/scsi/pm8001/pm8001_sas.h b/drivers/scsi/pm8001/pm8001_sas.h index 7f064f9ca828..bdb6b27dedd6 100644 --- a/drivers/scsi/pm8001/pm8001_sas.h +++ b/drivers/scsi/pm8001/pm8001_sas.h @@ -50,6 +50,7 @@ #include <linux/dma-mapping.h> #include <linux/pci.h> #include <linux/interrupt.h> +#include <linux/workqueue.h> #include <scsi/libsas.h> #include <scsi/scsi_tcq.h> #include <scsi/sas_ata.h> @@ -379,18 +380,16 @@ struct pm8001_hba_info { #ifdef PM8001_USE_TASKLET struct tasklet_struct tasklet; #endif - struct list_head wq_list; u32 logging_level; u32 fw_status; const struct firmware *fw_image; }; -struct pm8001_wq { - struct delayed_work work_q; +struct pm8001_work { + struct work_struct work; struct pm8001_hba_info *pm8001_ha; void *data; int handler; - struct list_head entry; }; struct pm8001_fw_image_header { @@ -460,6 +459,9 @@ struct fw_control_ex { void *param3; }; +/* pm8001 workqueue */ +extern struct workqueue_struct *pm8001_wq; + /******************** function prototype *********************/ int pm8001_tag_alloc(struct pm8001_hba_info *pm8001_ha, u32 *tag_out); void pm8001_tag_init(struct pm8001_hba_info *pm8001_ha); diff --git a/drivers/scsi/pmcraid.c b/drivers/scsi/pmcraid.c index 321cf3ae8630..bcf858e88c64 100644 --- a/drivers/scsi/pmcraid.c +++ b/drivers/scsi/pmcraid.c @@ -5454,7 +5454,7 @@ static void __devexit pmcraid_remove(struct pci_dev *pdev) pmcraid_shutdown(pdev); pmcraid_disable_interrupts(pinstance, ~0); - flush_scheduled_work(); + flush_work_sync(&pinstance->worker_q); pmcraid_kill_tasklets(pinstance); pmcraid_unregister_interrupt_handler(pinstance); diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h index ccfc8e78be21..6c51c0a35b9e 100644 --- a/drivers/scsi/qla2xxx/qla_def.h +++ b/drivers/scsi/qla2xxx/qla_def.h @@ -2402,13 +2402,13 @@ struct qla_hw_data { volatile struct { uint32_t mbox_int :1; uint32_t mbox_busy :1; - uint32_t disable_risc_code_load :1; uint32_t enable_64bit_addressing :1; uint32_t enable_lip_reset :1; uint32_t enable_target_reset :1; uint32_t enable_lip_full_login :1; uint32_t enable_led_scheme :1; + uint32_t msi_enabled :1; uint32_t msix_enabled :1; uint32_t disable_serdes :1; @@ -2417,6 +2417,7 @@ struct qla_hw_data { uint32_t pci_channel_io_perm_failure :1; uint32_t fce_enabled :1; uint32_t fac_supported :1; + uint32_t chip_reset_done :1; uint32_t port0 :1; uint32_t running_gold_fw :1; @@ -2424,9 +2425,11 @@ struct qla_hw_data { uint32_t cpu_affinity_enabled :1; uint32_t disable_msix_handshake :1; uint32_t fcp_prio_enabled :1; - uint32_t fw_hung :1; - uint32_t quiesce_owner:1; + uint32_t isp82xx_fw_hung:1; + + uint32_t quiesce_owner:1; uint32_t thermal_supported:1; + uint32_t isp82xx_reset_hdlr_active:1; /* 26 bits */ } flags; diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/qla_gbl.h index 89e900adb679..d48326ee3f61 100644 --- a/drivers/scsi/qla2xxx/qla_gbl.h +++ b/drivers/scsi/qla2xxx/qla_gbl.h @@ -565,6 +565,7 @@ extern int qla82xx_mbx_intr_enable(scsi_qla_host_t *); extern int qla82xx_mbx_intr_disable(scsi_qla_host_t *); extern void qla82xx_start_iocbs(srb_t *); extern int qla82xx_fcoe_ctx_reset(scsi_qla_host_t *); +extern void qla82xx_chip_reset_cleanup(scsi_qla_host_t *); /* BSG related functions */ extern int qla24xx_bsg_request(struct fc_bsg_job *); diff --git a/drivers/scsi/qla2xxx/qla_gs.c b/drivers/scsi/qla2xxx/qla_gs.c index 4c083928c2fb..74a91b6dfc68 100644 --- a/drivers/scsi/qla2xxx/qla_gs.c +++ b/drivers/scsi/qla2xxx/qla_gs.c @@ -121,8 +121,11 @@ qla2x00_chk_ms_status(scsi_qla_host_t *vha, ms_iocb_entry_t *ms_pkt, rval = QLA_FUNCTION_FAILED; if (ms_pkt->entry_status != 0) { - DEBUG2_3(printk("scsi(%ld): %s failed, error status (%x).\n", - vha->host_no, routine, ms_pkt->entry_status)); + DEBUG2_3(printk(KERN_WARNING "scsi(%ld): %s failed, error status " + "(%x) on port_id: %02x%02x%02x.\n", + vha->host_no, routine, ms_pkt->entry_status, + vha->d_id.b.domain, vha->d_id.b.area, + vha->d_id.b.al_pa)); } else { if (IS_FWI2_CAPABLE(ha)) comp_status = le16_to_cpu( @@ -136,8 +139,10 @@ qla2x00_chk_ms_status(scsi_qla_host_t *vha, ms_iocb_entry_t *ms_pkt, if (ct_rsp->header.response != __constant_cpu_to_be16(CT_ACCEPT_RESPONSE)) { DEBUG2_3(printk("scsi(%ld): %s failed, " - "rejected request:\n", vha->host_no, - routine)); + "rejected request on port_id: %02x%02x%02x\n", + vha->host_no, routine, + vha->d_id.b.domain, vha->d_id.b.area, + vha->d_id.b.al_pa)); DEBUG2_3(qla2x00_dump_buffer( (uint8_t *)&ct_rsp->header, sizeof(struct ct_rsp_hdr))); @@ -147,8 +152,10 @@ qla2x00_chk_ms_status(scsi_qla_host_t *vha, ms_iocb_entry_t *ms_pkt, break; default: DEBUG2_3(printk("scsi(%ld): %s failed, completion " - "status (%x).\n", vha->host_no, routine, - comp_status)); + "status (%x) on port_id: %02x%02x%02x.\n", + vha->host_no, routine, comp_status, + vha->d_id.b.domain, vha->d_id.b.area, + vha->d_id.b.al_pa)); break; } } @@ -1965,7 +1972,7 @@ qla2x00_gff_id(scsi_qla_host_t *vha, sw_info_t *list) "scsi(%ld): GFF_ID issue IOCB failed " "(%d).\n", vha->host_no, rval)); } else if (qla2x00_chk_ms_status(vha, ms_pkt, ct_rsp, - "GPN_ID") != QLA_SUCCESS) { + "GFF_ID") != QLA_SUCCESS) { DEBUG2_3(printk(KERN_INFO "scsi(%ld): GFF_ID IOCB status had a " "failure status code\n", vha->host_no)); diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index d9479c3fe5f8..8575808dbae0 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -1967,7 +1967,7 @@ qla2x00_fw_ready(scsi_qla_host_t *vha) } else { /* Mailbox cmd failed. Timeout on min_wait. */ if (time_after_eq(jiffies, mtime) || - (IS_QLA82XX(ha) && ha->flags.fw_hung)) + ha->flags.isp82xx_fw_hung) break; } @@ -3945,8 +3945,13 @@ qla2x00_abort_isp_cleanup(scsi_qla_host_t *vha) struct qla_hw_data *ha = vha->hw; struct scsi_qla_host *vp; unsigned long flags; + fc_port_t *fcport; - vha->flags.online = 0; + /* For ISP82XX, driver waits for completion of the commands. + * online flag should be set. + */ + if (!IS_QLA82XX(ha)) + vha->flags.online = 0; ha->flags.chip_reset_done = 0; clear_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); ha->qla_stats.total_isp_aborts++; @@ -3954,7 +3959,10 @@ qla2x00_abort_isp_cleanup(scsi_qla_host_t *vha) qla_printk(KERN_INFO, ha, "Performing ISP error recovery - ha= %p.\n", ha); - /* Chip reset does not apply to 82XX */ + /* For ISP82XX, reset_chip is just disabling interrupts. + * Driver waits for the completion of the commands. + * the interrupts need to be enabled. + */ if (!IS_QLA82XX(ha)) ha->isp_ops->reset_chip(vha); @@ -3980,14 +3988,31 @@ qla2x00_abort_isp_cleanup(scsi_qla_host_t *vha) LOOP_DOWN_TIME); } + /* Clear all async request states across all VPs. */ + list_for_each_entry(fcport, &vha->vp_fcports, list) + fcport->flags &= ~(FCF_LOGIN_NEEDED | FCF_ASYNC_SENT); + spin_lock_irqsave(&ha->vport_slock, flags); + list_for_each_entry(vp, &ha->vp_list, list) { + atomic_inc(&vp->vref_count); + spin_unlock_irqrestore(&ha->vport_slock, flags); + + list_for_each_entry(fcport, &vp->vp_fcports, list) + fcport->flags &= ~(FCF_LOGIN_NEEDED | FCF_ASYNC_SENT); + + spin_lock_irqsave(&ha->vport_slock, flags); + atomic_dec(&vp->vref_count); + } + spin_unlock_irqrestore(&ha->vport_slock, flags); + if (!ha->flags.eeh_busy) { /* Make sure for ISP 82XX IO DMA is complete */ if (IS_QLA82XX(ha)) { - if (qla2x00_eh_wait_for_pending_commands(vha, 0, 0, - WAIT_HOST) == QLA_SUCCESS) { - DEBUG2(qla_printk(KERN_INFO, ha, - "Done wait for pending commands\n")); - } + qla82xx_chip_reset_cleanup(vha); + + /* Done waiting for pending commands. + * Reset the online flag. + */ + vha->flags.online = 0; } /* Requeue all commands in outstanding command list. */ diff --git a/drivers/scsi/qla2xxx/qla_iocb.c b/drivers/scsi/qla2xxx/qla_iocb.c index 4c1ba6263eb3..d78d5896fc33 100644 --- a/drivers/scsi/qla2xxx/qla_iocb.c +++ b/drivers/scsi/qla2xxx/qla_iocb.c @@ -328,6 +328,7 @@ qla2x00_start_scsi(srb_t *sp) struct qla_hw_data *ha; struct req_que *req; struct rsp_que *rsp; + char tag[2]; /* Setup device pointers. */ ret = 0; @@ -406,7 +407,22 @@ qla2x00_start_scsi(srb_t *sp) cmd_pkt->lun = cpu_to_le16(sp->cmd->device->lun); /* Update tagged queuing modifier */ - cmd_pkt->control_flags = __constant_cpu_to_le16(CF_SIMPLE_TAG); + if (scsi_populate_tag_msg(cmd, tag)) { + switch (tag[0]) { + case HEAD_OF_QUEUE_TAG: + cmd_pkt->control_flags = + __constant_cpu_to_le16(CF_HEAD_TAG); + break; + case ORDERED_QUEUE_TAG: + cmd_pkt->control_flags = + __constant_cpu_to_le16(CF_ORDERED_TAG); + break; + default: + cmd_pkt->control_flags = + __constant_cpu_to_le16(CF_SIMPLE_TAG); + break; + } + } /* Load SCSI command packet. */ memcpy(cmd_pkt->scsi_cdb, cmd->cmnd, cmd->cmd_len); @@ -971,6 +987,7 @@ qla24xx_build_scsi_crc_2_iocbs(srb_t *sp, struct cmd_type_crc_2 *cmd_pkt, uint16_t fcp_cmnd_len; struct fcp_cmnd *fcp_cmnd; dma_addr_t crc_ctx_dma; + char tag[2]; cmd = sp->cmd; @@ -1068,9 +1085,27 @@ qla24xx_build_scsi_crc_2_iocbs(srb_t *sp, struct cmd_type_crc_2 *cmd_pkt, LSD(crc_ctx_dma + CRC_CONTEXT_FCPCMND_OFF)); cmd_pkt->fcp_cmnd_dseg_address[1] = cpu_to_le32( MSD(crc_ctx_dma + CRC_CONTEXT_FCPCMND_OFF)); - fcp_cmnd->task_attribute = 0; fcp_cmnd->task_management = 0; + /* + * Update tagged queuing modifier if using command tag queuing + */ + if (scsi_populate_tag_msg(cmd, tag)) { + switch (tag[0]) { + case HEAD_OF_QUEUE_TAG: + fcp_cmnd->task_attribute = TSK_HEAD_OF_QUEUE; + break; + case ORDERED_QUEUE_TAG: + fcp_cmnd->task_attribute = TSK_ORDERED; + break; + default: + fcp_cmnd->task_attribute = 0; + break; + } + } else { + fcp_cmnd->task_attribute = 0; + } + cmd_pkt->fcp_rsp_dseg_len = 0; /* Let response come in status iocb */ DEBUG18(printk(KERN_INFO "%s(%ld): Total SG(s) Entries %d, Data" @@ -1177,6 +1212,7 @@ qla24xx_start_scsi(srb_t *sp) struct scsi_cmnd *cmd = sp->cmd; struct scsi_qla_host *vha = sp->fcport->vha; struct qla_hw_data *ha = vha->hw; + char tag[2]; /* Setup device pointers. */ ret = 0; @@ -1260,6 +1296,18 @@ qla24xx_start_scsi(srb_t *sp) int_to_scsilun(sp->cmd->device->lun, &cmd_pkt->lun); host_to_fcp_swap((uint8_t *)&cmd_pkt->lun, sizeof(cmd_pkt->lun)); + /* Update tagged queuing modifier -- default is TSK_SIMPLE (0). */ + if (scsi_populate_tag_msg(cmd, tag)) { + switch (tag[0]) { + case HEAD_OF_QUEUE_TAG: + cmd_pkt->task = TSK_HEAD_OF_QUEUE; + break; + case ORDERED_QUEUE_TAG: + cmd_pkt->task = TSK_ORDERED; + break; + } + } + /* Load SCSI command packet. */ memcpy(cmd_pkt->fcp_cdb, cmd->cmnd, cmd->cmd_len); host_to_fcp_swap(cmd_pkt->fcp_cdb, sizeof(cmd_pkt->fcp_cdb)); diff --git a/drivers/scsi/qla2xxx/qla_mbx.c b/drivers/scsi/qla2xxx/qla_mbx.c index e473e9fb363c..7a7c0ecfe7dd 100644 --- a/drivers/scsi/qla2xxx/qla_mbx.c +++ b/drivers/scsi/qla2xxx/qla_mbx.c @@ -71,6 +71,13 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) return QLA_FUNCTION_TIMEOUT; } + if (ha->flags.isp82xx_fw_hung) { + /* Setting Link-Down error */ + mcp->mb[0] = MBS_LINK_DOWN_ERROR; + rval = QLA_FUNCTION_FAILED; + goto premature_exit; + } + /* * Wait for active mailbox commands to finish by waiting at most tov * seconds. This is to serialize actual issuing of mailbox cmds during @@ -83,13 +90,6 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) return QLA_FUNCTION_TIMEOUT; } - if (IS_QLA82XX(ha) && ha->flags.fw_hung) { - /* Setting Link-Down error */ - mcp->mb[0] = MBS_LINK_DOWN_ERROR; - rval = QLA_FUNCTION_FAILED; - goto premature_exit; - } - ha->flags.mbox_busy = 1; /* Save mailbox command for debug */ ha->mcp = mcp; @@ -223,7 +223,7 @@ qla2x00_mailbox_command(scsi_qla_host_t *vha, mbx_cmd_t *mcp) ha->flags.mbox_int = 0; clear_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); - if (IS_QLA82XX(ha) && ha->flags.fw_hung) { + if (ha->flags.isp82xx_fw_hung) { ha->flags.mbox_busy = 0; /* Setting Link-Down error */ mcp->mb[0] = MBS_LINK_DOWN_ERROR; @@ -2462,22 +2462,19 @@ __qla24xx_issue_tmf(char *name, uint32_t type, struct fc_port *fcport, "-- completion status (%x).\n", __func__, vha->host_no, le16_to_cpu(sts->comp_status))); rval = QLA_FUNCTION_FAILED; - } else if (!(le16_to_cpu(sts->scsi_status) & - SS_RESPONSE_INFO_LEN_VALID)) { - DEBUG2_3_11(printk("%s(%ld): failed to complete IOCB " - "-- no response info (%x).\n", __func__, vha->host_no, - le16_to_cpu(sts->scsi_status))); - rval = QLA_FUNCTION_FAILED; - } else if (le32_to_cpu(sts->rsp_data_len) < 4) { - DEBUG2_3_11(printk("%s(%ld): failed to complete IOCB " - "-- not enough response info (%d).\n", __func__, - vha->host_no, le32_to_cpu(sts->rsp_data_len))); - rval = QLA_FUNCTION_FAILED; - } else if (sts->data[3]) { - DEBUG2_3_11(printk("%s(%ld): failed to complete IOCB " - "-- response (%x).\n", __func__, - vha->host_no, sts->data[3])); - rval = QLA_FUNCTION_FAILED; + } else if (le16_to_cpu(sts->scsi_status) & + SS_RESPONSE_INFO_LEN_VALID) { + if (le32_to_cpu(sts->rsp_data_len) < 4) { + DEBUG2_3_11(printk("%s(%ld): ignoring inconsistent " + "data length -- not enough response info (%d).\n", + __func__, vha->host_no, + le32_to_cpu(sts->rsp_data_len))); + } else if (sts->data[3]) { + DEBUG2_3_11(printk("%s(%ld): failed to complete IOCB " + "-- response (%x).\n", __func__, + vha->host_no, sts->data[3])); + rval = QLA_FUNCTION_FAILED; + } } /* Issue marker IOCB. */ diff --git a/drivers/scsi/qla2xxx/qla_nx.c b/drivers/scsi/qla2xxx/qla_nx.c index fdb96a3584a5..76ec876e6b21 100644 --- a/drivers/scsi/qla2xxx/qla_nx.c +++ b/drivers/scsi/qla2xxx/qla_nx.c @@ -7,6 +7,7 @@ #include "qla_def.h" #include <linux/delay.h> #include <linux/pci.h> +#include <scsi/scsi_tcq.h> #define MASK(n) ((1ULL<<(n))-1) #define MN_WIN(addr) (((addr & 0x1fc0000) >> 1) | \ @@ -2547,7 +2548,7 @@ qla2xx_build_scsi_type_6_iocbs(srb_t *sp, struct cmd_type_6 *cmd_pkt, dsd_seg = (uint32_t *)&cmd_pkt->fcp_data_dseg_address; *dsd_seg++ = cpu_to_le32(LSD(dsd_ptr->dsd_list_dma)); *dsd_seg++ = cpu_to_le32(MSD(dsd_ptr->dsd_list_dma)); - *dsd_seg++ = dsd_list_len; + cmd_pkt->fcp_data_dseg_len = dsd_list_len; } else { *cur_dsd++ = cpu_to_le32(LSD(dsd_ptr->dsd_list_dma)); *cur_dsd++ = cpu_to_le32(MSD(dsd_ptr->dsd_list_dma)); @@ -2620,6 +2621,7 @@ qla82xx_start_scsi(srb_t *sp) struct qla_hw_data *ha = vha->hw; struct req_que *req = NULL; struct rsp_que *rsp = NULL; + char tag[2]; /* Setup device pointers. */ ret = 0; @@ -2770,6 +2772,22 @@ sufficient_dsds: int_to_scsilun(sp->cmd->device->lun, &cmd_pkt->lun); host_to_fcp_swap((uint8_t *)&cmd_pkt->lun, sizeof(cmd_pkt->lun)); + /* + * Update tagged queuing modifier -- default is TSK_SIMPLE (0). + */ + if (scsi_populate_tag_msg(cmd, tag)) { + switch (tag[0]) { + case HEAD_OF_QUEUE_TAG: + ctx->fcp_cmnd->task_attribute = + TSK_HEAD_OF_QUEUE; + break; + case ORDERED_QUEUE_TAG: + ctx->fcp_cmnd->task_attribute = + TSK_ORDERED; + break; + } + } + /* build FCP_CMND IU */ memset(ctx->fcp_cmnd, 0, sizeof(struct fcp_cmnd)); int_to_scsilun(sp->cmd->device->lun, &ctx->fcp_cmnd->lun); @@ -2835,6 +2853,20 @@ sufficient_dsds: host_to_fcp_swap((uint8_t *)&cmd_pkt->lun, sizeof(cmd_pkt->lun)); + /* + * Update tagged queuing modifier -- default is TSK_SIMPLE (0). + */ + if (scsi_populate_tag_msg(cmd, tag)) { + switch (tag[0]) { + case HEAD_OF_QUEUE_TAG: + cmd_pkt->task = TSK_HEAD_OF_QUEUE; + break; + case ORDERED_QUEUE_TAG: + cmd_pkt->task = TSK_ORDERED; + break; + } + } + /* Load SCSI command packet. */ memcpy(cmd_pkt->fcp_cdb, cmd->cmnd, cmd->cmd_len); host_to_fcp_swap(cmd_pkt->fcp_cdb, sizeof(cmd_pkt->fcp_cdb)); @@ -3457,46 +3489,28 @@ qla82xx_need_reset_handler(scsi_qla_host_t *vha) } } -static void +int qla82xx_check_fw_alive(scsi_qla_host_t *vha) { - uint32_t fw_heartbeat_counter, halt_status; - struct qla_hw_data *ha = vha->hw; + uint32_t fw_heartbeat_counter; + int status = 0; - fw_heartbeat_counter = qla82xx_rd_32(ha, QLA82XX_PEG_ALIVE_COUNTER); + fw_heartbeat_counter = qla82xx_rd_32(vha->hw, + QLA82XX_PEG_ALIVE_COUNTER); /* all 0xff, assume AER/EEH in progress, ignore */ if (fw_heartbeat_counter == 0xffffffff) - return; + return status; if (vha->fw_heartbeat_counter == fw_heartbeat_counter) { vha->seconds_since_last_heartbeat++; /* FW not alive after 2 seconds */ if (vha->seconds_since_last_heartbeat == 2) { vha->seconds_since_last_heartbeat = 0; - halt_status = qla82xx_rd_32(ha, - QLA82XX_PEG_HALT_STATUS1); - if (halt_status & HALT_STATUS_UNRECOVERABLE) { - set_bit(ISP_UNRECOVERABLE, &vha->dpc_flags); - } else { - qla_printk(KERN_INFO, ha, - "scsi(%ld): %s - detect abort needed\n", - vha->host_no, __func__); - set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); - } - qla2xxx_wake_dpc(vha); - ha->flags.fw_hung = 1; - if (ha->flags.mbox_busy) { - ha->flags.mbox_int = 1; - DEBUG2(qla_printk(KERN_ERR, ha, - "Due to fw hung, doing premature " - "completion of mbx command\n")); - if (test_bit(MBX_INTR_WAIT, - &ha->mbx_cmd_flags)) - complete(&ha->mbx_intr_comp); - } + status = 1; } } else vha->seconds_since_last_heartbeat = 0; vha->fw_heartbeat_counter = fw_heartbeat_counter; + return status; } /* @@ -3557,6 +3571,8 @@ qla82xx_device_state_handler(scsi_qla_host_t *vha) break; case QLA82XX_DEV_NEED_RESET: qla82xx_need_reset_handler(vha); + dev_init_timeout = jiffies + + (ha->nx_dev_init_timeout * HZ); break; case QLA82XX_DEV_NEED_QUIESCENT: qla82xx_need_qsnt_handler(vha); @@ -3596,30 +3612,18 @@ exit: void qla82xx_watchdog(scsi_qla_host_t *vha) { - uint32_t dev_state; + uint32_t dev_state, halt_status; struct qla_hw_data *ha = vha->hw; - dev_state = qla82xx_rd_32(ha, QLA82XX_CRB_DEV_STATE); - /* don't poll if reset is going on */ - if (!(test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags) || - test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) || - test_bit(ISP_ABORT_RETRY, &vha->dpc_flags))) { - if (dev_state == QLA82XX_DEV_NEED_RESET) { + if (!ha->flags.isp82xx_reset_hdlr_active) { + dev_state = qla82xx_rd_32(ha, QLA82XX_CRB_DEV_STATE); + if (dev_state == QLA82XX_DEV_NEED_RESET && + !test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags)) { qla_printk(KERN_WARNING, ha, - "%s(): Adapter reset needed!\n", __func__); + "%s(): Adapter reset needed!\n", __func__); set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); qla2xxx_wake_dpc(vha); - ha->flags.fw_hung = 1; - if (ha->flags.mbox_busy) { - ha->flags.mbox_int = 1; - DEBUG2(qla_printk(KERN_ERR, ha, - "Need reset, doing premature " - "completion of mbx command\n")); - if (test_bit(MBX_INTR_WAIT, - &ha->mbx_cmd_flags)) - complete(&ha->mbx_intr_comp); - } } else if (dev_state == QLA82XX_DEV_NEED_QUIESCENT && !test_bit(ISP_QUIESCE_NEEDED, &vha->dpc_flags)) { DEBUG(qla_printk(KERN_INFO, ha, @@ -3629,6 +3633,31 @@ void qla82xx_watchdog(scsi_qla_host_t *vha) qla2xxx_wake_dpc(vha); } else { qla82xx_check_fw_alive(vha); + if (qla82xx_check_fw_alive(vha)) { + halt_status = qla82xx_rd_32(ha, + QLA82XX_PEG_HALT_STATUS1); + if (halt_status & HALT_STATUS_UNRECOVERABLE) { + set_bit(ISP_UNRECOVERABLE, + &vha->dpc_flags); + } else { + qla_printk(KERN_INFO, ha, + "scsi(%ld): %s - detect abort needed\n", + vha->host_no, __func__); + set_bit(ISP_ABORT_NEEDED, + &vha->dpc_flags); + } + qla2xxx_wake_dpc(vha); + ha->flags.isp82xx_fw_hung = 1; + if (ha->flags.mbox_busy) { + ha->flags.mbox_int = 1; + DEBUG2(qla_printk(KERN_ERR, ha, + "Due to fw hung, doing premature " + "completion of mbx command\n")); + if (test_bit(MBX_INTR_WAIT, + &ha->mbx_cmd_flags)) + complete(&ha->mbx_intr_comp); + } + } } } } @@ -3663,6 +3692,7 @@ qla82xx_abort_isp(scsi_qla_host_t *vha) "Exiting.\n", __func__, vha->host_no); return QLA_SUCCESS; } + ha->flags.isp82xx_reset_hdlr_active = 1; qla82xx_idc_lock(ha); dev_state = qla82xx_rd_32(ha, QLA82XX_CRB_DEV_STATE); @@ -3683,7 +3713,8 @@ qla82xx_abort_isp(scsi_qla_host_t *vha) qla82xx_idc_unlock(ha); if (rval == QLA_SUCCESS) { - ha->flags.fw_hung = 0; + ha->flags.isp82xx_fw_hung = 0; + ha->flags.isp82xx_reset_hdlr_active = 0; qla82xx_restart_isp(vha); } @@ -3791,3 +3822,71 @@ int qla2x00_wait_for_fcoe_ctx_reset(scsi_qla_host_t *vha) return status; } + +void +qla82xx_chip_reset_cleanup(scsi_qla_host_t *vha) +{ + int i; + unsigned long flags; + struct qla_hw_data *ha = vha->hw; + + /* Check if 82XX firmware is alive or not + * We may have arrived here from NEED_RESET + * detection only + */ + if (!ha->flags.isp82xx_fw_hung) { + for (i = 0; i < 2; i++) { + msleep(1000); + if (qla82xx_check_fw_alive(vha)) { + ha->flags.isp82xx_fw_hung = 1; + if (ha->flags.mbox_busy) { + ha->flags.mbox_int = 1; + complete(&ha->mbx_intr_comp); + } + break; + } + } + } + + /* Abort all commands gracefully if fw NOT hung */ + if (!ha->flags.isp82xx_fw_hung) { + int cnt, que; + srb_t *sp; + struct req_que *req; + + spin_lock_irqsave(&ha->hardware_lock, flags); + for (que = 0; que < ha->max_req_queues; que++) { + req = ha->req_q_map[que]; + if (!req) + continue; + for (cnt = 1; cnt < MAX_OUTSTANDING_COMMANDS; cnt++) { + sp = req->outstanding_cmds[cnt]; + if (sp) { + if (!sp->ctx || + (sp->flags & SRB_FCP_CMND_DMA_VALID)) { + spin_unlock_irqrestore( + &ha->hardware_lock, flags); + if (ha->isp_ops->abort_command(sp)) { + qla_printk(KERN_INFO, ha, + "scsi(%ld): mbx abort command failed in %s\n", + vha->host_no, __func__); + } else { + qla_printk(KERN_INFO, ha, + "scsi(%ld): mbx abort command success in %s\n", + vha->host_no, __func__); + } + spin_lock_irqsave(&ha->hardware_lock, flags); + } + } + } + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + /* Wait for pending cmds (physical and virtual) to complete */ + if (!qla2x00_eh_wait_for_pending_commands(vha, 0, 0, + WAIT_HOST) == QLA_SUCCESS) { + DEBUG2(qla_printk(KERN_INFO, ha, + "Done wait for pending commands\n")); + } + } +} diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index f27724d76cf6..6c002fb6dd07 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -506,7 +506,7 @@ qla24xx_fw_version_str(struct scsi_qla_host *vha, char *str) static inline srb_t * qla2x00_get_new_sp(scsi_qla_host_t *vha, fc_port_t *fcport, - struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)) + struct scsi_cmnd *cmd) { srb_t *sp; struct qla_hw_data *ha = vha->hw; @@ -520,14 +520,13 @@ qla2x00_get_new_sp(scsi_qla_host_t *vha, fc_port_t *fcport, sp->cmd = cmd; sp->flags = 0; CMD_SP(cmd) = (void *)sp; - cmd->scsi_done = done; sp->ctx = NULL; return sp; } static int -qla2xxx_queuecommand_lck(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *)) +qla2xxx_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd) { scsi_qla_host_t *vha = shost_priv(cmd->device->host); fc_port_t *fcport = (struct fc_port *) cmd->device->hostdata; @@ -537,7 +536,6 @@ qla2xxx_queuecommand_lck(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *) srb_t *sp; int rval; - spin_unlock_irq(vha->host->host_lock); if (ha->flags.eeh_busy) { if (ha->flags.pci_channel_io_perm_failure) cmd->result = DID_NO_CONNECT << 16; @@ -569,40 +567,32 @@ qla2xxx_queuecommand_lck(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *) goto qc24_target_busy; } - sp = qla2x00_get_new_sp(base_vha, fcport, cmd, done); + sp = qla2x00_get_new_sp(base_vha, fcport, cmd); if (!sp) - goto qc24_host_busy_lock; + goto qc24_host_busy; rval = ha->isp_ops->start_scsi(sp); if (rval != QLA_SUCCESS) goto qc24_host_busy_free_sp; - spin_lock_irq(vha->host->host_lock); - return 0; qc24_host_busy_free_sp: qla2x00_sp_free_dma(sp); mempool_free(sp, ha->srb_mempool); -qc24_host_busy_lock: - spin_lock_irq(vha->host->host_lock); +qc24_host_busy: return SCSI_MLQUEUE_HOST_BUSY; qc24_target_busy: - spin_lock_irq(vha->host->host_lock); return SCSI_MLQUEUE_TARGET_BUSY; qc24_fail_command: - spin_lock_irq(vha->host->host_lock); - done(cmd); + cmd->scsi_done(cmd); return 0; } -static DEF_SCSI_QCMD(qla2xxx_queuecommand) - - /* * qla2x00_eh_wait_on_command * Waits for the command to be returned by the Firmware for some @@ -821,17 +811,20 @@ qla2xxx_eh_abort(struct scsi_cmnd *cmd) { scsi_qla_host_t *vha = shost_priv(cmd->device->host); srb_t *sp; - int ret = SUCCESS; + int ret; unsigned int id, lun; unsigned long flags; int wait = 0; struct qla_hw_data *ha = vha->hw; - fc_block_scsi_eh(cmd); - if (!CMD_SP(cmd)) return SUCCESS; + ret = fc_block_scsi_eh(cmd); + if (ret != 0) + return ret; + ret = SUCCESS; + id = cmd->device->id; lun = cmd->device->lun; @@ -940,11 +933,13 @@ __qla2xxx_eh_generic_reset(char *name, enum nexus_wait_type type, fc_port_t *fcport = (struct fc_port *) cmd->device->hostdata; int err; - fc_block_scsi_eh(cmd); - if (!fcport) return FAILED; + err = fc_block_scsi_eh(cmd); + if (err != 0) + return err; + qla_printk(KERN_INFO, vha->hw, "scsi(%ld:%d:%d): %s RESET ISSUED.\n", vha->host_no, cmd->device->id, cmd->device->lun, name); @@ -1018,14 +1013,17 @@ qla2xxx_eh_bus_reset(struct scsi_cmnd *cmd) int ret = FAILED; unsigned int id, lun; - fc_block_scsi_eh(cmd); - id = cmd->device->id; lun = cmd->device->lun; if (!fcport) return ret; + ret = fc_block_scsi_eh(cmd); + if (ret != 0) + return ret; + ret = FAILED; + qla_printk(KERN_INFO, vha->hw, "scsi(%ld:%d:%d): BUS RESET ISSUED.\n", vha->host_no, id, lun); @@ -1078,14 +1076,17 @@ qla2xxx_eh_host_reset(struct scsi_cmnd *cmd) unsigned int id, lun; scsi_qla_host_t *base_vha = pci_get_drvdata(ha->pdev); - fc_block_scsi_eh(cmd); - id = cmd->device->id; lun = cmd->device->lun; if (!fcport) return ret; + ret = fc_block_scsi_eh(cmd); + if (ret != 0) + return ret; + ret = FAILED; + qla_printk(KERN_INFO, ha, "scsi(%ld:%d:%d): ADAPTER RESET ISSUED.\n", vha->host_no, id, lun); @@ -3805,7 +3806,7 @@ qla2xxx_pci_error_detected(struct pci_dev *pdev, pci_channel_state_t state) ha->flags.eeh_busy = 1; /* For ISP82XX complete any pending mailbox cmd */ if (IS_QLA82XX(ha)) { - ha->flags.fw_hung = 1; + ha->flags.isp82xx_fw_hung = 1; if (ha->flags.mbox_busy) { ha->flags.mbox_int = 1; DEBUG2(qla_printk(KERN_ERR, ha, @@ -3945,7 +3946,7 @@ uint32_t qla82xx_error_recovery(scsi_qla_host_t *base_vha) qla82xx_wr_32(ha, QLA82XX_CRB_DEV_STATE, QLA82XX_DEV_READY); qla82xx_idc_unlock(ha); - ha->flags.fw_hung = 0; + ha->flags.isp82xx_fw_hung = 0; rval = qla82xx_restart_isp(base_vha); qla82xx_idc_lock(ha); /* Clear driver state register */ @@ -3958,7 +3959,7 @@ uint32_t qla82xx_error_recovery(scsi_qla_host_t *base_vha) "This devfn is not reset owner = 0x%x\n", ha->pdev->devfn)); if ((qla82xx_rd_32(ha, QLA82XX_CRB_DEV_STATE) == QLA82XX_DEV_READY)) { - ha->flags.fw_hung = 0; + ha->flags.isp82xx_fw_hung = 0; rval = qla82xx_restart_isp(base_vha); qla82xx_idc_lock(ha); qla82xx_set_drv_active(base_vha); diff --git a/drivers/scsi/qla2xxx/qla_version.h b/drivers/scsi/qla2xxx/qla_version.h index cf0075a2d0c2..3a260c3f055a 100644 --- a/drivers/scsi/qla2xxx/qla_version.h +++ b/drivers/scsi/qla2xxx/qla_version.h @@ -7,9 +7,9 @@ /* * Driver version */ -#define QLA2XXX_VERSION "8.03.05-k0" +#define QLA2XXX_VERSION "8.03.07.00" #define QLA_DRIVER_MAJOR_VER 8 #define QLA_DRIVER_MINOR_VER 3 -#define QLA_DRIVER_PATCH_VER 5 +#define QLA_DRIVER_PATCH_VER 7 #define QLA_DRIVER_BETA_VER 0 diff --git a/drivers/scsi/scsi_devinfo.c b/drivers/scsi/scsi_devinfo.c index 43fad4c09beb..82e9e5c0476e 100644 --- a/drivers/scsi/scsi_devinfo.c +++ b/drivers/scsi/scsi_devinfo.c @@ -382,6 +382,91 @@ int scsi_dev_info_list_add_keyed(int compatible, char *vendor, char *model, EXPORT_SYMBOL(scsi_dev_info_list_add_keyed); /** + * scsi_dev_info_list_del_keyed - remove one dev_info list entry. + * @vendor: vendor string + * @model: model (product) string + * @key: specify list to use + * + * Description: + * Remove and destroy one dev_info entry for @vendor, @model + * in list specified by @key. + * + * Returns: 0 OK, -error on failure. + **/ +int scsi_dev_info_list_del_keyed(char *vendor, char *model, int key) +{ + struct scsi_dev_info_list *devinfo, *found = NULL; + struct scsi_dev_info_list_table *devinfo_table = + scsi_devinfo_lookup_by_key(key); + + if (IS_ERR(devinfo_table)) + return PTR_ERR(devinfo_table); + + list_for_each_entry(devinfo, &devinfo_table->scsi_dev_info_list, + dev_info_list) { + if (devinfo->compatible) { + /* + * Behave like the older version of get_device_flags. + */ + size_t max; + /* + * XXX why skip leading spaces? If an odd INQUIRY + * value, that should have been part of the + * scsi_static_device_list[] entry, such as " FOO" + * rather than "FOO". Since this code is already + * here, and we don't know what device it is + * trying to work with, leave it as-is. + */ + max = 8; /* max length of vendor */ + while ((max > 0) && *vendor == ' ') { + max--; + vendor++; + } + /* + * XXX removing the following strlen() would be + * good, using it means that for a an entry not in + * the list, we scan every byte of every vendor + * listed in scsi_static_device_list[], and never match + * a single one (and still have to compare at + * least the first byte of each vendor). + */ + if (memcmp(devinfo->vendor, vendor, + min(max, strlen(devinfo->vendor)))) + continue; + /* + * Skip spaces again. + */ + max = 16; /* max length of model */ + while ((max > 0) && *model == ' ') { + max--; + model++; + } + if (memcmp(devinfo->model, model, + min(max, strlen(devinfo->model)))) + continue; + found = devinfo; + } else { + if (!memcmp(devinfo->vendor, vendor, + sizeof(devinfo->vendor)) && + !memcmp(devinfo->model, model, + sizeof(devinfo->model))) + found = devinfo; + } + if (found) + break; + } + + if (found) { + list_del(&found->dev_info_list); + kfree(found); + return 0; + } + + return -ENOENT; +} +EXPORT_SYMBOL(scsi_dev_info_list_del_keyed); + +/** * scsi_dev_info_list_add_str - parse dev_list and add to the scsi_dev_info_list. * @dev_list: string of device flags to add * diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index 45c75649b9e0..991de3c15cfc 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -223,7 +223,7 @@ static inline void scsi_eh_prt_fail_stats(struct Scsi_Host *shost, * @scmd: Cmd to have sense checked. * * Return value: - * SUCCESS or FAILED or NEEDS_RETRY + * SUCCESS or FAILED or NEEDS_RETRY or TARGET_ERROR * * Notes: * When a deferred error is detected the current command has @@ -326,17 +326,19 @@ static int scsi_check_sense(struct scsi_cmnd *scmd) */ return SUCCESS; - /* these three are not supported */ + /* these are not supported */ case COPY_ABORTED: case VOLUME_OVERFLOW: case MISCOMPARE: - return SUCCESS; + case BLANK_CHECK: + case DATA_PROTECT: + return TARGET_ERROR; case MEDIUM_ERROR: if (sshdr.asc == 0x11 || /* UNRECOVERED READ ERR */ sshdr.asc == 0x13 || /* AMNF DATA FIELD */ sshdr.asc == 0x14) { /* RECORD NOT FOUND */ - return SUCCESS; + return TARGET_ERROR; } return NEEDS_RETRY; @@ -344,11 +346,9 @@ static int scsi_check_sense(struct scsi_cmnd *scmd) if (scmd->device->retry_hwerror) return ADD_TO_MLQUEUE; else - return SUCCESS; + return TARGET_ERROR; case ILLEGAL_REQUEST: - case BLANK_CHECK: - case DATA_PROTECT: default: return SUCCESS; } @@ -787,6 +787,7 @@ static int scsi_send_eh_cmnd(struct scsi_cmnd *scmd, unsigned char *cmnd, case SUCCESS: case NEEDS_RETRY: case FAILED: + case TARGET_ERROR: break; case ADD_TO_MLQUEUE: rtn = NEEDS_RETRY; @@ -1469,6 +1470,14 @@ int scsi_decide_disposition(struct scsi_cmnd *scmd) rtn = scsi_check_sense(scmd); if (rtn == NEEDS_RETRY) goto maybe_retry; + else if (rtn == TARGET_ERROR) { + /* + * Need to modify host byte to signal a + * permanent target failure + */ + scmd->result |= (DID_TARGET_FAILURE << 16); + rtn = SUCCESS; + } /* if rtn == FAILED, we have no sense information; * returning FAILED will wake the error handler thread * to collect the sense and redo the decide @@ -1486,6 +1495,7 @@ int scsi_decide_disposition(struct scsi_cmnd *scmd) case RESERVATION_CONFLICT: sdev_printk(KERN_INFO, scmd->device, "reservation conflict\n"); + scmd->result |= (DID_NEXUS_FAILURE << 16); return SUCCESS; /* causes immediate i/o error */ default: return FAILED; diff --git a/drivers/scsi/scsi_lib.c b/drivers/scsi/scsi_lib.c index 9045c52abd25..8d4ef8efa3cd 100644 --- a/drivers/scsi/scsi_lib.c +++ b/drivers/scsi/scsi_lib.c @@ -667,6 +667,30 @@ void scsi_release_buffers(struct scsi_cmnd *cmd) } EXPORT_SYMBOL(scsi_release_buffers); +static int __scsi_error_from_host_byte(struct scsi_cmnd *cmd, int result) +{ + int error = 0; + + switch(host_byte(result)) { + case DID_TRANSPORT_FAILFAST: + error = -ENOLINK; + break; + case DID_TARGET_FAILURE: + cmd->result |= (DID_OK << 16); + error = -EREMOTEIO; + break; + case DID_NEXUS_FAILURE: + cmd->result |= (DID_OK << 16); + error = -EBADE; + break; + default: + error = -EIO; + break; + } + + return error; +} + /* * Function: scsi_io_completion() * @@ -737,7 +761,7 @@ void scsi_io_completion(struct scsi_cmnd *cmd, unsigned int good_bytes) req->sense_len = len; } if (!sense_deferred) - error = -EIO; + error = __scsi_error_from_host_byte(cmd, result); } req->resid_len = scsi_get_resid(cmd); @@ -796,7 +820,7 @@ void scsi_io_completion(struct scsi_cmnd *cmd, unsigned int good_bytes) if (scsi_end_request(cmd, error, good_bytes, result == 0) == NULL) return; - error = -EIO; + error = __scsi_error_from_host_byte(cmd, result); if (host_byte(result) == DID_RESET) { /* Third party bus reset or reset for error recovery diff --git a/drivers/scsi/scsi_priv.h b/drivers/scsi/scsi_priv.h index b4056d14f812..9868afdb24e2 100644 --- a/drivers/scsi/scsi_priv.h +++ b/drivers/scsi/scsi_priv.h @@ -45,6 +45,7 @@ static inline void scsi_log_completion(struct scsi_cmnd *cmd, int disposition) enum { SCSI_DEVINFO_GLOBAL = 0, SCSI_DEVINFO_SPI, + SCSI_DEVINFO_DH, }; extern int scsi_get_device_flags(struct scsi_device *sdev, @@ -56,6 +57,7 @@ extern int scsi_get_device_flags_keyed(struct scsi_device *sdev, extern int scsi_dev_info_list_add_keyed(int compatible, char *vendor, char *model, char *strflags, int flags, int key); +extern int scsi_dev_info_list_del_keyed(char *vendor, char *model, int key); extern int scsi_dev_info_add_list(int key, const char *name); extern int scsi_dev_info_remove_list(int key); diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c index f905ecb5704d..b4218390941e 100644 --- a/drivers/scsi/scsi_transport_iscsi.c +++ b/drivers/scsi/scsi_transport_iscsi.c @@ -954,6 +954,7 @@ iscsi_create_conn(struct iscsi_cls_session *session, int dd_size, uint32_t cid) if (dd_size) conn->dd_data = &conn[1]; + mutex_init(&conn->ep_mutex); INIT_LIST_HEAD(&conn->conn_list); conn->transport = transport; conn->cid = cid; @@ -975,7 +976,6 @@ iscsi_create_conn(struct iscsi_cls_session *session, int dd_size, uint32_t cid) spin_lock_irqsave(&connlock, flags); list_add(&conn->conn_list, &connlist); - conn->active = 1; spin_unlock_irqrestore(&connlock, flags); ISCSI_DBG_TRANS_CONN(conn, "Completed conn creation\n"); @@ -1001,7 +1001,6 @@ int iscsi_destroy_conn(struct iscsi_cls_conn *conn) unsigned long flags; spin_lock_irqsave(&connlock, flags); - conn->active = 0; list_del(&conn->conn_list); spin_unlock_irqrestore(&connlock, flags); @@ -1430,6 +1429,29 @@ release_host: return err; } +static int iscsi_if_ep_disconnect(struct iscsi_transport *transport, + u64 ep_handle) +{ + struct iscsi_cls_conn *conn; + struct iscsi_endpoint *ep; + + if (!transport->ep_disconnect) + return -EINVAL; + + ep = iscsi_lookup_endpoint(ep_handle); + if (!ep) + return -EINVAL; + conn = ep->conn; + if (conn) { + mutex_lock(&conn->ep_mutex); + conn->ep = NULL; + mutex_unlock(&conn->ep_mutex); + } + + transport->ep_disconnect(ep); + return 0; +} + static int iscsi_if_transport_ep(struct iscsi_transport *transport, struct iscsi_uevent *ev, int msg_type) @@ -1454,14 +1476,8 @@ iscsi_if_transport_ep(struct iscsi_transport *transport, ev->u.ep_poll.timeout_ms); break; case ISCSI_UEVENT_TRANSPORT_EP_DISCONNECT: - if (!transport->ep_disconnect) - return -EINVAL; - - ep = iscsi_lookup_endpoint(ev->u.ep_disconnect.ep_handle); - if (!ep) - return -EINVAL; - - transport->ep_disconnect(ep); + rc = iscsi_if_ep_disconnect(transport, + ev->u.ep_disconnect.ep_handle); break; } return rc; @@ -1609,12 +1625,31 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group) session = iscsi_session_lookup(ev->u.b_conn.sid); conn = iscsi_conn_lookup(ev->u.b_conn.sid, ev->u.b_conn.cid); - if (session && conn) - ev->r.retcode = transport->bind_conn(session, conn, - ev->u.b_conn.transport_eph, - ev->u.b_conn.is_leading); - else + if (conn && conn->ep) + iscsi_if_ep_disconnect(transport, conn->ep->id); + + if (!session || !conn) { err = -EINVAL; + break; + } + + ev->r.retcode = transport->bind_conn(session, conn, + ev->u.b_conn.transport_eph, + ev->u.b_conn.is_leading); + if (ev->r.retcode || !transport->ep_connect) + break; + + ep = iscsi_lookup_endpoint(ev->u.b_conn.transport_eph); + if (ep) { + ep->conn = conn; + + mutex_lock(&conn->ep_mutex); + conn->ep = ep; + mutex_unlock(&conn->ep_mutex); + } else + iscsi_cls_conn_printk(KERN_ERR, conn, + "Could not set ep conn " + "binding\n"); break; case ISCSI_UEVENT_SET_PARAM: err = iscsi_set_param(transport, ev); @@ -1747,13 +1782,48 @@ iscsi_conn_attr(data_digest, ISCSI_PARAM_DATADGST_EN); iscsi_conn_attr(ifmarker, ISCSI_PARAM_IFMARKER_EN); iscsi_conn_attr(ofmarker, ISCSI_PARAM_OFMARKER_EN); iscsi_conn_attr(persistent_port, ISCSI_PARAM_PERSISTENT_PORT); -iscsi_conn_attr(port, ISCSI_PARAM_CONN_PORT); iscsi_conn_attr(exp_statsn, ISCSI_PARAM_EXP_STATSN); iscsi_conn_attr(persistent_address, ISCSI_PARAM_PERSISTENT_ADDRESS); -iscsi_conn_attr(address, ISCSI_PARAM_CONN_ADDRESS); iscsi_conn_attr(ping_tmo, ISCSI_PARAM_PING_TMO); iscsi_conn_attr(recv_tmo, ISCSI_PARAM_RECV_TMO); +#define iscsi_conn_ep_attr_show(param) \ +static ssize_t show_conn_ep_param_##param(struct device *dev, \ + struct device_attribute *attr,\ + char *buf) \ +{ \ + struct iscsi_cls_conn *conn = iscsi_dev_to_conn(dev->parent); \ + struct iscsi_transport *t = conn->transport; \ + struct iscsi_endpoint *ep; \ + ssize_t rc; \ + \ + /* \ + * Need to make sure ep_disconnect does not free the LLD's \ + * interconnect resources while we are trying to read them. \ + */ \ + mutex_lock(&conn->ep_mutex); \ + ep = conn->ep; \ + if (!ep && t->ep_connect) { \ + mutex_unlock(&conn->ep_mutex); \ + return -ENOTCONN; \ + } \ + \ + if (ep) \ + rc = t->get_ep_param(ep, param, buf); \ + else \ + rc = t->get_conn_param(conn, param, buf); \ + mutex_unlock(&conn->ep_mutex); \ + return rc; \ +} + +#define iscsi_conn_ep_attr(field, param) \ + iscsi_conn_ep_attr_show(param) \ +static ISCSI_CLASS_ATTR(conn, field, S_IRUGO, \ + show_conn_ep_param_##param, NULL); + +iscsi_conn_ep_attr(address, ISCSI_PARAM_CONN_ADDRESS); +iscsi_conn_ep_attr(port, ISCSI_PARAM_CONN_PORT); + /* * iSCSI session attrs */ diff --git a/drivers/spi/amba-pl022.c b/drivers/spi/amba-pl022.c index 71a1219a995d..95e58c70a2c9 100644 --- a/drivers/spi/amba-pl022.c +++ b/drivers/spi/amba-pl022.c @@ -2021,7 +2021,7 @@ static void pl022_cleanup(struct spi_device *spi) static int __devinit -pl022_probe(struct amba_device *adev, struct amba_id *id) +pl022_probe(struct amba_device *adev, const struct amba_id *id) { struct device *dev = &adev->dev; struct pl022_ssp_controller *platform_info = adev->dev.platform_data; diff --git a/drivers/spi/davinci_spi.c b/drivers/spi/davinci_spi.c index 6beab99bf95b..166a879fd9e8 100644 --- a/drivers/spi/davinci_spi.c +++ b/drivers/spi/davinci_spi.c @@ -790,7 +790,6 @@ static int davinci_spi_probe(struct platform_device *pdev) struct resource *r, *mem; resource_size_t dma_rx_chan = SPI_NO_RESOURCE; resource_size_t dma_tx_chan = SPI_NO_RESOURCE; - resource_size_t dma_eventq = SPI_NO_RESOURCE; int i = 0, ret = 0; u32 spipc0; @@ -878,17 +877,13 @@ static int davinci_spi_probe(struct platform_device *pdev) r = platform_get_resource(pdev, IORESOURCE_DMA, 1); if (r) dma_tx_chan = r->start; - r = platform_get_resource(pdev, IORESOURCE_DMA, 2); - if (r) - dma_eventq = r->start; dspi->bitbang.txrx_bufs = davinci_spi_bufs; if (dma_rx_chan != SPI_NO_RESOURCE && - dma_tx_chan != SPI_NO_RESOURCE && - dma_eventq != SPI_NO_RESOURCE) { + dma_tx_chan != SPI_NO_RESOURCE) { dspi->dma.rx_channel = dma_rx_chan; dspi->dma.tx_channel = dma_tx_chan; - dspi->dma.eventq = dma_eventq; + dspi->dma.eventq = pdata->dma_event_q; ret = davinci_spi_request_dma(dspi); if (ret) @@ -897,7 +892,7 @@ static int davinci_spi_probe(struct platform_device *pdev) dev_info(&pdev->dev, "DMA: supported\n"); dev_info(&pdev->dev, "DMA: RX channel: %d, TX channel: %d, " "event queue: %d\n", dma_rx_chan, dma_tx_chan, - dma_eventq); + pdata->dma_event_q); } dspi->get_rx = davinci_spi_rx_buf_u8; diff --git a/drivers/spi/omap2_mcspi.c b/drivers/spi/omap2_mcspi.c index abb1ffbf3d20..36501adc125d 100644 --- a/drivers/spi/omap2_mcspi.c +++ b/drivers/spi/omap2_mcspi.c @@ -3,7 +3,7 @@ * * Copyright (C) 2005, 2006 Nokia Corporation * Author: Samuel Ortiz <samuel.ortiz@nokia.com> and - * Juha Yrjölä <juha.yrjola@nokia.com> + * Juha Yrj�l� <juha.yrjola@nokia.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 @@ -33,6 +33,7 @@ #include <linux/clk.h> #include <linux/io.h> #include <linux/slab.h> +#include <linux/pm_runtime.h> #include <linux/spi/spi.h> @@ -46,7 +47,6 @@ #define OMAP2_MCSPI_MAX_CTRL 4 #define OMAP2_MCSPI_REVISION 0x00 -#define OMAP2_MCSPI_SYSCONFIG 0x10 #define OMAP2_MCSPI_SYSSTATUS 0x14 #define OMAP2_MCSPI_IRQSTATUS 0x18 #define OMAP2_MCSPI_IRQENABLE 0x1c @@ -63,13 +63,6 @@ /* per-register bitmasks: */ -#define OMAP2_MCSPI_SYSCONFIG_SMARTIDLE BIT(4) -#define OMAP2_MCSPI_SYSCONFIG_ENAWAKEUP BIT(2) -#define OMAP2_MCSPI_SYSCONFIG_AUTOIDLE BIT(0) -#define OMAP2_MCSPI_SYSCONFIG_SOFTRESET BIT(1) - -#define OMAP2_MCSPI_SYSSTATUS_RESETDONE BIT(0) - #define OMAP2_MCSPI_MODULCTRL_SINGLE BIT(0) #define OMAP2_MCSPI_MODULCTRL_MS BIT(2) #define OMAP2_MCSPI_MODULCTRL_STEST BIT(3) @@ -122,13 +115,12 @@ struct omap2_mcspi { spinlock_t lock; struct list_head msg_queue; struct spi_master *master; - struct clk *ick; - struct clk *fck; /* Virtual base address of the controller */ void __iomem *base; unsigned long phys; /* SPI1 has 4 channels, while SPI2 has 2 */ struct omap2_mcspi_dma *dma_channels; + struct device *dev; }; struct omap2_mcspi_cs { @@ -144,7 +136,6 @@ struct omap2_mcspi_cs { * corresponding registers are modified. */ struct omap2_mcspi_regs { - u32 sysconfig; u32 modulctrl; u32 wakeupenable; struct list_head cs; @@ -268,9 +259,6 @@ static void omap2_mcspi_restore_ctx(struct omap2_mcspi *mcspi) mcspi_write_reg(spi_cntrl, OMAP2_MCSPI_MODULCTRL, omap2_mcspi_ctx[spi_cntrl->bus_num - 1].modulctrl); - mcspi_write_reg(spi_cntrl, OMAP2_MCSPI_SYSCONFIG, - omap2_mcspi_ctx[spi_cntrl->bus_num - 1].sysconfig); - mcspi_write_reg(spi_cntrl, OMAP2_MCSPI_WAKEUPENABLE, omap2_mcspi_ctx[spi_cntrl->bus_num - 1].wakeupenable); @@ -280,20 +268,12 @@ static void omap2_mcspi_restore_ctx(struct omap2_mcspi *mcspi) } static void omap2_mcspi_disable_clocks(struct omap2_mcspi *mcspi) { - clk_disable(mcspi->ick); - clk_disable(mcspi->fck); + pm_runtime_put_sync(mcspi->dev); } static int omap2_mcspi_enable_clocks(struct omap2_mcspi *mcspi) { - if (clk_enable(mcspi->ick)) - return -ENODEV; - if (clk_enable(mcspi->fck)) - return -ENODEV; - - omap2_mcspi_restore_ctx(mcspi); - - return 0; + return pm_runtime_get_sync(mcspi->dev); } static int mcspi_wait_for_reg_bit(void __iomem *reg, unsigned long bit) @@ -819,8 +799,9 @@ static int omap2_mcspi_setup(struct spi_device *spi) return ret; } - if (omap2_mcspi_enable_clocks(mcspi)) - return -ENODEV; + ret = omap2_mcspi_enable_clocks(mcspi); + if (ret < 0) + return ret; ret = omap2_mcspi_setup_transfer(spi, NULL); omap2_mcspi_disable_clocks(mcspi); @@ -863,10 +844,11 @@ static void omap2_mcspi_work(struct work_struct *work) struct omap2_mcspi *mcspi; mcspi = container_of(work, struct omap2_mcspi, work); - spin_lock_irq(&mcspi->lock); - if (omap2_mcspi_enable_clocks(mcspi)) - goto out; + if (omap2_mcspi_enable_clocks(mcspi) < 0) + return; + + spin_lock_irq(&mcspi->lock); /* We only enable one channel at a time -- the one whose message is * at the head of the queue -- although this controller would gladly @@ -979,10 +961,9 @@ static void omap2_mcspi_work(struct work_struct *work) spin_lock_irq(&mcspi->lock); } - omap2_mcspi_disable_clocks(mcspi); - -out: spin_unlock_irq(&mcspi->lock); + + omap2_mcspi_disable_clocks(mcspi); } static int omap2_mcspi_transfer(struct spi_device *spi, struct spi_message *m) @@ -1058,25 +1039,15 @@ static int omap2_mcspi_transfer(struct spi_device *spi, struct spi_message *m) return 0; } -static int __init omap2_mcspi_reset(struct omap2_mcspi *mcspi) +static int __init omap2_mcspi_master_setup(struct omap2_mcspi *mcspi) { struct spi_master *master = mcspi->master; u32 tmp; + int ret = 0; - if (omap2_mcspi_enable_clocks(mcspi)) - return -1; - - mcspi_write_reg(master, OMAP2_MCSPI_SYSCONFIG, - OMAP2_MCSPI_SYSCONFIG_SOFTRESET); - do { - tmp = mcspi_read_reg(master, OMAP2_MCSPI_SYSSTATUS); - } while (!(tmp & OMAP2_MCSPI_SYSSTATUS_RESETDONE)); - - tmp = OMAP2_MCSPI_SYSCONFIG_AUTOIDLE | - OMAP2_MCSPI_SYSCONFIG_ENAWAKEUP | - OMAP2_MCSPI_SYSCONFIG_SMARTIDLE; - mcspi_write_reg(master, OMAP2_MCSPI_SYSCONFIG, tmp); - omap2_mcspi_ctx[master->bus_num - 1].sysconfig = tmp; + ret = omap2_mcspi_enable_clocks(mcspi); + if (ret < 0) + return ret; tmp = OMAP2_MCSPI_WAKEUPENABLE_WKEN; mcspi_write_reg(master, OMAP2_MCSPI_WAKEUPENABLE, tmp); @@ -1087,91 +1058,26 @@ static int __init omap2_mcspi_reset(struct omap2_mcspi *mcspi) return 0; } -static u8 __initdata spi1_rxdma_id [] = { - OMAP24XX_DMA_SPI1_RX0, - OMAP24XX_DMA_SPI1_RX1, - OMAP24XX_DMA_SPI1_RX2, - OMAP24XX_DMA_SPI1_RX3, -}; - -static u8 __initdata spi1_txdma_id [] = { - OMAP24XX_DMA_SPI1_TX0, - OMAP24XX_DMA_SPI1_TX1, - OMAP24XX_DMA_SPI1_TX2, - OMAP24XX_DMA_SPI1_TX3, -}; - -static u8 __initdata spi2_rxdma_id[] = { - OMAP24XX_DMA_SPI2_RX0, - OMAP24XX_DMA_SPI2_RX1, -}; - -static u8 __initdata spi2_txdma_id[] = { - OMAP24XX_DMA_SPI2_TX0, - OMAP24XX_DMA_SPI2_TX1, -}; - -#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) \ - || defined(CONFIG_ARCH_OMAP4) -static u8 __initdata spi3_rxdma_id[] = { - OMAP24XX_DMA_SPI3_RX0, - OMAP24XX_DMA_SPI3_RX1, -}; +static int omap_mcspi_runtime_resume(struct device *dev) +{ + struct omap2_mcspi *mcspi; + struct spi_master *master; -static u8 __initdata spi3_txdma_id[] = { - OMAP24XX_DMA_SPI3_TX0, - OMAP24XX_DMA_SPI3_TX1, -}; -#endif + master = dev_get_drvdata(dev); + mcspi = spi_master_get_devdata(master); + omap2_mcspi_restore_ctx(mcspi); -#if defined(CONFIG_ARCH_OMAP3) || defined(CONFIG_ARCH_OMAP4) -static u8 __initdata spi4_rxdma_id[] = { - OMAP34XX_DMA_SPI4_RX0, -}; + return 0; +} -static u8 __initdata spi4_txdma_id[] = { - OMAP34XX_DMA_SPI4_TX0, -}; -#endif static int __init omap2_mcspi_probe(struct platform_device *pdev) { struct spi_master *master; + struct omap2_mcspi_platform_config *pdata = pdev->dev.platform_data; struct omap2_mcspi *mcspi; struct resource *r; int status = 0, i; - const u8 *rxdma_id, *txdma_id; - unsigned num_chipselect; - - switch (pdev->id) { - case 1: - rxdma_id = spi1_rxdma_id; - txdma_id = spi1_txdma_id; - num_chipselect = 4; - break; - case 2: - rxdma_id = spi2_rxdma_id; - txdma_id = spi2_txdma_id; - num_chipselect = 2; - break; -#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) \ - || defined(CONFIG_ARCH_OMAP4) - case 3: - rxdma_id = spi3_rxdma_id; - txdma_id = spi3_txdma_id; - num_chipselect = 2; - break; -#endif -#if defined(CONFIG_ARCH_OMAP3) || defined(CONFIG_ARCH_OMAP4) - case 4: - rxdma_id = spi4_rxdma_id; - txdma_id = spi4_txdma_id; - num_chipselect = 1; - break; -#endif - default: - return -EINVAL; - } master = spi_alloc_master(&pdev->dev, sizeof *mcspi); if (master == NULL) { @@ -1188,7 +1094,7 @@ static int __init omap2_mcspi_probe(struct platform_device *pdev) master->setup = omap2_mcspi_setup; master->transfer = omap2_mcspi_transfer; master->cleanup = omap2_mcspi_cleanup; - master->num_chipselect = num_chipselect; + master->num_chipselect = pdata->num_cs; dev_set_drvdata(&pdev->dev, master); @@ -1206,49 +1112,62 @@ static int __init omap2_mcspi_probe(struct platform_device *pdev) goto err1; } + r->start += pdata->regs_offset; + r->end += pdata->regs_offset; mcspi->phys = r->start; mcspi->base = ioremap(r->start, r->end - r->start + 1); if (!mcspi->base) { dev_dbg(&pdev->dev, "can't ioremap MCSPI\n"); status = -ENOMEM; - goto err1aa; + goto err2; } + mcspi->dev = &pdev->dev; INIT_WORK(&mcspi->work, omap2_mcspi_work); spin_lock_init(&mcspi->lock); INIT_LIST_HEAD(&mcspi->msg_queue); INIT_LIST_HEAD(&omap2_mcspi_ctx[master->bus_num - 1].cs); - mcspi->ick = clk_get(&pdev->dev, "ick"); - if (IS_ERR(mcspi->ick)) { - dev_dbg(&pdev->dev, "can't get mcspi_ick\n"); - status = PTR_ERR(mcspi->ick); - goto err1a; - } - mcspi->fck = clk_get(&pdev->dev, "fck"); - if (IS_ERR(mcspi->fck)) { - dev_dbg(&pdev->dev, "can't get mcspi_fck\n"); - status = PTR_ERR(mcspi->fck); - goto err2; - } - mcspi->dma_channels = kcalloc(master->num_chipselect, sizeof(struct omap2_mcspi_dma), GFP_KERNEL); if (mcspi->dma_channels == NULL) - goto err3; + goto err2; + + for (i = 0; i < master->num_chipselect; i++) { + char dma_ch_name[14]; + struct resource *dma_res; + + sprintf(dma_ch_name, "rx%d", i); + dma_res = platform_get_resource_byname(pdev, IORESOURCE_DMA, + dma_ch_name); + if (!dma_res) { + dev_dbg(&pdev->dev, "cannot get DMA RX channel\n"); + status = -ENODEV; + break; + } - for (i = 0; i < num_chipselect; i++) { mcspi->dma_channels[i].dma_rx_channel = -1; - mcspi->dma_channels[i].dma_rx_sync_dev = rxdma_id[i]; + mcspi->dma_channels[i].dma_rx_sync_dev = dma_res->start; + sprintf(dma_ch_name, "tx%d", i); + dma_res = platform_get_resource_byname(pdev, IORESOURCE_DMA, + dma_ch_name); + if (!dma_res) { + dev_dbg(&pdev->dev, "cannot get DMA TX channel\n"); + status = -ENODEV; + break; + } + mcspi->dma_channels[i].dma_tx_channel = -1; - mcspi->dma_channels[i].dma_tx_sync_dev = txdma_id[i]; + mcspi->dma_channels[i].dma_tx_sync_dev = dma_res->start; } - if (omap2_mcspi_reset(mcspi) < 0) - goto err4; + pm_runtime_enable(&pdev->dev); + + if (status || omap2_mcspi_master_setup(mcspi) < 0) + goto err3; status = spi_register_master(master); if (status < 0) @@ -1257,17 +1176,13 @@ static int __init omap2_mcspi_probe(struct platform_device *pdev) return status; err4: - kfree(mcspi->dma_channels); + spi_master_put(master); err3: - clk_put(mcspi->fck); + kfree(mcspi->dma_channels); err2: - clk_put(mcspi->ick); -err1a: - iounmap(mcspi->base); -err1aa: release_mem_region(r->start, (r->end - r->start) + 1); + iounmap(mcspi->base); err1: - spi_master_put(master); return status; } @@ -1283,9 +1198,7 @@ static int __exit omap2_mcspi_remove(struct platform_device *pdev) mcspi = spi_master_get_devdata(master); dma_channels = mcspi->dma_channels; - clk_put(mcspi->fck); - clk_put(mcspi->ick); - + omap2_mcspi_disable_clocks(mcspi); r = platform_get_resource(pdev, IORESOURCE_MEM, 0); release_mem_region(r->start, (r->end - r->start) + 1); @@ -1336,6 +1249,7 @@ static int omap2_mcspi_resume(struct device *dev) static const struct dev_pm_ops omap2_mcspi_pm_ops = { .resume = omap2_mcspi_resume, + .runtime_resume = omap_mcspi_runtime_resume, }; static struct platform_driver omap2_mcspi_driver = { diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 5c8fcfc42c3e..4a88e69bb9bb 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -51,6 +51,8 @@ source "drivers/staging/cx25821/Kconfig" source "drivers/staging/tm6000/Kconfig" +source "drivers/staging/cxd2099/Kconfig" + source "drivers/staging/dabusb/Kconfig" source "drivers/staging/se401/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index d53886317826..80ee49ab59de 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_SLICOSS) += slicoss/ obj-$(CONFIG_VIDEO_GO7007) += go7007/ obj-$(CONFIG_VIDEO_CX25821) += cx25821/ obj-$(CONFIG_VIDEO_TM6000) += tm6000/ +obj-$(CONFIG_DVB_CXD2099) += cxd2099/ obj-$(CONFIG_USB_DABUSB) += dabusb/ obj-$(CONFIG_USB_VICAM) += usbvideo/ obj-$(CONFIG_USB_SE401) += se401/ diff --git a/drivers/staging/cxd2099/Kconfig b/drivers/staging/cxd2099/Kconfig new file mode 100644 index 000000000000..9d638c30735d --- /dev/null +++ b/drivers/staging/cxd2099/Kconfig @@ -0,0 +1,11 @@ +config DVB_CXD2099 + tristate "CXD2099AR Common Interface driver" + depends on DVB_CORE && PCI && I2C && DVB_NGENE + ---help--- + Support for the CI module found on cineS2 DVB-S2, supported by + the Micronas PCIe device driver (ngene). + + For now, data is passed through '/dev/dvb/adapterX/sec0': + - Encrypted data must be written to 'sec0'. + - Decrypted data can be read from 'sec0'. + - Setup the CAM using device 'ca0'. diff --git a/drivers/staging/cxd2099/Makefile b/drivers/staging/cxd2099/Makefile new file mode 100644 index 000000000000..72b14558c119 --- /dev/null +++ b/drivers/staging/cxd2099/Makefile @@ -0,0 +1,5 @@ +obj-$(CONFIG_DVB_CXD2099) += cxd2099.o + +EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core/ +EXTRA_CFLAGS += -Idrivers/media/dvb/frontends/ +EXTRA_CFLAGS += -Idrivers/media/common/tuners/ diff --git a/drivers/staging/cxd2099/TODO b/drivers/staging/cxd2099/TODO new file mode 100644 index 000000000000..375bb6f8ee2c --- /dev/null +++ b/drivers/staging/cxd2099/TODO @@ -0,0 +1,12 @@ +For now, data is passed through '/dev/dvb/adapterX/sec0': + - Encrypted data must be written to 'sec0'. + - Decrypted data can be read from 'sec0'. + - Setup the CAM using device 'ca0'. + +But this is wrong. There are some discussions about the proper way for +doing it, as seen at: + http://www.mail-archive.com/linux-media@vger.kernel.org/msg22196.html + +While there's no proper fix for it, the driver should be kept in staging. + +Patches should be submitted to: linux-media@vger.kernel.org. diff --git a/drivers/staging/cxd2099/cxd2099.c b/drivers/staging/cxd2099/cxd2099.c new file mode 100644 index 000000000000..b49186c74eb3 --- /dev/null +++ b/drivers/staging/cxd2099/cxd2099.c @@ -0,0 +1,574 @@ +/* + * cxd2099.c: Driver for the CXD2099AR Common Interface Controller + * + * Copyright (C) 2010 DigitalDevices UG + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/version.h> +#include <linux/slab.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/moduleparam.h> +#include <linux/init.h> +#include <linux/i2c.h> +#include <linux/wait.h> +#include <linux/delay.h> +#include <linux/mutex.h> +#include <linux/io.h> + +#include "cxd2099.h" + +#define MAX_BUFFER_SIZE 248 + +struct cxd { + struct dvb_ca_en50221 en; + + struct i2c_adapter *i2c; + u8 adr; + u8 regs[0x23]; + u8 lastaddress; + u8 clk_reg_f; + u8 clk_reg_b; + int mode; + u32 bitrate; + int ready; + int dr; + int slot_stat; + + u8 amem[1024]; + int amem_read; + + int cammode; + struct mutex lock; +}; + +static int i2c_write_reg(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 data) +{ + u8 m[2] = {reg, data}; + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = m, .len = 2}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + printk(KERN_ERR "Failed to write to I2C register %02x@%02x!\n", + reg, adr); + return -1; + } + return 0; +} + +static int i2c_write(struct i2c_adapter *adapter, u8 adr, + u8 *data, u8 len) +{ + struct i2c_msg msg = {.addr = adr, .flags = 0, .buf = data, .len = len}; + + if (i2c_transfer(adapter, &msg, 1) != 1) { + printk(KERN_ERR "Failed to write to I2C!\n"); + return -1; + } + return 0; +} + +static int i2c_read_reg(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 *val) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1 }, + {.addr = adr, .flags = I2C_M_RD, + .buf = val, .len = 1 } }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + printk(KERN_ERR "error in i2c_read_reg\n"); + return -1; + } + return 0; +} + +static int i2c_read(struct i2c_adapter *adapter, u8 adr, + u8 reg, u8 *data, u8 n) +{ + struct i2c_msg msgs[2] = {{.addr = adr, .flags = 0, + .buf = ®, .len = 1 }, + {.addr = adr, .flags = I2C_M_RD, + .buf = data, .len = n } }; + + if (i2c_transfer(adapter, msgs, 2) != 2) { + printk(KERN_ERR "error in i2c_read\n"); + return -1; + } + return 0; +} + +static int read_block(struct cxd *ci, u8 adr, u8 *data, u8 n) +{ + int status; + + status = i2c_write_reg(ci->i2c, ci->adr, 0, adr); + if (!status) { + ci->lastaddress = adr; + status = i2c_read(ci->i2c, ci->adr, 1, data, n); + } + return status; +} + +static int read_reg(struct cxd *ci, u8 reg, u8 *val) +{ + return read_block(ci, reg, val, 1); +} + + +static int read_pccard(struct cxd *ci, u16 address, u8 *data, u8 n) +{ + int status; + u8 addr[3] = { 2, address&0xff, address>>8 }; + + status = i2c_write(ci->i2c, ci->adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->adr, 3, data, n); + return status; +} + +static int write_pccard(struct cxd *ci, u16 address, u8 *data, u8 n) +{ + int status; + u8 addr[3] = { 2, address&0xff, address>>8 }; + + status = i2c_write(ci->i2c, ci->adr, addr, 3); + if (!status) { + u8 buf[256] = {3}; + memcpy(buf+1, data, n); + status = i2c_write(ci->i2c, ci->adr, buf, n+1); + } + return status; +} + +static int read_io(struct cxd *ci, u16 address, u8 *val) +{ + int status; + u8 addr[3] = { 2, address&0xff, address>>8 }; + + status = i2c_write(ci->i2c, ci->adr, addr, 3); + if (!status) + status = i2c_read(ci->i2c, ci->adr, 3, val, 1); + return status; +} + +static int write_io(struct cxd *ci, u16 address, u8 val) +{ + int status; + u8 addr[3] = { 2, address&0xff, address>>8 }; + u8 buf[2] = { 3, val }; + + status = i2c_write(ci->i2c, ci->adr, addr, 3); + if (!status) + status = i2c_write(ci->i2c, ci->adr, buf, 2); + + return status; +} + + +static int write_regm(struct cxd *ci, u8 reg, u8 val, u8 mask) +{ + int status; + + status = i2c_write_reg(ci->i2c, ci->adr, 0, reg); + if (!status && reg >= 6 && reg <= 8 && mask != 0xff) + status = i2c_read_reg(ci->i2c, ci->adr, 1, &ci->regs[reg]); + ci->regs[reg] = (ci->regs[reg]&(~mask))|val; + if (!status) { + ci->lastaddress = reg; + status = i2c_write_reg(ci->i2c, ci->adr, 1, ci->regs[reg]); + } + if (reg == 0x20) + ci->regs[reg] &= 0x7f; + return status; +} + +static int write_reg(struct cxd *ci, u8 reg, u8 val) +{ + return write_regm(ci, reg, val, 0xff); +} + +#ifdef BUFFER_MODE +static int write_block(struct cxd *ci, u8 adr, u8 *data, int n) +{ + int status; + u8 buf[256] = {1}; + + status = i2c_write_reg(ci->i2c, ci->adr, 0, adr); + if (!status) { + ci->lastaddress = adr; + memcpy(buf+1, data, n); + status = i2c_write(ci->i2c, ci->adr, buf, n+1); + } + return status; +} +#endif + +static void set_mode(struct cxd *ci, int mode) +{ + if (mode == ci->mode) + return; + + switch (mode) { + case 0x00: /* IO mem */ + write_regm(ci, 0x06, 0x00, 0x07); + break; + case 0x01: /* ATT mem */ + write_regm(ci, 0x06, 0x02, 0x07); + break; + default: + break; + } + ci->mode = mode; +} + +static void cam_mode(struct cxd *ci, int mode) +{ + if (mode == ci->cammode) + return; + + switch (mode) { + case 0x00: + write_regm(ci, 0x20, 0x80, 0x80); + break; + case 0x01: + printk(KERN_INFO "enable cam buffer mode\n"); + /* write_reg(ci, 0x0d, 0x00); */ + /* write_reg(ci, 0x0e, 0x01); */ + write_regm(ci, 0x08, 0x40, 0x40); + /* read_reg(ci, 0x12, &dummy); */ + write_regm(ci, 0x08, 0x80, 0x80); + break; + default: + break; + } + ci->cammode = mode; +} + + + +#define CHK_ERROR(s) if ((status = s)) break + +static int init(struct cxd *ci) +{ + int status; + + mutex_lock(&ci->lock); + ci->mode = -1; + do { + CHK_ERROR(write_reg(ci, 0x00, 0x00)); + CHK_ERROR(write_reg(ci, 0x01, 0x00)); + CHK_ERROR(write_reg(ci, 0x02, 0x10)); + CHK_ERROR(write_reg(ci, 0x03, 0x00)); + CHK_ERROR(write_reg(ci, 0x05, 0xFF)); + CHK_ERROR(write_reg(ci, 0x06, 0x1F)); + CHK_ERROR(write_reg(ci, 0x07, 0x1F)); + CHK_ERROR(write_reg(ci, 0x08, 0x28)); + CHK_ERROR(write_reg(ci, 0x14, 0x20)); + + CHK_ERROR(write_reg(ci, 0x09, 0x4D)); /* Input Mode C, BYPass Serial, TIVAL = low, MSB */ + CHK_ERROR(write_reg(ci, 0x0A, 0xA7)); /* TOSTRT = 8, Mode B (gated clock), falling Edge, Serial, POL=HIGH, MSB */ + + /* Sync detector */ + CHK_ERROR(write_reg(ci, 0x0B, 0x33)); + CHK_ERROR(write_reg(ci, 0x0C, 0x33)); + + CHK_ERROR(write_regm(ci, 0x14, 0x00, 0x0F)); + CHK_ERROR(write_reg(ci, 0x15, ci->clk_reg_b)); + CHK_ERROR(write_regm(ci, 0x16, 0x00, 0x0F)); + CHK_ERROR(write_reg(ci, 0x17, ci->clk_reg_f)); + + CHK_ERROR(write_reg(ci, 0x20, 0x28)); /* Integer Divider, Falling Edge, Internal Sync, */ + CHK_ERROR(write_reg(ci, 0x21, 0x00)); /* MCLKI = TICLK/8 */ + CHK_ERROR(write_reg(ci, 0x22, 0x07)); /* MCLKI = TICLK/8 */ + + + CHK_ERROR(write_regm(ci, 0x20, 0x80, 0x80)); /* Reset CAM state machine */ + + CHK_ERROR(write_regm(ci, 0x03, 0x02, 02)); /* Enable IREQA Interrupt */ + CHK_ERROR(write_reg(ci, 0x01, 0x04)); /* Enable CD Interrupt */ + CHK_ERROR(write_reg(ci, 0x00, 0x31)); /* Enable TS1,Hot Swap,Slot A */ + CHK_ERROR(write_regm(ci, 0x09, 0x08, 0x08)); /* Put TS in bypass */ + ci->cammode = -1; +#ifdef BUFFER_MODE + cam_mode(ci, 0); +#endif + } while (0); + mutex_unlock(&ci->lock); + + return 0; +} + + +static int read_attribute_mem(struct dvb_ca_en50221 *ca, + int slot, int address) +{ + struct cxd *ci = ca->data; + u8 val; + mutex_lock(&ci->lock); + set_mode(ci, 1); + read_pccard(ci, address, &val, 1); + mutex_unlock(&ci->lock); + return val; +} + + +static int write_attribute_mem(struct dvb_ca_en50221 *ca, int slot, + int address, u8 value) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + set_mode(ci, 1); + write_pccard(ci, address, &value, 1); + mutex_unlock(&ci->lock); + return 0; +} + +static int read_cam_control(struct dvb_ca_en50221 *ca, + int slot, u8 address) +{ + struct cxd *ci = ca->data; + u8 val; + + mutex_lock(&ci->lock); + set_mode(ci, 0); + read_io(ci, address, &val); + mutex_unlock(&ci->lock); + return val; +} + +static int write_cam_control(struct dvb_ca_en50221 *ca, int slot, + u8 address, u8 value) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + set_mode(ci, 0); + write_io(ci, address, value); + mutex_unlock(&ci->lock); + return 0; +} + +static int slot_reset(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + cam_mode(ci, 0); + write_reg(ci, 0x00, 0x21); + write_reg(ci, 0x06, 0x1F); + write_reg(ci, 0x00, 0x31); + write_regm(ci, 0x20, 0x80, 0x80); + write_reg(ci, 0x03, 0x02); + ci->ready = 0; + ci->mode = -1; + { + int i; + for (i = 0; i < 100; i++) { + msleep(10); + if (ci->ready) + break; + } + } + mutex_unlock(&ci->lock); + /* msleep(500); */ + return 0; +} + +static int slot_shutdown(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + printk(KERN_INFO "slot_shutdown\n"); + mutex_lock(&ci->lock); + /* write_regm(ci, 0x09, 0x08, 0x08); */ + write_regm(ci, 0x20, 0x80, 0x80); + write_regm(ci, 0x06, 0x07, 0x07); + ci->mode = -1; + mutex_unlock(&ci->lock); + return 0; /* shutdown(ci); */ +} + +static int slot_ts_enable(struct dvb_ca_en50221 *ca, int slot) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + write_regm(ci, 0x09, 0x00, 0x08); + set_mode(ci, 0); +#ifdef BUFFER_MODE + cam_mode(ci, 1); +#endif + mutex_unlock(&ci->lock); + return 0; +} + + +static int campoll(struct cxd *ci) +{ + u8 istat; + + read_reg(ci, 0x04, &istat); + if (!istat) + return 0; + write_reg(ci, 0x05, istat); + + if (istat&0x40) { + ci->dr = 1; + printk(KERN_INFO "DR\n"); + } + if (istat&0x20) + printk(KERN_INFO "WC\n"); + + if (istat&2) { + u8 slotstat; + + read_reg(ci, 0x01, &slotstat); + if (!(2&slotstat)) { + if (!ci->slot_stat) { + ci->slot_stat |= DVB_CA_EN50221_POLL_CAM_PRESENT; + write_regm(ci, 0x03, 0x08, 0x08); + } + + } else { + if (ci->slot_stat) { + ci->slot_stat = 0; + write_regm(ci, 0x03, 0x00, 0x08); + printk(KERN_INFO "NO CAM\n"); + ci->ready = 0; + } + } + if (istat&8 && ci->slot_stat == DVB_CA_EN50221_POLL_CAM_PRESENT) { + ci->ready = 1; + ci->slot_stat |= DVB_CA_EN50221_POLL_CAM_READY; + printk(KERN_INFO "READY\n"); + } + } + return 0; +} + + +static int poll_slot_status(struct dvb_ca_en50221 *ca, int slot, int open) +{ + struct cxd *ci = ca->data; + u8 slotstat; + + mutex_lock(&ci->lock); + campoll(ci); + read_reg(ci, 0x01, &slotstat); + mutex_unlock(&ci->lock); + + return ci->slot_stat; +} + +#ifdef BUFFER_MODE +static int read_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) +{ + struct cxd *ci = ca->data; + u8 msb, lsb; + u16 len; + + mutex_lock(&ci->lock); + campoll(ci); + mutex_unlock(&ci->lock); + + printk(KERN_INFO "read_data\n"); + if (!ci->dr) + return 0; + + mutex_lock(&ci->lock); + read_reg(ci, 0x0f, &msb); + read_reg(ci, 0x10, &lsb); + len = (msb<<8)|lsb; + read_block(ci, 0x12, ebuf, len); + ci->dr = 0; + mutex_unlock(&ci->lock); + + return len; +} + +static int write_data(struct dvb_ca_en50221 *ca, int slot, u8 *ebuf, int ecount) +{ + struct cxd *ci = ca->data; + + mutex_lock(&ci->lock); + printk(KERN_INFO "write_data %d\n", ecount); + write_reg(ci, 0x0d, ecount>>8); + write_reg(ci, 0x0e, ecount&0xff); + write_block(ci, 0x11, ebuf, ecount); + mutex_unlock(&ci->lock); + return ecount; +} +#endif + +static struct dvb_ca_en50221 en_templ = { + .read_attribute_mem = read_attribute_mem, + .write_attribute_mem = write_attribute_mem, + .read_cam_control = read_cam_control, + .write_cam_control = write_cam_control, + .slot_reset = slot_reset, + .slot_shutdown = slot_shutdown, + .slot_ts_enable = slot_ts_enable, + .poll_slot_status = poll_slot_status, +#ifdef BUFFER_MODE + .read_data = read_data, + .write_data = write_data, +#endif + +}; + +struct dvb_ca_en50221 *cxd2099_attach(u8 adr, void *priv, + struct i2c_adapter *i2c) +{ + struct cxd *ci = 0; + u32 bitrate = 62000000; + u8 val; + + if (i2c_read_reg(i2c, adr, 0, &val) < 0) { + printk(KERN_ERR "No CXD2099 detected at %02x\n", adr); + return 0; + } + + ci = kmalloc(sizeof(struct cxd), GFP_KERNEL); + if (!ci) + return 0; + memset(ci, 0, sizeof(*ci)); + + mutex_init(&ci->lock); + ci->i2c = i2c; + ci->adr = adr; + ci->lastaddress = 0xff; + ci->clk_reg_b = 0x4a; + ci->clk_reg_f = 0x1b; + ci->bitrate = bitrate; + + memcpy(&ci->en, &en_templ, sizeof(en_templ)); + ci->en.data = ci; + init(ci); + printk(KERN_INFO "Attached CXD2099AR at %02x\n", ci->adr); + return &ci->en; +} +EXPORT_SYMBOL(cxd2099_attach); + +MODULE_DESCRIPTION("cxd2099"); +MODULE_AUTHOR("Ralph Metzler <rjkm@metzlerbros.de>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/cxd2099/cxd2099.h b/drivers/staging/cxd2099/cxd2099.h new file mode 100644 index 000000000000..bed54ff3e30b --- /dev/null +++ b/drivers/staging/cxd2099/cxd2099.h @@ -0,0 +1,41 @@ +/* + * cxd2099.h: Driver for the CXD2099AR Common Interface Controller + * + * Copyright (C) 2010 DigitalDevices UG + * + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * version 2 only, as published by the Free Software Foundation. + * + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html + */ + +#ifndef _CXD2099_H_ +#define _CXD2099_H_ + +#include <dvb_ca_en50221.h> + +#if defined(CONFIG_DVB_CXD2099) || \ + (defined(CONFIG_DVB_CXD2099_MODULE) && defined(MODULE)) +struct dvb_ca_en50221 *cxd2099_attach(u8 adr, void *priv, struct i2c_adapter *i2c); +#else +static inline struct dvb_ca_en50221 *cxd2099_attach(u8 adr, void *priv, struct i2c_adapter *i2c) +{ + printk(KERN_WARNING "%s: driver disabled by Kconfig\n", __func__); + return NULL; +} +#endif + +#endif diff --git a/drivers/target/target_core_tmr.c b/drivers/target/target_core_tmr.c index 158cecbec718..4a109835e420 100644 --- a/drivers/target/target_core_tmr.c +++ b/drivers/target/target_core_tmr.c @@ -282,6 +282,9 @@ int core_tmr_lun_reset( atomic_set(&task->task_active, 0); atomic_set(&task->task_stop, 0); + } else { + if (atomic_read(&task->task_execute_queue) != 0) + transport_remove_task_from_execute_queue(task, dev); } __transport_stop_task_timer(task, &flags); @@ -301,6 +304,7 @@ int core_tmr_lun_reset( DEBUG_LR("LUN_RESET: got t_transport_active = 1 for" " task: %p, t_fe_count: %d dev: %p\n", task, fe_count, dev); + atomic_set(&T_TASK(cmd)->t_transport_aborted, 1); spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); core_tmr_handle_tas_abort(tmr_nacl, cmd, tas, fe_count); @@ -310,6 +314,7 @@ int core_tmr_lun_reset( } DEBUG_LR("LUN_RESET: Got t_transport_active = 0 for task: %p," " t_fe_count: %d dev: %p\n", task, fe_count, dev); + atomic_set(&T_TASK(cmd)->t_transport_aborted, 1); spin_unlock_irqrestore(&T_TASK(cmd)->t_state_lock, flags); core_tmr_handle_tas_abort(tmr_nacl, cmd, tas, fe_count); diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 236e22d8cfae..4bbf6c147f89 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -1207,7 +1207,7 @@ transport_get_task_from_execute_queue(struct se_device *dev) * * */ -static void transport_remove_task_from_execute_queue( +void transport_remove_task_from_execute_queue( struct se_task *task, struct se_device *dev) { @@ -5549,7 +5549,8 @@ static void transport_generic_wait_for_tasks( atomic_set(&T_TASK(cmd)->transport_lun_stop, 0); } - if (!atomic_read(&T_TASK(cmd)->t_transport_active)) + if (!atomic_read(&T_TASK(cmd)->t_transport_active) || + atomic_read(&T_TASK(cmd)->t_transport_aborted)) goto remove; atomic_set(&T_TASK(cmd)->t_transport_stop, 1); @@ -5956,6 +5957,9 @@ static void transport_processing_shutdown(struct se_device *dev) atomic_set(&task->task_active, 0); atomic_set(&task->task_stop, 0); + } else { + if (atomic_read(&task->task_execute_queue) != 0) + transport_remove_task_from_execute_queue(task, dev); } __transport_stop_task_timer(task, &flags); diff --git a/drivers/tty/serial/Kconfig b/drivers/tty/serial/Kconfig index 2b8334601c8b..532cdd6fa6fc 100644 --- a/drivers/tty/serial/Kconfig +++ b/drivers/tty/serial/Kconfig @@ -537,8 +537,8 @@ config SERIAL_S3C6400 config SERIAL_S5PV210 tristate "Samsung S5PV210 Serial port support" - depends on SERIAL_SAMSUNG && (CPU_S5PV210 || CPU_S5P6442 || CPU_S5PV310) - select SERIAL_SAMSUNG_UARTS_4 if (CPU_S5PV210 || CPU_S5PV310) + depends on SERIAL_SAMSUNG && (CPU_S5PV210 || CPU_S5P6442 || CPU_EXYNOS4210) + select SERIAL_SAMSUNG_UARTS_4 if (CPU_S5PV210 || CPU_EXYNOS4210) default y help Serial port support for Samsung's S5P Family of SoC's @@ -1110,29 +1110,6 @@ config SERIAL_PMACZILOG_CONSOLE on your (Power)Mac as the console, you can do so by answering Y to this option. -config SERIAL_LH7A40X - tristate "Sharp LH7A40X embedded UART support" - depends on ARM && ARCH_LH7A40X - select SERIAL_CORE - help - This enables support for the three on-board UARTs of the - Sharp LH7A40X series CPUs. Choose Y or M. - -config SERIAL_LH7A40X_CONSOLE - bool "Support for console on Sharp LH7A40X serial port" - depends on SERIAL_LH7A40X=y - select SERIAL_CORE_CONSOLE - help - Say Y here if you wish to use one of the serial ports as the - system console--the system console is the device which - receives all kernel messages and warnings and which allows - logins in single user mode. - - Even if you say Y here, the currently visible framebuffer console - (/dev/tty0) will still be used as the default system console, but - you can alter that using a kernel command line, for example - "console=ttyAM1". - config SERIAL_CPM tristate "CPM SCC/SMC serial port support" depends on CPM2 || 8xx @@ -1596,4 +1573,19 @@ config SERIAL_PCH_UART This driver is for PCH(Platform controller Hub) UART of Intel EG20T which is an IOH(Input/Output Hub) for x86 embedded processor. Enabling PCH_DMA, this PCH UART works as DMA mode. + +config SERIAL_MXS_AUART + depends on ARCH_MXS + tristate "MXS AUART support" + select SERIAL_CORE + help + This driver supports the MXS Application UART (AUART) port. + +config SERIAL_MXS_AUART_CONSOLE + bool "MXS AUART console support" + depends on SERIAL_MXS_AUART=y + select SERIAL_CORE_CONSOLE + help + Enable a MXS AUART port to be the system console. + endmenu diff --git a/drivers/tty/serial/Makefile b/drivers/tty/serial/Makefile index 8ea92e9c73b0..cf4a43411d36 100644 --- a/drivers/tty/serial/Makefile +++ b/drivers/tty/serial/Makefile @@ -54,7 +54,6 @@ obj-$(CONFIG_SERIAL_68328) += 68328serial.o obj-$(CONFIG_SERIAL_68360) += 68360serial.o obj-$(CONFIG_SERIAL_MCF) += mcf.o obj-$(CONFIG_SERIAL_PMACZILOG) += pmac_zilog.o -obj-$(CONFIG_SERIAL_LH7A40X) += serial_lh7a40x.o obj-$(CONFIG_SERIAL_DZ) += dz.o obj-$(CONFIG_SERIAL_ZS) += zs.o obj-$(CONFIG_SERIAL_SH_SCI) += sh-sci.o @@ -92,3 +91,4 @@ obj-$(CONFIG_SERIAL_MRST_MAX3110) += mrst_max3110.o obj-$(CONFIG_SERIAL_MFD_HSU) += mfd.o obj-$(CONFIG_SERIAL_IFX6X60) += ifx6x60.o obj-$(CONFIG_SERIAL_PCH_UART) += pch_uart.o +obj-$(CONFIG_SERIAL_MXS_AUART) += mxs-auart.o diff --git a/drivers/tty/serial/amba-pl010.c b/drivers/tty/serial/amba-pl010.c index 2904aa044126..d742dd2c525c 100644 --- a/drivers/tty/serial/amba-pl010.c +++ b/drivers/tty/serial/amba-pl010.c @@ -676,7 +676,7 @@ static struct uart_driver amba_reg = { .cons = AMBA_CONSOLE, }; -static int pl010_probe(struct amba_device *dev, struct amba_id *id) +static int pl010_probe(struct amba_device *dev, const struct amba_id *id) { struct uart_amba_port *uap; void __iomem *base; diff --git a/drivers/tty/serial/amba-pl011.c b/drivers/tty/serial/amba-pl011.c index e76d7d000128..0dae46f91021 100644 --- a/drivers/tty/serial/amba-pl011.c +++ b/drivers/tty/serial/amba-pl011.c @@ -1349,7 +1349,7 @@ static struct uart_driver amba_reg = { .cons = AMBA_CONSOLE, }; -static int pl011_probe(struct amba_device *dev, struct amba_id *id) +static int pl011_probe(struct amba_device *dev, const struct amba_id *id) { struct uart_amba_port *uap; struct vendor_data *vendor = id->data; diff --git a/drivers/tty/serial/msm_serial.c b/drivers/tty/serial/msm_serial.c index 8e43a7b69e64..bfee9b4c6661 100644 --- a/drivers/tty/serial/msm_serial.c +++ b/drivers/tty/serial/msm_serial.c @@ -3,6 +3,7 @@ * * Copyright (C) 2007 Google, Inc. * Author: Robert Love <rlove@google.com> + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -31,6 +32,7 @@ #include <linux/serial.h> #include <linux/clk.h> #include <linux/platform_device.h> +#include <linux/delay.h> #include "msm_serial.h" @@ -38,9 +40,20 @@ struct msm_port { struct uart_port uart; char name[16]; struct clk *clk; + struct clk *pclk; unsigned int imr; + unsigned int *gsbi_base; + int is_uartdm; + unsigned int old_snap_state; }; +static inline void wait_for_xmitr(struct uart_port *port, int bits) +{ + if (!(msm_read(port, UART_SR) & UART_SR_TX_EMPTY)) + while ((msm_read(port, UART_ISR) & bits) != bits) + cpu_relax(); +} + static void msm_stop_tx(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); @@ -73,6 +86,61 @@ static void msm_enable_ms(struct uart_port *port) msm_write(port, msm_port->imr, UART_IMR); } +static void handle_rx_dm(struct uart_port *port, unsigned int misr) +{ + struct tty_struct *tty = port->state->port.tty; + unsigned int sr; + int count = 0; + struct msm_port *msm_port = UART_TO_MSM(port); + + if ((msm_read(port, UART_SR) & UART_SR_OVERRUN)) { + port->icount.overrun++; + tty_insert_flip_char(tty, 0, TTY_OVERRUN); + msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR); + } + + if (misr & UART_IMR_RXSTALE) { + count = msm_read(port, UARTDM_RX_TOTAL_SNAP) - + msm_port->old_snap_state; + msm_port->old_snap_state = 0; + } else { + count = 4 * (msm_read(port, UART_RFWR)); + msm_port->old_snap_state += count; + } + + /* TODO: Precise error reporting */ + + port->icount.rx += count; + + while (count > 0) { + unsigned int c; + + sr = msm_read(port, UART_SR); + if ((sr & UART_SR_RX_READY) == 0) { + msm_port->old_snap_state -= count; + break; + } + c = msm_read(port, UARTDM_RF); + if (sr & UART_SR_RX_BREAK) { + port->icount.brk++; + if (uart_handle_break(port)) + continue; + } else if (sr & UART_SR_PAR_FRAME_ERR) + port->icount.frame++; + + /* TODO: handle sysrq */ + tty_insert_flip_string(tty, (char *) &c, + (count > 4) ? 4 : count); + count -= 4; + } + + tty_flip_buffer_push(tty); + if (misr & (UART_IMR_RXSTALE)) + msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR); + msm_write(port, 0xFFFFFF, UARTDM_DMRX); + msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR); +} + static void handle_rx(struct uart_port *port) { struct tty_struct *tty = port->state->port.tty; @@ -121,6 +189,12 @@ static void handle_rx(struct uart_port *port) tty_flip_buffer_push(tty); } +static void reset_dm_count(struct uart_port *port) +{ + wait_for_xmitr(port, UART_ISR_TX_READY); + msm_write(port, 1, UARTDM_NCF_TX); +} + static void handle_tx(struct uart_port *port) { struct circ_buf *xmit = &port->state->xmit; @@ -128,11 +202,18 @@ static void handle_tx(struct uart_port *port) int sent_tx; if (port->x_char) { - msm_write(port, port->x_char, UART_TF); + if (msm_port->is_uartdm) + reset_dm_count(port); + + msm_write(port, port->x_char, + msm_port->is_uartdm ? UARTDM_TF : UART_TF); port->icount.tx++; port->x_char = 0; } + if (msm_port->is_uartdm) + reset_dm_count(port); + while (msm_read(port, UART_SR) & UART_SR_TX_READY) { if (uart_circ_empty(xmit)) { /* disable tx interrupts */ @@ -140,8 +221,11 @@ static void handle_tx(struct uart_port *port) msm_write(port, msm_port->imr, UART_IMR); break; } + msm_write(port, xmit->buf[xmit->tail], + msm_port->is_uartdm ? UARTDM_TF : UART_TF); - msm_write(port, xmit->buf[xmit->tail], UART_TF); + if (msm_port->is_uartdm) + reset_dm_count(port); xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); port->icount.tx++; @@ -169,8 +253,12 @@ static irqreturn_t msm_irq(int irq, void *dev_id) misr = msm_read(port, UART_MISR); msm_write(port, 0, UART_IMR); /* disable interrupt */ - if (misr & (UART_IMR_RXLEV | UART_IMR_RXSTALE)) - handle_rx(port); + if (misr & (UART_IMR_RXLEV | UART_IMR_RXSTALE)) { + if (msm_port->is_uartdm) + handle_rx_dm(port, misr); + else + handle_rx(port); + } if (misr & UART_IMR_TXLEV) handle_tx(port); if (misr & UART_IMR_DELTA_CTS) @@ -192,10 +280,21 @@ static unsigned int msm_get_mctrl(struct uart_port *port) return TIOCM_CAR | TIOCM_CTS | TIOCM_DSR | TIOCM_RTS; } -static void msm_set_mctrl(struct uart_port *port, unsigned int mctrl) + +static void msm_reset(struct uart_port *port) { - unsigned int mr; + /* reset everything */ + msm_write(port, UART_CR_CMD_RESET_RX, UART_CR); + msm_write(port, UART_CR_CMD_RESET_TX, UART_CR); + msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR); + msm_write(port, UART_CR_CMD_RESET_BREAK_INT, UART_CR); + msm_write(port, UART_CR_CMD_RESET_CTS, UART_CR); + msm_write(port, UART_CR_CMD_SET_RFR, UART_CR); +} +void msm_set_mctrl(struct uart_port *port, unsigned int mctrl) +{ + unsigned int mr; mr = msm_read(port, UART_MR1); if (!(mctrl & TIOCM_RTS)) { @@ -219,6 +318,7 @@ static void msm_break_ctl(struct uart_port *port, int break_ctl) static int msm_set_baud_rate(struct uart_port *port, unsigned int baud) { unsigned int baud_code, rxstale, watermark; + struct msm_port *msm_port = UART_TO_MSM(port); switch (baud) { case 300: @@ -273,6 +373,9 @@ static int msm_set_baud_rate(struct uart_port *port, unsigned int baud) break; } + if (msm_port->is_uartdm) + msm_write(port, UART_CR_CMD_RESET_RX, UART_CR); + msm_write(port, baud_code, UART_CSR); /* RX stale watermark */ @@ -288,25 +391,23 @@ static int msm_set_baud_rate(struct uart_port *port, unsigned int baud) /* set TX watermark */ msm_write(port, 10, UART_TFWR); + if (msm_port->is_uartdm) { + msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR); + msm_write(port, 0xFFFFFF, UARTDM_DMRX); + msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR); + } + return baud; } -static void msm_reset(struct uart_port *port) -{ - /* reset everything */ - msm_write(port, UART_CR_CMD_RESET_RX, UART_CR); - msm_write(port, UART_CR_CMD_RESET_TX, UART_CR); - msm_write(port, UART_CR_CMD_RESET_ERR, UART_CR); - msm_write(port, UART_CR_CMD_RESET_BREAK_INT, UART_CR); - msm_write(port, UART_CR_CMD_RESET_CTS, UART_CR); - msm_write(port, UART_CR_CMD_SET_RFR, UART_CR); -} static void msm_init_clock(struct uart_port *port) { struct msm_port *msm_port = UART_TO_MSM(port); clk_enable(msm_port->clk); + if (!IS_ERR(msm_port->pclk)) + clk_enable(msm_port->pclk); msm_serial_set_mnd_regs(port); } @@ -347,15 +448,31 @@ static int msm_startup(struct uart_port *port) msm_write(port, data, UART_IPR); } - msm_reset(port); + data = 0; + if (!port->cons || (port->cons && !(port->cons->flags & CON_ENABLED))) { + msm_write(port, UART_CR_CMD_PROTECTION_EN, UART_CR); + msm_reset(port); + data = UART_CR_TX_ENABLE; + } + + data |= UART_CR_RX_ENABLE; + msm_write(port, data, UART_CR); /* enable TX & RX */ - msm_write(port, 0x05, UART_CR); /* enable TX & RX */ + /* Make sure IPR is not 0 to start with*/ + if (msm_port->is_uartdm) + msm_write(port, UART_IPR_STALE_LSB, UART_IPR); /* turn on RX and CTS interrupts */ msm_port->imr = UART_IMR_RXLEV | UART_IMR_RXSTALE | UART_IMR_CURRENT_CTS; - msm_write(port, msm_port->imr, UART_IMR); + if (msm_port->is_uartdm) { + msm_write(port, 0xFFFFFF, UARTDM_DMRX); + msm_write(port, UART_CR_CMD_RESET_STALE_INT, UART_CR); + msm_write(port, UART_CR_CMD_STALE_EVENT_ENABLE, UART_CR); + } + + msm_write(port, msm_port->imr, UART_IMR); return 0; } @@ -384,7 +501,7 @@ static void msm_set_termios(struct uart_port *port, struct ktermios *termios, baud = msm_set_baud_rate(port, baud); if (tty_termios_baud_rate(termios)) tty_termios_encode_baud_rate(termios, baud, baud); - + /* calculate parity */ mr = msm_read(port, UART_MR2); mr &= ~UART_MR2_PARITY_MODE; @@ -454,48 +571,105 @@ static const char *msm_type(struct uart_port *port) static void msm_release_port(struct uart_port *port) { struct platform_device *pdev = to_platform_device(port->dev); - struct resource *resource; + struct msm_port *msm_port = UART_TO_MSM(port); + struct resource *uart_resource; + struct resource *gsbi_resource; resource_size_t size; - resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (unlikely(!resource)) + uart_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!uart_resource)) return; - size = resource->end - resource->start + 1; + size = resource_size(uart_resource); release_mem_region(port->mapbase, size); iounmap(port->membase); port->membase = NULL; + + if (msm_port->gsbi_base) { + iowrite32(GSBI_PROTOCOL_IDLE, msm_port->gsbi_base + + GSBI_CONTROL); + + gsbi_resource = platform_get_resource_byname(pdev, + IORESOURCE_MEM, + "gsbi_resource"); + + if (unlikely(!gsbi_resource)) + return; + + size = resource_size(gsbi_resource); + release_mem_region(gsbi_resource->start, size); + iounmap(msm_port->gsbi_base); + msm_port->gsbi_base = NULL; + } } static int msm_request_port(struct uart_port *port) { + struct msm_port *msm_port = UART_TO_MSM(port); struct platform_device *pdev = to_platform_device(port->dev); - struct resource *resource; + struct resource *uart_resource; + struct resource *gsbi_resource; resource_size_t size; + int ret; - resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); - if (unlikely(!resource)) + uart_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uart_resource"); + if (unlikely(!uart_resource)) return -ENXIO; - size = resource->end - resource->start + 1; - if (unlikely(!request_mem_region(port->mapbase, size, "msm_serial"))) + size = resource_size(uart_resource); + + if (!request_mem_region(port->mapbase, size, "msm_serial")) return -EBUSY; port->membase = ioremap(port->mapbase, size); if (!port->membase) { - release_mem_region(port->mapbase, size); - return -EBUSY; + ret = -EBUSY; + goto fail_release_port; + } + + gsbi_resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "gsbi_resource"); + /* Is this a GSBI-based port? */ + if (gsbi_resource) { + size = resource_size(gsbi_resource); + + if (!request_mem_region(gsbi_resource->start, size, + "msm_serial")) { + ret = -EBUSY; + goto fail_release_port; + } + + msm_port->gsbi_base = ioremap(gsbi_resource->start, size); + if (!msm_port->gsbi_base) { + ret = -EBUSY; + goto fail_release_gsbi; + } } return 0; + +fail_release_gsbi: + release_mem_region(gsbi_resource->start, size); +fail_release_port: + release_mem_region(port->mapbase, size); + return ret; } static void msm_config_port(struct uart_port *port, int flags) { + struct msm_port *msm_port = UART_TO_MSM(port); + int ret; if (flags & UART_CONFIG_TYPE) { port->type = PORT_MSM; - msm_request_port(port); + ret = msm_request_port(port); + if (ret) + return; } + + if (msm_port->is_uartdm) + iowrite32(GSBI_PROTOCOL_UART, msm_port->gsbi_base + + GSBI_CONTROL); } static int msm_verify_port(struct uart_port *port, struct serial_struct *ser) @@ -515,9 +689,13 @@ static void msm_power(struct uart_port *port, unsigned int state, switch (state) { case 0: clk_enable(msm_port->clk); + if (!IS_ERR(msm_port->pclk)) + clk_enable(msm_port->pclk); break; case 3: clk_disable(msm_port->clk); + if (!IS_ERR(msm_port->pclk)) + clk_disable(msm_port->pclk); break; default: printk(KERN_ERR "msm_serial: Unknown PM state %d\n", state); @@ -550,7 +728,7 @@ static struct msm_port msm_uart_ports[] = { .iotype = UPIO_MEM, .ops = &msm_uart_pops, .flags = UPF_BOOT_AUTOCONF, - .fifosize = 512, + .fifosize = 64, .line = 0, }, }, @@ -559,7 +737,7 @@ static struct msm_port msm_uart_ports[] = { .iotype = UPIO_MEM, .ops = &msm_uart_pops, .flags = UPF_BOOT_AUTOCONF, - .fifosize = 512, + .fifosize = 64, .line = 1, }, }, @@ -585,9 +763,14 @@ static inline struct uart_port *get_port_from_line(unsigned int line) static void msm_console_putchar(struct uart_port *port, int c) { + struct msm_port *msm_port = UART_TO_MSM(port); + + if (msm_port->is_uartdm) + reset_dm_count(port); + while (!(msm_read(port, UART_SR) & UART_SR_TX_READY)) ; - msm_write(port, c, UART_TF); + msm_write(port, c, msm_port->is_uartdm ? UARTDM_TF : UART_TF); } static void msm_console_write(struct console *co, const char *s, @@ -609,12 +792,14 @@ static void msm_console_write(struct console *co, const char *s, static int __init msm_console_setup(struct console *co, char *options) { struct uart_port *port; + struct msm_port *msm_port; int baud, flow, bits, parity; if (unlikely(co->index >= UART_NR || co->index < 0)) return -ENXIO; port = get_port_from_line(co->index); + msm_port = UART_TO_MSM(port); if (unlikely(!port->membase)) return -ENXIO; @@ -638,6 +823,11 @@ static int __init msm_console_setup(struct console *co, char *options) msm_reset(port); + if (msm_port->is_uartdm) { + msm_write(port, UART_CR_CMD_PROTECTION_EN, UART_CR); + msm_write(port, UART_CR_TX_ENABLE, UART_CR); + } + printk(KERN_INFO "msm_serial: console setup on port #%d\n", port->line); return uart_set_options(port, co, baud, parity, bits, flow); @@ -685,14 +875,32 @@ static int __init msm_serial_probe(struct platform_device *pdev) port->dev = &pdev->dev; msm_port = UART_TO_MSM(port); - msm_port->clk = clk_get(&pdev->dev, "uart_clk"); - if (IS_ERR(msm_port->clk)) - return PTR_ERR(msm_port->clk); + if (platform_get_resource_byname(pdev, IORESOURCE_MEM, "gsbi_resource")) + msm_port->is_uartdm = 1; + else + msm_port->is_uartdm = 0; + + if (msm_port->is_uartdm) { + msm_port->clk = clk_get(&pdev->dev, "gsbi_uart_clk"); + msm_port->pclk = clk_get(&pdev->dev, "gsbi_pclk"); + } else { + msm_port->clk = clk_get(&pdev->dev, "uart_clk"); + msm_port->pclk = ERR_PTR(-ENOENT); + } + + if (unlikely(IS_ERR(msm_port->clk) || (IS_ERR(msm_port->pclk) && + msm_port->is_uartdm))) + return PTR_ERR(msm_port->clk); + + if (msm_port->is_uartdm) + clk_set_rate(msm_port->clk, 7372800); + port->uartclk = clk_get_rate(msm_port->clk); printk(KERN_INFO "uartclk = %d\n", port->uartclk); - resource = platform_get_resource(pdev, IORESOURCE_MEM, 0); + resource = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "uart_resource"); if (unlikely(!resource)) return -ENXIO; port->mapbase = resource->start; diff --git a/drivers/tty/serial/msm_serial.h b/drivers/tty/serial/msm_serial.h index f6ca9ca79e98..9b8dc5d0d855 100644 --- a/drivers/tty/serial/msm_serial.h +++ b/drivers/tty/serial/msm_serial.h @@ -3,6 +3,7 @@ * * Copyright (C) 2007 Google, Inc. * Author: Robert Love <rlove@google.com> + * Copyright (c) 2011, Code Aurora Forum. All rights reserved. * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and @@ -54,6 +55,7 @@ #define UART_CSR_300 0x22 #define UART_TF 0x000C +#define UARTDM_TF 0x0070 #define UART_CR 0x0010 #define UART_CR_CMD_NULL (0 << 4) @@ -64,14 +66,17 @@ #define UART_CR_CMD_START_BREAK (5 << 4) #define UART_CR_CMD_STOP_BREAK (6 << 4) #define UART_CR_CMD_RESET_CTS (7 << 4) +#define UART_CR_CMD_RESET_STALE_INT (8 << 4) #define UART_CR_CMD_PACKET_MODE (9 << 4) #define UART_CR_CMD_MODE_RESET (12 << 4) #define UART_CR_CMD_SET_RFR (13 << 4) #define UART_CR_CMD_RESET_RFR (14 << 4) +#define UART_CR_CMD_PROTECTION_EN (16 << 4) +#define UART_CR_CMD_STALE_EVENT_ENABLE (80 << 4) #define UART_CR_TX_DISABLE (1 << 3) -#define UART_CR_TX_ENABLE (1 << 3) -#define UART_CR_RX_DISABLE (1 << 3) -#define UART_CR_RX_ENABLE (1 << 3) +#define UART_CR_TX_ENABLE (1 << 2) +#define UART_CR_RX_DISABLE (1 << 1) +#define UART_CR_RX_ENABLE (1 << 0) #define UART_IMR 0x0014 #define UART_IMR_TXLEV (1 << 0) @@ -110,9 +115,20 @@ #define UART_SR_RX_FULL (1 << 1) #define UART_SR_RX_READY (1 << 0) -#define UART_RF 0x000C -#define UART_MISR 0x0010 -#define UART_ISR 0x0014 +#define UART_RF 0x000C +#define UARTDM_RF 0x0070 +#define UART_MISR 0x0010 +#define UART_ISR 0x0014 +#define UART_ISR_TX_READY (1 << 7) + +#define GSBI_CONTROL 0x0 +#define GSBI_PROTOCOL_CODE 0x30 +#define GSBI_PROTOCOL_UART 0x40 +#define GSBI_PROTOCOL_IDLE 0x0 + +#define UARTDM_DMRX 0x34 +#define UARTDM_NCF_TX 0x40 +#define UARTDM_RX_TOTAL_SNAP 0x38 #define UART_TO_MSM(uart_port) ((struct msm_port *) uart_port) diff --git a/drivers/tty/serial/mxs-auart.c b/drivers/tty/serial/mxs-auart.c new file mode 100644 index 000000000000..7e02c9c344fe --- /dev/null +++ b/drivers/tty/serial/mxs-auart.c @@ -0,0 +1,798 @@ +/* + * Freescale STMP37XX/STMP378X Application UART driver + * + * Author: dmitry pervushin <dimka@embeddedalley.com> + * + * Copyright 2008-2010 Freescale Semiconductor, Inc. + * Copyright 2008 Embedded Alley Solutions, Inc All Rights Reserved. + * + * The code contained herein is licensed under the GNU General Public + * License. You may obtain a copy of the GNU General Public License + * Version 2 or later at the following locations: + * + * http://www.opensource.org/licenses/gpl-license.html + * http://www.gnu.org/copyleft/gpl.html + */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/console.h> +#include <linux/interrupt.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/wait.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/serial.h> +#include <linux/serial_core.h> +#include <linux/platform_device.h> +#include <linux/device.h> +#include <linux/clk.h> +#include <linux/delay.h> +#include <linux/io.h> + +#include <asm/cacheflush.h> + +#define MXS_AUART_PORTS 5 + +#define AUART_CTRL0 0x00000000 +#define AUART_CTRL0_SET 0x00000004 +#define AUART_CTRL0_CLR 0x00000008 +#define AUART_CTRL0_TOG 0x0000000c +#define AUART_CTRL1 0x00000010 +#define AUART_CTRL1_SET 0x00000014 +#define AUART_CTRL1_CLR 0x00000018 +#define AUART_CTRL1_TOG 0x0000001c +#define AUART_CTRL2 0x00000020 +#define AUART_CTRL2_SET 0x00000024 +#define AUART_CTRL2_CLR 0x00000028 +#define AUART_CTRL2_TOG 0x0000002c +#define AUART_LINECTRL 0x00000030 +#define AUART_LINECTRL_SET 0x00000034 +#define AUART_LINECTRL_CLR 0x00000038 +#define AUART_LINECTRL_TOG 0x0000003c +#define AUART_LINECTRL2 0x00000040 +#define AUART_LINECTRL2_SET 0x00000044 +#define AUART_LINECTRL2_CLR 0x00000048 +#define AUART_LINECTRL2_TOG 0x0000004c +#define AUART_INTR 0x00000050 +#define AUART_INTR_SET 0x00000054 +#define AUART_INTR_CLR 0x00000058 +#define AUART_INTR_TOG 0x0000005c +#define AUART_DATA 0x00000060 +#define AUART_STAT 0x00000070 +#define AUART_DEBUG 0x00000080 +#define AUART_VERSION 0x00000090 +#define AUART_AUTOBAUD 0x000000a0 + +#define AUART_CTRL0_SFTRST (1 << 31) +#define AUART_CTRL0_CLKGATE (1 << 30) + +#define AUART_CTRL2_CTSEN (1 << 15) +#define AUART_CTRL2_RTS (1 << 11) +#define AUART_CTRL2_RXE (1 << 9) +#define AUART_CTRL2_TXE (1 << 8) +#define AUART_CTRL2_UARTEN (1 << 0) + +#define AUART_LINECTRL_BAUD_DIVINT_SHIFT 16 +#define AUART_LINECTRL_BAUD_DIVINT_MASK 0xffff0000 +#define AUART_LINECTRL_BAUD_DIVINT(v) (((v) & 0xffff) << 16) +#define AUART_LINECTRL_BAUD_DIVFRAC_SHIFT 8 +#define AUART_LINECTRL_BAUD_DIVFRAC_MASK 0x00003f00 +#define AUART_LINECTRL_BAUD_DIVFRAC(v) (((v) & 0x3f) << 8) +#define AUART_LINECTRL_WLEN_MASK 0x00000060 +#define AUART_LINECTRL_WLEN(v) (((v) & 0x3) << 5) +#define AUART_LINECTRL_FEN (1 << 4) +#define AUART_LINECTRL_STP2 (1 << 3) +#define AUART_LINECTRL_EPS (1 << 2) +#define AUART_LINECTRL_PEN (1 << 1) +#define AUART_LINECTRL_BRK (1 << 0) + +#define AUART_INTR_RTIEN (1 << 22) +#define AUART_INTR_TXIEN (1 << 21) +#define AUART_INTR_RXIEN (1 << 20) +#define AUART_INTR_CTSMIEN (1 << 17) +#define AUART_INTR_RTIS (1 << 6) +#define AUART_INTR_TXIS (1 << 5) +#define AUART_INTR_RXIS (1 << 4) +#define AUART_INTR_CTSMIS (1 << 1) + +#define AUART_STAT_BUSY (1 << 29) +#define AUART_STAT_CTS (1 << 28) +#define AUART_STAT_TXFE (1 << 27) +#define AUART_STAT_TXFF (1 << 25) +#define AUART_STAT_RXFE (1 << 24) +#define AUART_STAT_OERR (1 << 19) +#define AUART_STAT_BERR (1 << 18) +#define AUART_STAT_PERR (1 << 17) +#define AUART_STAT_FERR (1 << 16) + +static struct uart_driver auart_driver; + +struct mxs_auart_port { + struct uart_port port; + + unsigned int flags; + unsigned int ctrl; + + unsigned int irq; + + struct clk *clk; + struct device *dev; +}; + +static void mxs_auart_stop_tx(struct uart_port *u); + +#define to_auart_port(u) container_of(u, struct mxs_auart_port, port) + +static inline void mxs_auart_tx_chars(struct mxs_auart_port *s) +{ + struct circ_buf *xmit = &s->port.state->xmit; + + while (!(readl(s->port.membase + AUART_STAT) & + AUART_STAT_TXFF)) { + if (s->port.x_char) { + s->port.icount.tx++; + writel(s->port.x_char, + s->port.membase + AUART_DATA); + s->port.x_char = 0; + continue; + } + if (!uart_circ_empty(xmit) && !uart_tx_stopped(&s->port)) { + s->port.icount.tx++; + writel(xmit->buf[xmit->tail], + s->port.membase + AUART_DATA); + xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); + if (uart_circ_chars_pending(xmit) < WAKEUP_CHARS) + uart_write_wakeup(&s->port); + } else + break; + } + if (uart_circ_empty(&(s->port.state->xmit))) + writel(AUART_INTR_TXIEN, + s->port.membase + AUART_INTR_CLR); + else + writel(AUART_INTR_TXIEN, + s->port.membase + AUART_INTR_SET); + + if (uart_tx_stopped(&s->port)) + mxs_auart_stop_tx(&s->port); +} + +static void mxs_auart_rx_char(struct mxs_auart_port *s) +{ + int flag; + u32 stat; + u8 c; + + c = readl(s->port.membase + AUART_DATA); + stat = readl(s->port.membase + AUART_STAT); + + flag = TTY_NORMAL; + s->port.icount.rx++; + + if (stat & AUART_STAT_BERR) { + s->port.icount.brk++; + if (uart_handle_break(&s->port)) + goto out; + } else if (stat & AUART_STAT_PERR) { + s->port.icount.parity++; + } else if (stat & AUART_STAT_FERR) { + s->port.icount.frame++; + } + + /* + * Mask off conditions which should be ingored. + */ + stat &= s->port.read_status_mask; + + if (stat & AUART_STAT_BERR) { + flag = TTY_BREAK; + } else if (stat & AUART_STAT_PERR) + flag = TTY_PARITY; + else if (stat & AUART_STAT_FERR) + flag = TTY_FRAME; + + if (stat & AUART_STAT_OERR) + s->port.icount.overrun++; + + if (uart_handle_sysrq_char(&s->port, c)) + goto out; + + uart_insert_char(&s->port, stat, AUART_STAT_OERR, c, flag); +out: + writel(stat, s->port.membase + AUART_STAT); +} + +static void mxs_auart_rx_chars(struct mxs_auart_port *s) +{ + struct tty_struct *tty = s->port.state->port.tty; + u32 stat = 0; + + for (;;) { + stat = readl(s->port.membase + AUART_STAT); + if (stat & AUART_STAT_RXFE) + break; + mxs_auart_rx_char(s); + } + + writel(stat, s->port.membase + AUART_STAT); + tty_flip_buffer_push(tty); +} + +static int mxs_auart_request_port(struct uart_port *u) +{ + return 0; +} + +static int mxs_auart_verify_port(struct uart_port *u, + struct serial_struct *ser) +{ + if (u->type != PORT_UNKNOWN && u->type != PORT_IMX) + return -EINVAL; + return 0; +} + +static void mxs_auart_config_port(struct uart_port *u, int flags) +{ +} + +static const char *mxs_auart_type(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + return dev_name(s->dev); +} + +static void mxs_auart_release_port(struct uart_port *u) +{ +} + +static void mxs_auart_set_mctrl(struct uart_port *u, unsigned mctrl) +{ + struct mxs_auart_port *s = to_auart_port(u); + + u32 ctrl = readl(u->membase + AUART_CTRL2); + + ctrl &= ~AUART_CTRL2_RTS; + if (mctrl & TIOCM_RTS) + ctrl |= AUART_CTRL2_RTS; + s->ctrl = mctrl; + writel(ctrl, u->membase + AUART_CTRL2); +} + +static u32 mxs_auart_get_mctrl(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + u32 stat = readl(u->membase + AUART_STAT); + int ctrl2 = readl(u->membase + AUART_CTRL2); + u32 mctrl = s->ctrl; + + mctrl &= ~TIOCM_CTS; + if (stat & AUART_STAT_CTS) + mctrl |= TIOCM_CTS; + + if (ctrl2 & AUART_CTRL2_RTS) + mctrl |= TIOCM_RTS; + + return mctrl; +} + +static void mxs_auart_settermios(struct uart_port *u, + struct ktermios *termios, + struct ktermios *old) +{ + u32 bm, ctrl, ctrl2, div; + unsigned int cflag, baud; + + cflag = termios->c_cflag; + + ctrl = AUART_LINECTRL_FEN; + ctrl2 = readl(u->membase + AUART_CTRL2); + + /* byte size */ + switch (cflag & CSIZE) { + case CS5: + bm = 0; + break; + case CS6: + bm = 1; + break; + case CS7: + bm = 2; + break; + case CS8: + bm = 3; + break; + default: + return; + } + + ctrl |= AUART_LINECTRL_WLEN(bm); + + /* parity */ + if (cflag & PARENB) { + ctrl |= AUART_LINECTRL_PEN; + if ((cflag & PARODD) == 0) + ctrl |= AUART_LINECTRL_EPS; + } + + u->read_status_mask = 0; + + if (termios->c_iflag & INPCK) + u->read_status_mask |= AUART_STAT_PERR; + if (termios->c_iflag & (BRKINT | PARMRK)) + u->read_status_mask |= AUART_STAT_BERR; + + /* + * Characters to ignore + */ + u->ignore_status_mask = 0; + if (termios->c_iflag & IGNPAR) + u->ignore_status_mask |= AUART_STAT_PERR; + if (termios->c_iflag & IGNBRK) { + u->ignore_status_mask |= AUART_STAT_BERR; + /* + * If we're ignoring parity and break indicators, + * ignore overruns too (for real raw support). + */ + if (termios->c_iflag & IGNPAR) + u->ignore_status_mask |= AUART_STAT_OERR; + } + + /* + * ignore all characters if CREAD is not set + */ + if (cflag & CREAD) + ctrl2 |= AUART_CTRL2_RXE; + else + ctrl2 &= ~AUART_CTRL2_RXE; + + /* figure out the stop bits requested */ + if (cflag & CSTOPB) + ctrl |= AUART_LINECTRL_STP2; + + /* figure out the hardware flow control settings */ + if (cflag & CRTSCTS) + ctrl2 |= AUART_CTRL2_CTSEN; + else + ctrl2 &= ~AUART_CTRL2_CTSEN; + + /* set baud rate */ + baud = uart_get_baud_rate(u, termios, old, 0, u->uartclk); + div = u->uartclk * 32 / baud; + ctrl |= AUART_LINECTRL_BAUD_DIVFRAC(div & 0x3F); + ctrl |= AUART_LINECTRL_BAUD_DIVINT(div >> 6); + + writel(ctrl, u->membase + AUART_LINECTRL); + writel(ctrl2, u->membase + AUART_CTRL2); +} + +static irqreturn_t mxs_auart_irq_handle(int irq, void *context) +{ + u32 istatus, istat; + struct mxs_auart_port *s = context; + u32 stat = readl(s->port.membase + AUART_STAT); + + istatus = istat = readl(s->port.membase + AUART_INTR); + + if (istat & AUART_INTR_CTSMIS) { + uart_handle_cts_change(&s->port, stat & AUART_STAT_CTS); + writel(AUART_INTR_CTSMIS, + s->port.membase + AUART_INTR_CLR); + istat &= ~AUART_INTR_CTSMIS; + } + + if (istat & (AUART_INTR_RTIS | AUART_INTR_RXIS)) { + mxs_auart_rx_chars(s); + istat &= ~(AUART_INTR_RTIS | AUART_INTR_RXIS); + } + + if (istat & AUART_INTR_TXIS) { + mxs_auart_tx_chars(s); + istat &= ~AUART_INTR_TXIS; + } + + writel(istatus & (AUART_INTR_RTIS + | AUART_INTR_TXIS + | AUART_INTR_RXIS + | AUART_INTR_CTSMIS), + s->port.membase + AUART_INTR_CLR); + + return IRQ_HANDLED; +} + +static void mxs_auart_reset(struct uart_port *u) +{ + int i; + unsigned int reg; + + writel(AUART_CTRL0_SFTRST, u->membase + AUART_CTRL0_CLR); + + for (i = 0; i < 10000; i++) { + reg = readl(u->membase + AUART_CTRL0); + if (!(reg & AUART_CTRL0_SFTRST)) + break; + udelay(3); + } + writel(AUART_CTRL0_CLKGATE, u->membase + AUART_CTRL0_CLR); +} + +static int mxs_auart_startup(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + clk_enable(s->clk); + + writel(AUART_CTRL0_CLKGATE, u->membase + AUART_CTRL0_CLR); + + writel(AUART_CTRL2_UARTEN, u->membase + AUART_CTRL2_SET); + + writel(AUART_INTR_RXIEN | AUART_INTR_RTIEN | AUART_INTR_CTSMIEN, + u->membase + AUART_INTR); + + /* + * Enable fifo so all four bytes of a DMA word are written to + * output (otherwise, only the LSB is written, ie. 1 in 4 bytes) + */ + writel(AUART_LINECTRL_FEN, u->membase + AUART_LINECTRL_SET); + + return 0; +} + +static void mxs_auart_shutdown(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + writel(AUART_CTRL2_UARTEN, u->membase + AUART_CTRL2_CLR); + + writel(AUART_CTRL0_CLKGATE, u->membase + AUART_CTRL0_SET); + + writel(AUART_INTR_RXIEN | AUART_INTR_RTIEN | AUART_INTR_CTSMIEN, + u->membase + AUART_INTR_CLR); + + clk_disable(s->clk); +} + +static unsigned int mxs_auart_tx_empty(struct uart_port *u) +{ + if (readl(u->membase + AUART_STAT) & AUART_STAT_TXFE) + return TIOCSER_TEMT; + else + return 0; +} + +static void mxs_auart_start_tx(struct uart_port *u) +{ + struct mxs_auart_port *s = to_auart_port(u); + + /* enable transmitter */ + writel(AUART_CTRL2_TXE, u->membase + AUART_CTRL2_SET); + + mxs_auart_tx_chars(s); +} + +static void mxs_auart_stop_tx(struct uart_port *u) +{ + writel(AUART_CTRL2_TXE, u->membase + AUART_CTRL2_CLR); +} + +static void mxs_auart_stop_rx(struct uart_port *u) +{ + writel(AUART_CTRL2_RXE, u->membase + AUART_CTRL2_CLR); +} + +static void mxs_auart_break_ctl(struct uart_port *u, int ctl) +{ + if (ctl) + writel(AUART_LINECTRL_BRK, + u->membase + AUART_LINECTRL_SET); + else + writel(AUART_LINECTRL_BRK, + u->membase + AUART_LINECTRL_CLR); +} + +static void mxs_auart_enable_ms(struct uart_port *port) +{ + /* just empty */ +} + +static struct uart_ops mxs_auart_ops = { + .tx_empty = mxs_auart_tx_empty, + .start_tx = mxs_auart_start_tx, + .stop_tx = mxs_auart_stop_tx, + .stop_rx = mxs_auart_stop_rx, + .enable_ms = mxs_auart_enable_ms, + .break_ctl = mxs_auart_break_ctl, + .set_mctrl = mxs_auart_set_mctrl, + .get_mctrl = mxs_auart_get_mctrl, + .startup = mxs_auart_startup, + .shutdown = mxs_auart_shutdown, + .set_termios = mxs_auart_settermios, + .type = mxs_auart_type, + .release_port = mxs_auart_release_port, + .request_port = mxs_auart_request_port, + .config_port = mxs_auart_config_port, + .verify_port = mxs_auart_verify_port, +}; + +static struct mxs_auart_port *auart_port[MXS_AUART_PORTS]; + +#ifdef CONFIG_SERIAL_MXS_AUART_CONSOLE +static void mxs_auart_console_putchar(struct uart_port *port, int ch) +{ + unsigned int to = 1000; + + while (readl(port->membase + AUART_STAT) & AUART_STAT_TXFF) { + if (!to--) + break; + udelay(1); + } + + writel(ch, port->membase + AUART_DATA); +} + +static void +auart_console_write(struct console *co, const char *str, unsigned int count) +{ + struct mxs_auart_port *s; + struct uart_port *port; + unsigned int old_ctrl0, old_ctrl2; + unsigned int to = 1000; + + if (co->index > MXS_AUART_PORTS || co->index < 0) + return; + + s = auart_port[co->index]; + port = &s->port; + + clk_enable(s->clk); + + /* First save the CR then disable the interrupts */ + old_ctrl2 = readl(port->membase + AUART_CTRL2); + old_ctrl0 = readl(port->membase + AUART_CTRL0); + + writel(AUART_CTRL0_CLKGATE, + port->membase + AUART_CTRL0_CLR); + writel(AUART_CTRL2_UARTEN | AUART_CTRL2_TXE, + port->membase + AUART_CTRL2_SET); + + uart_console_write(port, str, count, mxs_auart_console_putchar); + + /* + * Finally, wait for transmitter to become empty + * and restore the TCR + */ + while (readl(port->membase + AUART_STAT) & AUART_STAT_BUSY) { + if (!to--) + break; + udelay(1); + } + + writel(old_ctrl0, port->membase + AUART_CTRL0); + writel(old_ctrl2, port->membase + AUART_CTRL2); + + clk_disable(s->clk); +} + +static void __init +auart_console_get_options(struct uart_port *port, int *baud, + int *parity, int *bits) +{ + unsigned int lcr_h, quot; + + if (!(readl(port->membase + AUART_CTRL2) & AUART_CTRL2_UARTEN)) + return; + + lcr_h = readl(port->membase + AUART_LINECTRL); + + *parity = 'n'; + if (lcr_h & AUART_LINECTRL_PEN) { + if (lcr_h & AUART_LINECTRL_EPS) + *parity = 'e'; + else + *parity = 'o'; + } + + if ((lcr_h & AUART_LINECTRL_WLEN_MASK) == AUART_LINECTRL_WLEN(2)) + *bits = 7; + else + *bits = 8; + + quot = ((readl(port->membase + AUART_LINECTRL) + & AUART_LINECTRL_BAUD_DIVINT_MASK)) + >> (AUART_LINECTRL_BAUD_DIVINT_SHIFT - 6); + quot |= ((readl(port->membase + AUART_LINECTRL) + & AUART_LINECTRL_BAUD_DIVFRAC_MASK)) + >> AUART_LINECTRL_BAUD_DIVFRAC_SHIFT; + if (quot == 0) + quot = 1; + + *baud = (port->uartclk << 2) / quot; +} + +static int __init +auart_console_setup(struct console *co, char *options) +{ + struct mxs_auart_port *s; + int baud = 9600; + int bits = 8; + int parity = 'n'; + int flow = 'n'; + int ret; + + /* + * Check whether an invalid uart number has been specified, and + * if so, search for the first available port that does have + * console support. + */ + if (co->index == -1 || co->index >= ARRAY_SIZE(auart_port)) + co->index = 0; + s = auart_port[co->index]; + if (!s) + return -ENODEV; + + clk_enable(s->clk); + + if (options) + uart_parse_options(options, &baud, &parity, &bits, &flow); + else + auart_console_get_options(&s->port, &baud, &parity, &bits); + + ret = uart_set_options(&s->port, co, baud, parity, bits, flow); + + clk_disable(s->clk); + + return ret; +} + +static struct console auart_console = { + .name = "ttyAPP", + .write = auart_console_write, + .device = uart_console_device, + .setup = auart_console_setup, + .flags = CON_PRINTBUFFER, + .index = -1, + .data = &auart_driver, +}; +#endif + +static struct uart_driver auart_driver = { + .owner = THIS_MODULE, + .driver_name = "ttyAPP", + .dev_name = "ttyAPP", + .major = 0, + .minor = 0, + .nr = MXS_AUART_PORTS, +#ifdef CONFIG_SERIAL_MXS_AUART_CONSOLE + .cons = &auart_console, +#endif +}; + +static int __devinit mxs_auart_probe(struct platform_device *pdev) +{ + struct mxs_auart_port *s; + u32 version; + int ret = 0; + struct resource *r; + + s = kzalloc(sizeof(struct mxs_auart_port), GFP_KERNEL); + if (!s) { + ret = -ENOMEM; + goto out; + } + + s->clk = clk_get(&pdev->dev, NULL); + if (IS_ERR(s->clk)) { + ret = PTR_ERR(s->clk); + goto out_free; + } + + r = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!r) { + ret = -ENXIO; + goto out_free_clk; + } + + s->port.mapbase = r->start; + s->port.membase = ioremap(r->start, resource_size(r)); + s->port.ops = &mxs_auart_ops; + s->port.iotype = UPIO_MEM; + s->port.line = pdev->id < 0 ? 0 : pdev->id; + s->port.fifosize = 16; + s->port.uartclk = clk_get_rate(s->clk); + s->port.type = PORT_IMX; + s->port.dev = s->dev = get_device(&pdev->dev); + + s->flags = 0; + s->ctrl = 0; + + s->irq = platform_get_irq(pdev, 0); + s->port.irq = s->irq; + ret = request_irq(s->irq, mxs_auart_irq_handle, 0, dev_name(&pdev->dev), s); + if (ret) + goto out_free_clk; + + platform_set_drvdata(pdev, s); + + auart_port[pdev->id] = s; + + mxs_auart_reset(&s->port); + + ret = uart_add_one_port(&auart_driver, &s->port); + if (ret) + goto out_free_irq; + + version = readl(s->port.membase + AUART_VERSION); + dev_info(&pdev->dev, "Found APPUART %d.%d.%d\n", + (version >> 24) & 0xff, + (version >> 16) & 0xff, version & 0xffff); + + return 0; + +out_free_irq: + auart_port[pdev->id] = NULL; + free_irq(s->irq, s); +out_free_clk: + clk_put(s->clk); +out_free: + kfree(s); +out: + return ret; +} + +static int __devexit mxs_auart_remove(struct platform_device *pdev) +{ + struct mxs_auart_port *s = platform_get_drvdata(pdev); + + uart_remove_one_port(&auart_driver, &s->port); + + auart_port[pdev->id] = NULL; + + clk_put(s->clk); + free_irq(s->irq, s); + kfree(s); + + return 0; +} + +static struct platform_driver mxs_auart_driver = { + .probe = mxs_auart_probe, + .remove = __devexit_p(mxs_auart_remove), + .driver = { + .name = "mxs-auart", + .owner = THIS_MODULE, + }, +}; + +static int __init mxs_auart_init(void) +{ + int r; + + r = uart_register_driver(&auart_driver); + if (r) + goto out; + + r = platform_driver_register(&mxs_auart_driver); + if (r) + goto out_err; + + return 0; +out_err: + uart_unregister_driver(&auart_driver); +out: + return r; +} + +static void __exit mxs_auart_exit(void) +{ + platform_driver_unregister(&mxs_auart_driver); + uart_unregister_driver(&auart_driver); +} + +module_init(mxs_auart_init); +module_exit(mxs_auart_exit); +MODULE_LICENSE("GPL"); +MODULE_DESCRIPTION("Freescale MXS application uart driver"); diff --git a/drivers/tty/serial/serial_cs.c b/drivers/tty/serial/serial_cs.c index 93760b2ea172..1ef4df9bf7e4 100644 --- a/drivers/tty/serial/serial_cs.c +++ b/drivers/tty/serial/serial_cs.c @@ -712,6 +712,7 @@ static struct pcmcia_device_id serial_ids[] = { PCMCIA_PFC_DEVICE_PROD_ID12(1, "Xircom", "CreditCard Ethernet+Modem II", 0x2e3ee845, 0xeca401bf), PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0032, 0x0e01), PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0032, 0x0a05), + PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0032, 0x0b05), PCMCIA_PFC_DEVICE_MANF_CARD(1, 0x0032, 0x1101), PCMCIA_MFC_DEVICE_MANF_CARD(0, 0x0104, 0x0070), PCMCIA_MFC_DEVICE_MANF_CARD(1, 0x0101, 0x0562), diff --git a/drivers/tty/serial/serial_lh7a40x.c b/drivers/tty/serial/serial_lh7a40x.c deleted file mode 100644 index ea744707c4d6..000000000000 --- a/drivers/tty/serial/serial_lh7a40x.c +++ /dev/null @@ -1,682 +0,0 @@ -/* drivers/serial/serial_lh7a40x.c - * - * Copyright (C) 2004 Coastal Environmental Systems - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - */ - -/* Driver for Sharp LH7A40X embedded serial ports - * - * Based on drivers/char/serial.c, by Linus Torvalds, Theodore Ts'o. - * Based on drivers/serial/amba.c, by Deep Blue Solutions Ltd. - * - * --- - * - * This driver supports the embedded UARTs of the Sharp LH7A40X series - * CPUs. While similar to the 16550 and other UART chips, there is - * nothing close to register compatibility. Moreover, some of the - * modem control lines are not available, either in the chip or they - * are lacking in the board-level implementation. - * - * - Use of SIRDIS - * For simplicity, we disable the IR functions of any UART whenever - * we enable it. - * - */ - - -#if defined(CONFIG_SERIAL_LH7A40X_CONSOLE) && defined(CONFIG_MAGIC_SYSRQ) -#define SUPPORT_SYSRQ -#endif - -#include <linux/module.h> -#include <linux/ioport.h> -#include <linux/init.h> -#include <linux/console.h> -#include <linux/sysrq.h> -#include <linux/tty.h> -#include <linux/tty_flip.h> -#include <linux/serial_core.h> -#include <linux/serial.h> -#include <linux/io.h> - -#include <asm/irq.h> -#include <mach/hardware.h> - -#define DEV_MAJOR 204 -#define DEV_MINOR 16 -#define DEV_NR 3 - -#define ISR_LOOP_LIMIT 256 - -#define UR(p,o) _UR ((p)->membase, o) -#define _UR(b,o) (*((volatile unsigned int*)(((unsigned char*) b) + (o)))) -#define BIT_CLR(p,o,m) UR(p,o) = UR(p,o) & (~(unsigned int)m) -#define BIT_SET(p,o,m) UR(p,o) = UR(p,o) | ( (unsigned int)m) - -#define UART_REG_SIZE 32 - -#define UART_R_DATA (0x00) -#define UART_R_FCON (0x04) -#define UART_R_BRCON (0x08) -#define UART_R_CON (0x0c) -#define UART_R_STATUS (0x10) -#define UART_R_RAWISR (0x14) -#define UART_R_INTEN (0x18) -#define UART_R_ISR (0x1c) - -#define UARTEN (0x01) /* UART enable */ -#define SIRDIS (0x02) /* Serial IR disable (UART1 only) */ - -#define RxEmpty (0x10) -#define TxEmpty (0x80) -#define TxFull (0x20) -#define nRxRdy RxEmpty -#define nTxRdy TxFull -#define TxBusy (0x08) - -#define RxBreak (0x0800) -#define RxOverrunError (0x0400) -#define RxParityError (0x0200) -#define RxFramingError (0x0100) -#define RxError (RxBreak | RxOverrunError | RxParityError | RxFramingError) - -#define DCD (0x04) -#define DSR (0x02) -#define CTS (0x01) - -#define RxInt (0x01) -#define TxInt (0x02) -#define ModemInt (0x04) -#define RxTimeoutInt (0x08) - -#define MSEOI (0x10) - -#define WLEN_8 (0x60) -#define WLEN_7 (0x40) -#define WLEN_6 (0x20) -#define WLEN_5 (0x00) -#define WLEN (0x60) /* Mask for all word-length bits */ -#define STP2 (0x08) -#define PEN (0x02) /* Parity Enable */ -#define EPS (0x04) /* Even Parity Set */ -#define FEN (0x10) /* FIFO Enable */ -#define BRK (0x01) /* Send Break */ - - -struct uart_port_lh7a40x { - struct uart_port port; - unsigned int statusPrev; /* Most recently read modem status */ -}; - -static void lh7a40xuart_stop_tx (struct uart_port* port) -{ - BIT_CLR (port, UART_R_INTEN, TxInt); -} - -static void lh7a40xuart_start_tx (struct uart_port* port) -{ - BIT_SET (port, UART_R_INTEN, TxInt); - - /* *** FIXME: do I need to check for startup of the - transmitter? The old driver did, but AMBA - doesn't . */ -} - -static void lh7a40xuart_stop_rx (struct uart_port* port) -{ - BIT_SET (port, UART_R_INTEN, RxTimeoutInt | RxInt); -} - -static void lh7a40xuart_enable_ms (struct uart_port* port) -{ - BIT_SET (port, UART_R_INTEN, ModemInt); -} - -static void lh7a40xuart_rx_chars (struct uart_port* port) -{ - struct tty_struct* tty = port->state->port.tty; - int cbRxMax = 256; /* (Gross) limit on receive */ - unsigned int data; /* Received data and status */ - unsigned int flag; - - while (!(UR (port, UART_R_STATUS) & nRxRdy) && --cbRxMax) { - data = UR (port, UART_R_DATA); - flag = TTY_NORMAL; - ++port->icount.rx; - - if (unlikely(data & RxError)) { - if (data & RxBreak) { - data &= ~(RxFramingError | RxParityError); - ++port->icount.brk; - if (uart_handle_break (port)) - continue; - } - else if (data & RxParityError) - ++port->icount.parity; - else if (data & RxFramingError) - ++port->icount.frame; - if (data & RxOverrunError) - ++port->icount.overrun; - - /* Mask by termios, leave Rx'd byte */ - data &= port->read_status_mask | 0xff; - - if (data & RxBreak) - flag = TTY_BREAK; - else if (data & RxParityError) - flag = TTY_PARITY; - else if (data & RxFramingError) - flag = TTY_FRAME; - } - - if (uart_handle_sysrq_char (port, (unsigned char) data)) - continue; - - uart_insert_char(port, data, RxOverrunError, data, flag); - } - tty_flip_buffer_push (tty); - return; -} - -static void lh7a40xuart_tx_chars (struct uart_port* port) -{ - struct circ_buf* xmit = &port->state->xmit; - int cbTxMax = port->fifosize; - - if (port->x_char) { - UR (port, UART_R_DATA) = port->x_char; - ++port->icount.tx; - port->x_char = 0; - return; - } - if (uart_circ_empty (xmit) || uart_tx_stopped (port)) { - lh7a40xuart_stop_tx (port); - return; - } - - /* Unlike the AMBA UART, the lh7a40x UART does not guarantee - that at least half of the FIFO is empty. Instead, we check - status for every character. Using the AMBA method causes - the transmitter to drop characters. */ - - do { - UR (port, UART_R_DATA) = xmit->buf[xmit->tail]; - xmit->tail = (xmit->tail + 1) & (UART_XMIT_SIZE - 1); - ++port->icount.tx; - if (uart_circ_empty(xmit)) - break; - } while (!(UR (port, UART_R_STATUS) & nTxRdy) - && cbTxMax--); - - if (uart_circ_chars_pending (xmit) < WAKEUP_CHARS) - uart_write_wakeup (port); - - if (uart_circ_empty (xmit)) - lh7a40xuart_stop_tx (port); -} - -static void lh7a40xuart_modem_status (struct uart_port* port) -{ - unsigned int status = UR (port, UART_R_STATUS); - unsigned int delta - = status ^ ((struct uart_port_lh7a40x*) port)->statusPrev; - - BIT_SET (port, UART_R_RAWISR, MSEOI); /* Clear modem status intr */ - - if (!delta) /* Only happens if we missed 2 transitions */ - return; - - ((struct uart_port_lh7a40x*) port)->statusPrev = status; - - if (delta & DCD) - uart_handle_dcd_change (port, status & DCD); - - if (delta & DSR) - ++port->icount.dsr; - - if (delta & CTS) - uart_handle_cts_change (port, status & CTS); - - wake_up_interruptible (&port->state->port.delta_msr_wait); -} - -static irqreturn_t lh7a40xuart_int (int irq, void* dev_id) -{ - struct uart_port* port = dev_id; - unsigned int cLoopLimit = ISR_LOOP_LIMIT; - unsigned int isr = UR (port, UART_R_ISR); - - - do { - if (isr & (RxInt | RxTimeoutInt)) - lh7a40xuart_rx_chars(port); - if (isr & ModemInt) - lh7a40xuart_modem_status (port); - if (isr & TxInt) - lh7a40xuart_tx_chars (port); - - if (--cLoopLimit == 0) - break; - - isr = UR (port, UART_R_ISR); - } while (isr & (RxInt | TxInt | RxTimeoutInt)); - - return IRQ_HANDLED; -} - -static unsigned int lh7a40xuart_tx_empty (struct uart_port* port) -{ - return (UR (port, UART_R_STATUS) & TxEmpty) ? TIOCSER_TEMT : 0; -} - -static unsigned int lh7a40xuart_get_mctrl (struct uart_port* port) -{ - unsigned int result = 0; - unsigned int status = UR (port, UART_R_STATUS); - - if (status & DCD) - result |= TIOCM_CAR; - if (status & DSR) - result |= TIOCM_DSR; - if (status & CTS) - result |= TIOCM_CTS; - - return result; -} - -static void lh7a40xuart_set_mctrl (struct uart_port* port, unsigned int mctrl) -{ - /* None of the ports supports DTR. UART1 supports RTS through GPIO. */ - /* Note, kernel appears to be setting DTR and RTS on console. */ - - /* *** FIXME: this deserves more work. There's some work in - tracing all of the IO pins. */ -#if 0 - if( port->mapbase == UART1_PHYS) { - gpioRegs_t *gpio = (gpioRegs_t *)IO_ADDRESS(GPIO_PHYS); - - if (mctrl & TIOCM_RTS) - gpio->pbdr &= ~GPIOB_UART1_RTS; - else - gpio->pbdr |= GPIOB_UART1_RTS; - } -#endif -} - -static void lh7a40xuart_break_ctl (struct uart_port* port, int break_state) -{ - unsigned long flags; - - spin_lock_irqsave(&port->lock, flags); - if (break_state == -1) - BIT_SET (port, UART_R_FCON, BRK); /* Assert break */ - else - BIT_CLR (port, UART_R_FCON, BRK); /* Deassert break */ - spin_unlock_irqrestore(&port->lock, flags); -} - -static int lh7a40xuart_startup (struct uart_port* port) -{ - int retval; - - retval = request_irq (port->irq, lh7a40xuart_int, 0, - "serial_lh7a40x", port); - if (retval) - return retval; - - /* Initial modem control-line settings */ - ((struct uart_port_lh7a40x*) port)->statusPrev - = UR (port, UART_R_STATUS); - - /* There is presently no configuration option to enable IR. - Thus, we always disable it. */ - - BIT_SET (port, UART_R_CON, UARTEN | SIRDIS); - BIT_SET (port, UART_R_INTEN, RxTimeoutInt | RxInt); - - return 0; -} - -static void lh7a40xuart_shutdown (struct uart_port* port) -{ - free_irq (port->irq, port); - BIT_CLR (port, UART_R_FCON, BRK | FEN); - BIT_CLR (port, UART_R_CON, UARTEN); -} - -static void lh7a40xuart_set_termios (struct uart_port* port, - struct ktermios* termios, - struct ktermios* old) -{ - unsigned int con; - unsigned int inten; - unsigned int fcon; - unsigned long flags; - unsigned int baud; - unsigned int quot; - - baud = uart_get_baud_rate (port, termios, old, 8, port->uartclk/16); - quot = uart_get_divisor (port, baud); /* -1 performed elsewhere */ - - switch (termios->c_cflag & CSIZE) { - case CS5: - fcon = WLEN_5; - break; - case CS6: - fcon = WLEN_6; - break; - case CS7: - fcon = WLEN_7; - break; - case CS8: - default: - fcon = WLEN_8; - break; - } - if (termios->c_cflag & CSTOPB) - fcon |= STP2; - if (termios->c_cflag & PARENB) { - fcon |= PEN; - if (!(termios->c_cflag & PARODD)) - fcon |= EPS; - } - if (port->fifosize > 1) - fcon |= FEN; - - spin_lock_irqsave (&port->lock, flags); - - uart_update_timeout (port, termios->c_cflag, baud); - - port->read_status_mask = RxOverrunError; - if (termios->c_iflag & INPCK) - port->read_status_mask |= RxFramingError | RxParityError; - if (termios->c_iflag & (BRKINT | PARMRK)) - port->read_status_mask |= RxBreak; - - /* Figure mask for status we ignore */ - port->ignore_status_mask = 0; - if (termios->c_iflag & IGNPAR) - port->ignore_status_mask |= RxFramingError | RxParityError; - if (termios->c_iflag & IGNBRK) { - port->ignore_status_mask |= RxBreak; - /* Ignore overrun when ignorning parity */ - /* *** FIXME: is this in the right place? */ - if (termios->c_iflag & IGNPAR) - port->ignore_status_mask |= RxOverrunError; - } - - /* Ignore all receive errors when receive disabled */ - if ((termios->c_cflag & CREAD) == 0) - port->ignore_status_mask |= RxError; - - con = UR (port, UART_R_CON); - inten = (UR (port, UART_R_INTEN) & ~ModemInt); - - if (UART_ENABLE_MS (port, termios->c_cflag)) - inten |= ModemInt; - - BIT_CLR (port, UART_R_CON, UARTEN); /* Disable UART */ - UR (port, UART_R_INTEN) = 0; /* Disable interrupts */ - UR (port, UART_R_BRCON) = quot - 1; /* Set baud rate divisor */ - UR (port, UART_R_FCON) = fcon; /* Set FIFO and frame ctrl */ - UR (port, UART_R_INTEN) = inten; /* Enable interrupts */ - UR (port, UART_R_CON) = con; /* Restore UART mode */ - - spin_unlock_irqrestore(&port->lock, flags); -} - -static const char* lh7a40xuart_type (struct uart_port* port) -{ - return port->type == PORT_LH7A40X ? "LH7A40X" : NULL; -} - -static void lh7a40xuart_release_port (struct uart_port* port) -{ - release_mem_region (port->mapbase, UART_REG_SIZE); -} - -static int lh7a40xuart_request_port (struct uart_port* port) -{ - return request_mem_region (port->mapbase, UART_REG_SIZE, - "serial_lh7a40x") != NULL - ? 0 : -EBUSY; -} - -static void lh7a40xuart_config_port (struct uart_port* port, int flags) -{ - if (flags & UART_CONFIG_TYPE) { - port->type = PORT_LH7A40X; - lh7a40xuart_request_port (port); - } -} - -static int lh7a40xuart_verify_port (struct uart_port* port, - struct serial_struct* ser) -{ - int ret = 0; - - if (ser->type != PORT_UNKNOWN && ser->type != PORT_LH7A40X) - ret = -EINVAL; - if (ser->irq < 0 || ser->irq >= nr_irqs) - ret = -EINVAL; - if (ser->baud_base < 9600) /* *** FIXME: is this true? */ - ret = -EINVAL; - return ret; -} - -static struct uart_ops lh7a40x_uart_ops = { - .tx_empty = lh7a40xuart_tx_empty, - .set_mctrl = lh7a40xuart_set_mctrl, - .get_mctrl = lh7a40xuart_get_mctrl, - .stop_tx = lh7a40xuart_stop_tx, - .start_tx = lh7a40xuart_start_tx, - .stop_rx = lh7a40xuart_stop_rx, - .enable_ms = lh7a40xuart_enable_ms, - .break_ctl = lh7a40xuart_break_ctl, - .startup = lh7a40xuart_startup, - .shutdown = lh7a40xuart_shutdown, - .set_termios = lh7a40xuart_set_termios, - .type = lh7a40xuart_type, - .release_port = lh7a40xuart_release_port, - .request_port = lh7a40xuart_request_port, - .config_port = lh7a40xuart_config_port, - .verify_port = lh7a40xuart_verify_port, -}; - -static struct uart_port_lh7a40x lh7a40x_ports[DEV_NR] = { - { - .port = { - .membase = (void*) io_p2v (UART1_PHYS), - .mapbase = UART1_PHYS, - .iotype = UPIO_MEM, - .irq = IRQ_UART1INTR, - .uartclk = 14745600/2, - .fifosize = 16, - .ops = &lh7a40x_uart_ops, - .flags = UPF_BOOT_AUTOCONF, - .line = 0, - }, - }, - { - .port = { - .membase = (void*) io_p2v (UART2_PHYS), - .mapbase = UART2_PHYS, - .iotype = UPIO_MEM, - .irq = IRQ_UART2INTR, - .uartclk = 14745600/2, - .fifosize = 16, - .ops = &lh7a40x_uart_ops, - .flags = UPF_BOOT_AUTOCONF, - .line = 1, - }, - }, - { - .port = { - .membase = (void*) io_p2v (UART3_PHYS), - .mapbase = UART3_PHYS, - .iotype = UPIO_MEM, - .irq = IRQ_UART3INTR, - .uartclk = 14745600/2, - .fifosize = 16, - .ops = &lh7a40x_uart_ops, - .flags = UPF_BOOT_AUTOCONF, - .line = 2, - }, - }, -}; - -#ifndef CONFIG_SERIAL_LH7A40X_CONSOLE -# define LH7A40X_CONSOLE NULL -#else -# define LH7A40X_CONSOLE &lh7a40x_console - -static void lh7a40xuart_console_putchar(struct uart_port *port, int ch) -{ - while (UR(port, UART_R_STATUS) & nTxRdy) - ; - UR(port, UART_R_DATA) = ch; -} - -static void lh7a40xuart_console_write (struct console* co, - const char* s, - unsigned int count) -{ - struct uart_port* port = &lh7a40x_ports[co->index].port; - unsigned int con = UR (port, UART_R_CON); - unsigned int inten = UR (port, UART_R_INTEN); - - - UR (port, UART_R_INTEN) = 0; /* Disable all interrupts */ - BIT_SET (port, UART_R_CON, UARTEN | SIRDIS); /* Enable UART */ - - uart_console_write(port, s, count, lh7a40xuart_console_putchar); - - /* Wait until all characters are sent */ - while (UR (port, UART_R_STATUS) & TxBusy) - ; - - /* Restore control and interrupt mask */ - UR (port, UART_R_CON) = con; - UR (port, UART_R_INTEN) = inten; -} - -static void __init lh7a40xuart_console_get_options (struct uart_port* port, - int* baud, - int* parity, - int* bits) -{ - if (UR (port, UART_R_CON) & UARTEN) { - unsigned int fcon = UR (port, UART_R_FCON); - unsigned int quot = UR (port, UART_R_BRCON) + 1; - - switch (fcon & (PEN | EPS)) { - default: *parity = 'n'; break; - case PEN: *parity = 'o'; break; - case PEN | EPS: *parity = 'e'; break; - } - - switch (fcon & WLEN) { - default: - case WLEN_8: *bits = 8; break; - case WLEN_7: *bits = 7; break; - case WLEN_6: *bits = 6; break; - case WLEN_5: *bits = 5; break; - } - - *baud = port->uartclk/(16*quot); - } -} - -static int __init lh7a40xuart_console_setup (struct console* co, char* options) -{ - struct uart_port* port; - int baud = 38400; - int bits = 8; - int parity = 'n'; - int flow = 'n'; - - if (co->index >= DEV_NR) /* Bounds check on device number */ - co->index = 0; - port = &lh7a40x_ports[co->index].port; - - if (options) - uart_parse_options (options, &baud, &parity, &bits, &flow); - else - lh7a40xuart_console_get_options (port, &baud, &parity, &bits); - - return uart_set_options (port, co, baud, parity, bits, flow); -} - -static struct uart_driver lh7a40x_reg; -static struct console lh7a40x_console = { - .name = "ttyAM", - .write = lh7a40xuart_console_write, - .device = uart_console_device, - .setup = lh7a40xuart_console_setup, - .flags = CON_PRINTBUFFER, - .index = -1, - .data = &lh7a40x_reg, -}; - -static int __init lh7a40xuart_console_init(void) -{ - register_console (&lh7a40x_console); - return 0; -} - -console_initcall (lh7a40xuart_console_init); - -#endif - -static struct uart_driver lh7a40x_reg = { - .owner = THIS_MODULE, - .driver_name = "ttyAM", - .dev_name = "ttyAM", - .major = DEV_MAJOR, - .minor = DEV_MINOR, - .nr = DEV_NR, - .cons = LH7A40X_CONSOLE, -}; - -static int __init lh7a40xuart_init(void) -{ - int ret; - - printk (KERN_INFO "serial: LH7A40X serial driver\n"); - - ret = uart_register_driver (&lh7a40x_reg); - - if (ret == 0) { - int i; - - for (i = 0; i < DEV_NR; i++) { - /* UART3, when used, requires GPIO pin reallocation */ - if (lh7a40x_ports[i].port.mapbase == UART3_PHYS) - GPIO_PINMUX |= 1<<3; - uart_add_one_port (&lh7a40x_reg, - &lh7a40x_ports[i].port); - } - } - return ret; -} - -static void __exit lh7a40xuart_exit(void) -{ - int i; - - for (i = 0; i < DEV_NR; i++) - uart_remove_one_port (&lh7a40x_reg, &lh7a40x_ports[i].port); - - uart_unregister_driver (&lh7a40x_reg); -} - -module_init (lh7a40xuart_init); -module_exit (lh7a40xuart_exit); - -MODULE_AUTHOR ("Marc Singer"); -MODULE_DESCRIPTION ("Sharp LH7A40X serial port driver"); -MODULE_LICENSE ("GPL"); diff --git a/drivers/tty/serial/sh-sci.c b/drivers/tty/serial/sh-sci.c index 92c91c83edde..0257fd5ede52 100644 --- a/drivers/tty/serial/sh-sci.c +++ b/drivers/tty/serial/sh-sci.c @@ -47,7 +47,6 @@ #include <linux/clk.h> #include <linux/ctype.h> #include <linux/err.h> -#include <linux/list.h> #include <linux/dmaengine.h> #include <linux/scatterlist.h> #include <linux/slab.h> @@ -65,11 +64,8 @@ struct sci_port { struct uart_port port; - /* Port type */ - unsigned int type; - - /* Port IRQs: ERI, RXI, TXI, BRI (optional) */ - unsigned int irqs[SCIx_NR_IRQS]; + /* Platform configuration */ + struct plat_sci_port *cfg; /* Port enable callback */ void (*enable)(struct uart_port *port); @@ -81,26 +77,15 @@ struct sci_port { struct timer_list break_timer; int break_flag; - /* SCSCR initialization */ - unsigned int scscr; - - /* SCBRR calculation algo */ - unsigned int scbrr_algo_id; - /* Interface clock */ struct clk *iclk; /* Function clock */ struct clk *fclk; - struct list_head node; - struct dma_chan *chan_tx; struct dma_chan *chan_rx; #ifdef CONFIG_SERIAL_SH_SCI_DMA - struct device *dma_dev; - unsigned int slave_tx; - unsigned int slave_rx; struct dma_async_tx_descriptor *desc_tx; struct dma_async_tx_descriptor *desc_rx[2]; dma_cookie_t cookie_tx; @@ -117,16 +102,14 @@ struct sci_port { struct timer_list rx_timer; unsigned int rx_timeout; #endif -}; -struct sh_sci_priv { - spinlock_t lock; - struct list_head ports; - struct notifier_block clk_nb; + struct notifier_block freq_transition; }; /* Function prototypes */ +static void sci_start_tx(struct uart_port *port); static void sci_stop_tx(struct uart_port *port); +static void sci_start_rx(struct uart_port *port); #define SCI_NPORTS CONFIG_SERIAL_SH_SCI_NR_UARTS @@ -142,12 +125,6 @@ to_sci_port(struct uart_port *uart) #if defined(CONFIG_CONSOLE_POLL) || defined(CONFIG_SERIAL_SH_SCI_CONSOLE) #ifdef CONFIG_CONSOLE_POLL -static inline void handle_error(struct uart_port *port) -{ - /* Clear error flags */ - sci_out(port, SCxSR, SCxSR_ERROR_CLEAR(port)); -} - static int sci_poll_get_char(struct uart_port *port) { unsigned short status; @@ -156,7 +133,7 @@ static int sci_poll_get_char(struct uart_port *port) do { status = sci_in(port, SCxSR); if (status & SCxSR_ERRORS(port)) { - handle_error(port); + sci_out(port, SCxSR, SCxSR_ERROR_CLEAR(port)); continue; } break; @@ -475,7 +452,7 @@ static void sci_transmit_chars(struct uart_port *port) /* On SH3, SCIF may read end-of-break as a space->mark char */ #define STEPFN(c) ({int __c = (c); (((__c-1)|(__c)) == -1); }) -static inline void sci_receive_chars(struct uart_port *port) +static void sci_receive_chars(struct uart_port *port) { struct sci_port *sci_port = to_sci_port(port); struct tty_struct *tty = port->state->port.tty; @@ -566,18 +543,20 @@ static inline void sci_receive_chars(struct uart_port *port) } #define SCI_BREAK_JIFFIES (HZ/20) -/* The sci generates interrupts during the break, + +/* + * The sci generates interrupts during the break, * 1 per millisecond or so during the break period, for 9600 baud. * So dont bother disabling interrupts. * But dont want more than 1 break event. * Use a kernel timer to periodically poll the rx line until * the break is finished. */ -static void sci_schedule_break_timer(struct sci_port *port) +static inline void sci_schedule_break_timer(struct sci_port *port) { - port->break_timer.expires = jiffies + SCI_BREAK_JIFFIES; - add_timer(&port->break_timer); + mod_timer(&port->break_timer, jiffies + SCI_BREAK_JIFFIES); } + /* Ensure that two consecutive samples find the break over. */ static void sci_break_timer(unsigned long data) { @@ -594,7 +573,7 @@ static void sci_break_timer(unsigned long data) port->break_flag = 0; } -static inline int sci_handle_errors(struct uart_port *port) +static int sci_handle_errors(struct uart_port *port) { int copied = 0; unsigned short status = sci_in(port, SCxSR); @@ -650,7 +629,7 @@ static inline int sci_handle_errors(struct uart_port *port) return copied; } -static inline int sci_handle_fifo_overrun(struct uart_port *port) +static int sci_handle_fifo_overrun(struct uart_port *port) { struct tty_struct *tty = port->state->port.tty; int copied = 0; @@ -671,7 +650,7 @@ static inline int sci_handle_fifo_overrun(struct uart_port *port) return copied; } -static inline int sci_handle_breaks(struct uart_port *port) +static int sci_handle_breaks(struct uart_port *port) { int copied = 0; unsigned short status = sci_in(port, SCxSR); @@ -794,7 +773,7 @@ static inline unsigned long port_rx_irq_mask(struct uart_port *port) * it's unset, it's logically inferred that there's no point in * testing for it. */ - return SCSCR_RIE | (to_sci_port(port)->scscr & SCSCR_REIE); + return SCSCR_RIE | (to_sci_port(port)->cfg->scscr & SCSCR_REIE); } static irqreturn_t sci_mpxed_interrupt(int irq, void *ptr) @@ -839,17 +818,18 @@ static irqreturn_t sci_mpxed_interrupt(int irq, void *ptr) static int sci_notifier(struct notifier_block *self, unsigned long phase, void *p) { - struct sh_sci_priv *priv = container_of(self, - struct sh_sci_priv, clk_nb); struct sci_port *sci_port; unsigned long flags; + sci_port = container_of(self, struct sci_port, freq_transition); + if ((phase == CPUFREQ_POSTCHANGE) || (phase == CPUFREQ_RESUMECHANGE)) { - spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry(sci_port, &priv->ports, node) - sci_port->port.uartclk = clk_get_rate(sci_port->iclk); - spin_unlock_irqrestore(&priv->lock, flags); + struct uart_port *port = &sci_port->port; + + spin_lock_irqsave(&port->lock, flags); + port->uartclk = clk_get_rate(sci_port->iclk); + spin_unlock_irqrestore(&port->lock, flags); } return NOTIFY_OK; @@ -882,21 +862,21 @@ static int sci_request_irq(struct sci_port *port) const char *desc[] = { "SCI Receive Error", "SCI Receive Data Full", "SCI Transmit Data Empty", "SCI Break" }; - if (port->irqs[0] == port->irqs[1]) { - if (unlikely(!port->irqs[0])) + if (port->cfg->irqs[0] == port->cfg->irqs[1]) { + if (unlikely(!port->cfg->irqs[0])) return -ENODEV; - if (request_irq(port->irqs[0], sci_mpxed_interrupt, + if (request_irq(port->cfg->irqs[0], sci_mpxed_interrupt, IRQF_DISABLED, "sci", port)) { dev_err(port->port.dev, "Can't allocate IRQ\n"); return -ENODEV; } } else { for (i = 0; i < ARRAY_SIZE(handlers); i++) { - if (unlikely(!port->irqs[i])) + if (unlikely(!port->cfg->irqs[i])) continue; - if (request_irq(port->irqs[i], handlers[i], + if (request_irq(port->cfg->irqs[i], handlers[i], IRQF_DISABLED, desc[i], port)) { dev_err(port->port.dev, "Can't allocate IRQ\n"); return -ENODEV; @@ -911,14 +891,14 @@ static void sci_free_irq(struct sci_port *port) { int i; - if (port->irqs[0] == port->irqs[1]) - free_irq(port->irqs[0], port); + if (port->cfg->irqs[0] == port->cfg->irqs[1]) + free_irq(port->cfg->irqs[0], port); else { - for (i = 0; i < ARRAY_SIZE(port->irqs); i++) { - if (!port->irqs[i]) + for (i = 0; i < ARRAY_SIZE(port->cfg->irqs); i++) { + if (!port->cfg->irqs[i]) continue; - free_irq(port->irqs[i], port); + free_irq(port->cfg->irqs[i], port); } } } @@ -1037,9 +1017,6 @@ static void sci_dma_rx_complete(void *arg) schedule_work(&s->work_rx); } -static void sci_start_rx(struct uart_port *port); -static void sci_start_tx(struct uart_port *port); - static void sci_rx_dma_release(struct sci_port *s, bool enable_pio) { struct dma_chan *chan = s->chan_rx; @@ -1325,7 +1302,7 @@ static void rx_timer_fn(unsigned long arg) if (port->type == PORT_SCIFA || port->type == PORT_SCIFB) { scr &= ~0x4000; - enable_irq(s->irqs[1]); + enable_irq(s->cfg->irqs[1]); } sci_out(port, SCSCR, scr | SCSCR_RIE); dev_dbg(port->dev, "DMA Rx timed out\n"); @@ -1341,9 +1318,9 @@ static void sci_request_dma(struct uart_port *port) int nent; dev_dbg(port->dev, "%s: port %d DMA %p\n", __func__, - port->line, s->dma_dev); + port->line, s->cfg->dma_dev); - if (!s->dma_dev) + if (!s->cfg->dma_dev) return; dma_cap_zero(mask); @@ -1352,8 +1329,8 @@ static void sci_request_dma(struct uart_port *port) param = &s->param_tx; /* Slave ID, e.g., SHDMA_SLAVE_SCIF0_TX */ - param->slave_id = s->slave_tx; - param->dma_dev = s->dma_dev; + param->slave_id = s->cfg->dma_slave_tx; + param->dma_dev = s->cfg->dma_dev; s->cookie_tx = -EINVAL; chan = dma_request_channel(mask, filter, param); @@ -1381,8 +1358,8 @@ static void sci_request_dma(struct uart_port *port) param = &s->param_rx; /* Slave ID, e.g., SHDMA_SLAVE_SCIF0_RX */ - param->slave_id = s->slave_rx; - param->dma_dev = s->dma_dev; + param->slave_id = s->cfg->dma_slave_rx; + param->dma_dev = s->cfg->dma_dev; chan = dma_request_channel(mask, filter, param); dev_dbg(port->dev, "%s: RX: got channel %p\n", __func__, chan); @@ -1427,7 +1404,7 @@ static void sci_free_dma(struct uart_port *port) { struct sci_port *s = to_sci_port(port); - if (!s->dma_dev) + if (!s->cfg->dma_dev) return; if (s->chan_tx) @@ -1435,21 +1412,32 @@ static void sci_free_dma(struct uart_port *port) if (s->chan_rx) sci_rx_dma_release(s, false); } +#else +static inline void sci_request_dma(struct uart_port *port) +{ +} + +static inline void sci_free_dma(struct uart_port *port) +{ +} #endif static int sci_startup(struct uart_port *port) { struct sci_port *s = to_sci_port(port); + int ret; dev_dbg(port->dev, "%s(%d)\n", __func__, port->line); if (s->enable) s->enable(port); - sci_request_irq(s); -#ifdef CONFIG_SERIAL_SH_SCI_DMA + ret = sci_request_irq(s); + if (unlikely(ret < 0)) + return ret; + sci_request_dma(port); -#endif + sci_start_tx(port); sci_start_rx(port); @@ -1464,9 +1452,8 @@ static void sci_shutdown(struct uart_port *port) sci_stop_rx(port); sci_stop_tx(port); -#ifdef CONFIG_SERIAL_SH_SCI_DMA + sci_free_dma(port); -#endif sci_free_irq(s); if (s->disable) @@ -1491,6 +1478,7 @@ static unsigned int sci_scbrr_calc(unsigned int algo_id, unsigned int bps, /* Warn, but use a safe default */ WARN_ON(1); + return ((freq + 16 * bps) / (32 * bps) - 1); } @@ -1514,7 +1502,7 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios, baud = uart_get_baud_rate(port, termios, old, 0, max_baud); if (likely(baud && port->uartclk)) - t = sci_scbrr_calc(s->scbrr_algo_id, baud, port->uartclk); + t = sci_scbrr_calc(s->cfg->scbrr_algo_id, baud, port->uartclk); do { status = sci_in(port, SCxSR); @@ -1526,6 +1514,7 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios, sci_out(port, SCFCR, scfcr | SCFCR_RFRST | SCFCR_TFRST); smr_val = sci_in(port, SCSMR) & 3; + if ((termios->c_cflag & CSIZE) == CS7) smr_val |= 0x40; if (termios->c_cflag & PARENB) @@ -1540,7 +1529,7 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios, sci_out(port, SCSMR, smr_val); dev_dbg(port->dev, "%s: SMR %x, t %x, SCSCR %x\n", __func__, smr_val, t, - s->scscr); + s->cfg->scscr); if (t > 0) { if (t >= 256) { @@ -1556,7 +1545,7 @@ static void sci_set_termios(struct uart_port *port, struct ktermios *termios, sci_init_pins(port, termios->c_cflag); sci_out(port, SCFCR, scfcr | ((termios->c_cflag & CRTSCTS) ? SCFCR_MCE : 0)); - sci_out(port, SCSCR, s->scscr); + sci_out(port, SCSCR, s->cfg->scscr); #ifdef CONFIG_SERIAL_SH_SCI_DMA /* @@ -1602,31 +1591,33 @@ static const char *sci_type(struct uart_port *port) return NULL; } -static void sci_release_port(struct uart_port *port) +static inline unsigned long sci_port_size(struct uart_port *port) { - /* Nothing here yet .. */ -} - -static int sci_request_port(struct uart_port *port) -{ - /* Nothing here yet .. */ - return 0; + /* + * Pick an arbitrary size that encapsulates all of the base + * registers by default. This can be optimized later, or derived + * from platform resource data at such a time that ports begin to + * behave more erratically. + */ + return 64; } -static void sci_config_port(struct uart_port *port, int flags) +static int sci_remap_port(struct uart_port *port) { - struct sci_port *s = to_sci_port(port); - - port->type = s->type; + unsigned long size = sci_port_size(port); + /* + * Nothing to do if there's already an established membase. + */ if (port->membase) - return; + return 0; if (port->flags & UPF_IOREMAP) { - port->membase = ioremap_nocache(port->mapbase, 0x40); - - if (IS_ERR(port->membase)) + port->membase = ioremap_nocache(port->mapbase, size); + if (unlikely(!port->membase)) { dev_err(port->dev, "can't remap port#%d\n", port->line); + return -ENXIO; + } } else { /* * For the simple (and majority of) cases where we don't @@ -1635,13 +1626,54 @@ static void sci_config_port(struct uart_port *port, int flags) */ port->membase = (void __iomem *)port->mapbase; } + + return 0; +} + +static void sci_release_port(struct uart_port *port) +{ + if (port->flags & UPF_IOREMAP) { + iounmap(port->membase); + port->membase = NULL; + } + + release_mem_region(port->mapbase, sci_port_size(port)); +} + +static int sci_request_port(struct uart_port *port) +{ + unsigned long size = sci_port_size(port); + struct resource *res; + int ret; + + res = request_mem_region(port->mapbase, size, dev_name(port->dev)); + if (unlikely(res == NULL)) + return -EBUSY; + + ret = sci_remap_port(port); + if (unlikely(ret != 0)) { + release_resource(res); + return ret; + } + + return 0; +} + +static void sci_config_port(struct uart_port *port, int flags) +{ + if (flags & UART_CONFIG_TYPE) { + struct sci_port *sport = to_sci_port(port); + + port->type = sport->cfg->type; + sci_request_port(port); + } } static int sci_verify_port(struct uart_port *port, struct serial_struct *ser) { struct sci_port *s = to_sci_port(port); - if (ser->irq != s->irqs[SCIx_TXI_IRQ] || ser->irq > nr_irqs) + if (ser->irq != s->cfg->irqs[SCIx_TXI_IRQ] || ser->irq > nr_irqs) return -EINVAL; if (ser->baud_base < 2400) /* No paper tape reader for Mitch.. */ @@ -1726,36 +1758,29 @@ static int __devinit sci_init_single(struct platform_device *dev, sci_port->break_timer.function = sci_break_timer; init_timer(&sci_port->break_timer); - port->mapbase = p->mapbase; - port->membase = p->membase; + sci_port->cfg = p; - port->irq = p->irqs[SCIx_TXI_IRQ]; + port->mapbase = p->mapbase; + port->type = p->type; port->flags = p->flags; - sci_port->type = port->type = p->type; - sci_port->scscr = p->scscr; - sci_port->scbrr_algo_id = p->scbrr_algo_id; -#ifdef CONFIG_SERIAL_SH_SCI_DMA - sci_port->dma_dev = p->dma_dev; - sci_port->slave_tx = p->dma_slave_tx; - sci_port->slave_rx = p->dma_slave_rx; + /* + * The UART port needs an IRQ value, so we peg this to the TX IRQ + * for the multi-IRQ ports, which is where we are primarily + * concerned with the shutdown path synchronization. + * + * For the muxed case there's nothing more to do. + */ + port->irq = p->irqs[SCIx_TXI_IRQ]; - dev_dbg(port->dev, "%s: DMA device %p, tx %d, rx %d\n", __func__, - p->dma_dev, p->dma_slave_tx, p->dma_slave_rx); -#endif + if (p->dma_dev) + dev_dbg(port->dev, "DMA device %p, tx %d, rx %d\n", + p->dma_dev, p->dma_slave_tx, p->dma_slave_rx); - memcpy(&sci_port->irqs, &p->irqs, sizeof(p->irqs)); return 0; } #ifdef CONFIG_SERIAL_SH_SCI_CONSOLE -static struct tty_driver *serial_console_device(struct console *co, int *index) -{ - struct uart_driver *p = &sci_uart_driver; - *index = co->index; - return p->tty_driver; -} - static void serial_console_putchar(struct uart_port *port, int ch) { sci_poll_put_char(port, ch); @@ -1768,8 +1793,8 @@ static void serial_console_putchar(struct uart_port *port, int ch) static void serial_console_write(struct console *co, const char *s, unsigned count) { - struct uart_port *port = co->data; - struct sci_port *sci_port = to_sci_port(port); + struct sci_port *sci_port = &sci_ports[co->index]; + struct uart_port *port = &sci_port->port; unsigned short bits; if (sci_port->enable) @@ -1797,32 +1822,17 @@ static int __devinit serial_console_setup(struct console *co, char *options) int ret; /* - * Check whether an invalid uart number has been specified, and - * if so, search for the first available port that does have - * console support. - */ - if (co->index >= SCI_NPORTS) - co->index = 0; - - if (co->data) { - port = co->data; - sci_port = to_sci_port(port); - } else { - sci_port = &sci_ports[co->index]; - port = &sci_port->port; - co->data = port; - } - - /* - * Also need to check port->type, we don't actually have any - * UPIO_PORT ports, but uart_report_port() handily misreports - * it anyways if we don't have a port available by the time this is - * called. + * Refuse to handle any bogus ports. */ - if (!port->type) + if (co->index < 0 || co->index >= SCI_NPORTS) return -ENODEV; - sci_config_port(port, 0); + sci_port = &sci_ports[co->index]; + port = &sci_port->port; + + ret = sci_remap_port(port); + if (unlikely(ret != 0)) + return ret; if (sci_port->enable) sci_port->enable(port); @@ -1842,11 +1852,12 @@ static int __devinit serial_console_setup(struct console *co, char *options) static struct console serial_console = { .name = "ttySC", - .device = serial_console_device, + .device = uart_console_device, .write = serial_console_write, .setup = serial_console_setup, .flags = CON_PRINTBUFFER, .index = -1, + .data = &sci_uart_driver, }; static int __init sci_console_init(void) @@ -1856,14 +1867,39 @@ static int __init sci_console_init(void) } console_initcall(sci_console_init); -static struct sci_port early_serial_port; static struct console early_serial_console = { .name = "early_ttySC", .write = serial_console_write, .flags = CON_PRINTBUFFER, + .index = -1, }; + static char early_serial_buf[32]; +static int __devinit sci_probe_earlyprintk(struct platform_device *pdev) +{ + struct plat_sci_port *cfg = pdev->dev.platform_data; + + if (early_serial_console.data) + return -EEXIST; + + early_serial_console.index = pdev->id; + + sci_init_single(NULL, &sci_ports[pdev->id], pdev->id, cfg); + + serial_console_setup(&early_serial_console, early_serial_buf); + + if (!strstr(early_serial_buf, "keep")) + early_serial_console.flags |= CON_BOOT; + + register_console(&early_serial_console); + return 0; +} +#else +static inline int __devinit sci_probe_earlyprintk(struct platform_device *pdev) +{ + return -EINVAL; +} #endif /* CONFIG_SERIAL_SH_SCI_CONSOLE */ #if defined(CONFIG_SERIAL_SH_SCI_CONSOLE) @@ -1885,24 +1921,18 @@ static struct uart_driver sci_uart_driver = { .cons = SCI_CONSOLE, }; - static int sci_remove(struct platform_device *dev) { - struct sh_sci_priv *priv = platform_get_drvdata(dev); - struct sci_port *p; - unsigned long flags; + struct sci_port *port = platform_get_drvdata(dev); - cpufreq_unregister_notifier(&priv->clk_nb, CPUFREQ_TRANSITION_NOTIFIER); + cpufreq_unregister_notifier(&port->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); - spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry(p, &priv->ports, node) { - uart_remove_one_port(&sci_uart_driver, &p->port); - clk_put(p->iclk); - clk_put(p->fclk); - } - spin_unlock_irqrestore(&priv->lock, flags); + uart_remove_one_port(&sci_uart_driver, &port->port); + + clk_put(port->iclk); + clk_put(port->fclk); - kfree(priv); return 0; } @@ -1911,8 +1941,6 @@ static int __devinit sci_probe_single(struct platform_device *dev, struct plat_sci_port *p, struct sci_port *sciport) { - struct sh_sci_priv *priv = platform_get_drvdata(dev); - unsigned long flags; int ret; /* Sanity check */ @@ -1929,68 +1957,35 @@ static int __devinit sci_probe_single(struct platform_device *dev, if (ret) return ret; - ret = uart_add_one_port(&sci_uart_driver, &sciport->port); - if (ret) - return ret; - - INIT_LIST_HEAD(&sciport->node); - - spin_lock_irqsave(&priv->lock, flags); - list_add(&sciport->node, &priv->ports); - spin_unlock_irqrestore(&priv->lock, flags); - - return 0; + return uart_add_one_port(&sci_uart_driver, &sciport->port); } -/* - * Register a set of serial devices attached to a platform device. The - * list is terminated with a zero flags entry, which means we expect - * all entries to have at least UPF_BOOT_AUTOCONF set. Platforms that need - * remapping (such as sh64) should also set UPF_IOREMAP. - */ static int __devinit sci_probe(struct platform_device *dev) { struct plat_sci_port *p = dev->dev.platform_data; - struct sh_sci_priv *priv; - int i, ret = -EINVAL; + struct sci_port *sp = &sci_ports[dev->id]; + int ret; -#ifdef CONFIG_SERIAL_SH_SCI_CONSOLE - if (is_early_platform_device(dev)) { - if (dev->id == -1) - return -ENOTSUPP; - early_serial_console.index = dev->id; - early_serial_console.data = &early_serial_port.port; - sci_init_single(NULL, &early_serial_port, dev->id, p); - serial_console_setup(&early_serial_console, early_serial_buf); - if (!strstr(early_serial_buf, "keep")) - early_serial_console.flags |= CON_BOOT; - register_console(&early_serial_console); - return 0; - } -#endif + /* + * If we've come here via earlyprintk initialization, head off to + * the special early probe. We don't have sufficient device state + * to make it beyond this yet. + */ + if (is_early_platform_device(dev)) + return sci_probe_earlyprintk(dev); - priv = kzalloc(sizeof(*priv), GFP_KERNEL); - if (!priv) - return -ENOMEM; + platform_set_drvdata(dev, sp); - INIT_LIST_HEAD(&priv->ports); - spin_lock_init(&priv->lock); - platform_set_drvdata(dev, priv); + ret = sci_probe_single(dev, dev->id, p, sp); + if (ret) + goto err_unreg; - priv->clk_nb.notifier_call = sci_notifier; - cpufreq_register_notifier(&priv->clk_nb, CPUFREQ_TRANSITION_NOTIFIER); + sp->freq_transition.notifier_call = sci_notifier; - if (dev->id != -1) { - ret = sci_probe_single(dev, dev->id, p, &sci_ports[dev->id]); - if (ret) - goto err_unreg; - } else { - for (i = 0; p && p->flags != 0; p++, i++) { - ret = sci_probe_single(dev, i, p, &sci_ports[i]); - if (ret) - goto err_unreg; - } - } + ret = cpufreq_register_notifier(&sp->freq_transition, + CPUFREQ_TRANSITION_NOTIFIER); + if (unlikely(ret < 0)) + goto err_unreg; #ifdef CONFIG_SH_STANDARD_BIOS sh_bios_gdb_detach(); @@ -2005,28 +2000,20 @@ err_unreg: static int sci_suspend(struct device *dev) { - struct sh_sci_priv *priv = dev_get_drvdata(dev); - struct sci_port *p; - unsigned long flags; + struct sci_port *sport = dev_get_drvdata(dev); - spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry(p, &priv->ports, node) - uart_suspend_port(&sci_uart_driver, &p->port); - spin_unlock_irqrestore(&priv->lock, flags); + if (sport) + uart_suspend_port(&sci_uart_driver, &sport->port); return 0; } static int sci_resume(struct device *dev) { - struct sh_sci_priv *priv = dev_get_drvdata(dev); - struct sci_port *p; - unsigned long flags; + struct sci_port *sport = dev_get_drvdata(dev); - spin_lock_irqsave(&priv->lock, flags); - list_for_each_entry(p, &priv->ports, node) - uart_resume_port(&sci_uart_driver, &p->port); - spin_unlock_irqrestore(&priv->lock, flags); + if (sport) + uart_resume_port(&sci_uart_driver, &sport->port); return 0; } diff --git a/drivers/tty/serial/sh-sci.h b/drivers/tty/serial/sh-sci.h index b223d6cbf33a..5fefed53fa42 100644 --- a/drivers/tty/serial/sh-sci.h +++ b/drivers/tty/serial/sh-sci.h @@ -54,9 +54,6 @@ # define PBCR 0xa4050102 #elif defined(CONFIG_CPU_SUBTYPE_SH7343) # define SCSPTR0 0xffe00010 /* 16 bit SCIF */ -# define SCSPTR1 0xffe10010 /* 16 bit SCIF */ -# define SCSPTR2 0xffe20010 /* 16 bit SCIF */ -# define SCSPTR3 0xffe30010 /* 16 bit SCIF */ #elif defined(CONFIG_CPU_SUBTYPE_SH7722) # define PADR 0xA4050120 # define PSDR 0xA405013e @@ -69,77 +66,42 @@ # define SCIF_ORER 0x0001 /* overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SH7723) # define SCSPTR0 0xa4050160 -# define SCSPTR1 0xa405013e -# define SCSPTR2 0xa4050160 -# define SCSPTR3 0xa405013e -# define SCSPTR4 0xa4050128 -# define SCSPTR5 0xa4050128 # define SCIF_ORER 0x0001 /* overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SH7724) # define SCIF_ORER 0x0001 /* overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SH4_202) # define SCSPTR2 0xffe80020 /* 16 bit SCIF */ # define SCIF_ORER 0x0001 /* overrun error bit */ -#elif defined(CONFIG_CPU_SUBTYPE_SH5_101) || defined(CONFIG_CPU_SUBTYPE_SH5_103) -# define SCIF_PTR2_OFFS 0x0000020 -# define SCSPTR2 ((port->mapbase)+SCIF_PTR2_OFFS) /* 16 bit SCIF */ #elif defined(CONFIG_H83007) || defined(CONFIG_H83068) # define H8300_SCI_DR(ch) *(volatile char *)(P1DR + h8300_sci_pins[ch].port) #elif defined(CONFIG_H8S2678) # define H8300_SCI_DR(ch) *(volatile char *)(P1DR + h8300_sci_pins[ch].port) #elif defined(CONFIG_CPU_SUBTYPE_SH7757) # define SCSPTR0 0xfe4b0020 -# define SCSPTR1 0xfe4b0020 -# define SCSPTR2 0xfe4b0020 # define SCIF_ORER 0x0001 -# define SCIF_ONLY #elif defined(CONFIG_CPU_SUBTYPE_SH7763) # define SCSPTR0 0xffe00024 /* 16 bit SCIF */ -# define SCSPTR1 0xffe08024 /* 16 bit SCIF */ -# define SCSPTR2 0xffe10020 /* 16 bit SCIF/IRDA */ # define SCIF_ORER 0x0001 /* overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SH7770) # define SCSPTR0 0xff923020 /* 16 bit SCIF */ -# define SCSPTR1 0xff924020 /* 16 bit SCIF */ -# define SCSPTR2 0xff925020 /* 16 bit SCIF */ # define SCIF_ORER 0x0001 /* overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SH7780) # define SCSPTR0 0xffe00024 /* 16 bit SCIF */ -# define SCSPTR1 0xffe10024 /* 16 bit SCIF */ # define SCIF_ORER 0x0001 /* Overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SH7785) || \ defined(CONFIG_CPU_SUBTYPE_SH7786) # define SCSPTR0 0xffea0024 /* 16 bit SCIF */ -# define SCSPTR1 0xffeb0024 /* 16 bit SCIF */ -# define SCSPTR2 0xffec0024 /* 16 bit SCIF */ -# define SCSPTR3 0xffed0024 /* 16 bit SCIF */ -# define SCSPTR4 0xffee0024 /* 16 bit SCIF */ -# define SCSPTR5 0xffef0024 /* 16 bit SCIF */ # define SCIF_ORER 0x0001 /* Overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SH7201) || \ defined(CONFIG_CPU_SUBTYPE_SH7203) || \ defined(CONFIG_CPU_SUBTYPE_SH7206) || \ defined(CONFIG_CPU_SUBTYPE_SH7263) # define SCSPTR0 0xfffe8020 /* 16 bit SCIF */ -# define SCSPTR1 0xfffe8820 /* 16 bit SCIF */ -# define SCSPTR2 0xfffe9020 /* 16 bit SCIF */ -# define SCSPTR3 0xfffe9820 /* 16 bit SCIF */ -# if defined(CONFIG_CPU_SUBTYPE_SH7201) -# define SCSPTR4 0xfffeA020 /* 16 bit SCIF */ -# define SCSPTR5 0xfffeA820 /* 16 bit SCIF */ -# define SCSPTR6 0xfffeB020 /* 16 bit SCIF */ -# define SCSPTR7 0xfffeB820 /* 16 bit SCIF */ -# endif #elif defined(CONFIG_CPU_SUBTYPE_SH7619) # define SCSPTR0 0xf8400020 /* 16 bit SCIF */ -# define SCSPTR1 0xf8410020 /* 16 bit SCIF */ -# define SCSPTR2 0xf8420020 /* 16 bit SCIF */ # define SCIF_ORER 0x0001 /* overrun error bit */ #elif defined(CONFIG_CPU_SUBTYPE_SHX3) # define SCSPTR0 0xffc30020 /* 16 bit SCIF */ -# define SCSPTR1 0xffc40020 /* 16 bit SCIF */ -# define SCSPTR2 0xffc50020 /* 16 bit SCIF */ -# define SCSPTR3 0xffc60020 /* 16 bit SCIF */ # define SCIF_ORER 0x0001 /* Overrun error bit */ #else # error CPU subtype not defined @@ -411,7 +373,6 @@ SCIF_FNS(SCSPTR, 0, 0, 0x24, 16) SCIF_FNS(SCLSR, 0, 0, 0x28, 16) #elif defined(CONFIG_CPU_SUBTYPE_SH7763) SCIF_FNS(SCFDR, 0, 0, 0x1C, 16) -SCIF_FNS(SCSPTR2, 0, 0, 0x20, 16) SCIF_FNS(SCTFDR, 0x0e, 16, 0x1C, 16) SCIF_FNS(SCRFDR, 0x0e, 16, 0x20, 16) SCIF_FNS(SCSPTR, 0, 0, 0x24, 16) diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index fceea5e4e02f..41b6e51188e4 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -31,7 +31,6 @@ config USB_ARCH_HAS_OHCI # ARM: default y if SA1111 default y if ARCH_OMAP - default y if ARCH_LH7A404 default y if ARCH_S3C2410 default y if PXA27x default y if PXA3xx diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index d041c6826e43..0f299b7aad60 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -2681,17 +2681,13 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, mutex_lock(&usb_address0_mutex); - if (!udev->config && oldspeed == USB_SPEED_SUPER) { - /* Don't reset USB 3.0 devices during an initial setup */ - usb_set_device_state(udev, USB_STATE_DEFAULT); - } else { - /* Reset the device; full speed may morph to high speed */ - /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */ - retval = hub_port_reset(hub, port1, udev, delay); - if (retval < 0) /* error or disconnect */ - goto fail; - /* success, speed is known */ - } + /* Reset the device; full speed may morph to high speed */ + /* FIXME a USB 2.0 device may morph into SuperSpeed on reset. */ + retval = hub_port_reset(hub, port1, udev, delay); + if (retval < 0) /* error or disconnect */ + goto fail; + /* success, speed is known */ + retval = -ENODEV; if (oldspeed != USB_SPEED_UNKNOWN && oldspeed != udev->speed) { diff --git a/drivers/usb/core/quirks.c b/drivers/usb/core/quirks.c index 44c595432d6f..81ce6a8e1d94 100644 --- a/drivers/usb/core/quirks.c +++ b/drivers/usb/core/quirks.c @@ -48,6 +48,10 @@ static const struct usb_device_id usb_quirk_list[] = { { USB_DEVICE(0x04b4, 0x0526), .driver_info = USB_QUIRK_CONFIG_INTF_STRINGS }, + /* Samsung Android phone modem - ID conflict with SPH-I500 */ + { USB_DEVICE(0x04e8, 0x6601), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + /* Roland SC-8820 */ { USB_DEVICE(0x0582, 0x0007), .driver_info = USB_QUIRK_RESET_RESUME }, @@ -68,6 +72,10 @@ static const struct usb_device_id usb_quirk_list[] = { /* M-Systems Flash Disk Pioneers */ { USB_DEVICE(0x08ec, 0x1000), .driver_info = USB_QUIRK_RESET_RESUME }, + /* Keytouch QWERTY Panel keyboard */ + { USB_DEVICE(0x0926, 0x3333), .driver_info = + USB_QUIRK_CONFIG_INTF_STRINGS }, + /* X-Rite/Gretag-Macbeth Eye-One Pro display colorimeter */ { USB_DEVICE(0x0971, 0x2000), .driver_info = USB_QUIRK_NO_SET_INTF }, diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index d50099675f28..e8cbbdac7a15 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -176,18 +176,6 @@ config USB_FSL_USB2 default USB_GADGET select USB_GADGET_SELECTED -config USB_GADGET_LH7A40X - boolean "LH7A40X" - depends on ARCH_LH7A40X - help - This driver provides USB Device Controller driver for LH7A40x - -config USB_LH7A40X - tristate - depends on USB_GADGET_LH7A40X - default USB_GADGET - select USB_GADGET_SELECTED - config USB_GADGET_OMAP boolean "OMAP USB Device Controller" depends on ARCH_OMAP diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index 55f5e8ae5924..a2f7f9a4f4a8 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -11,7 +11,6 @@ obj-$(CONFIG_USB_PXA27X) += pxa27x_udc.o obj-$(CONFIG_USB_IMX) += imx_udc.o obj-$(CONFIG_USB_GOKU) += goku_udc.o obj-$(CONFIG_USB_OMAP) += omap_udc.o -obj-$(CONFIG_USB_LH7A40X) += lh7a40x_udc.o obj-$(CONFIG_USB_S3C2410) += s3c2410_udc.o obj-$(CONFIG_USB_AT91) += at91_udc.o obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o diff --git a/drivers/usb/gadget/f_phonet.c b/drivers/usb/gadget/f_phonet.c index 3c6e1a058745..5e1495097ec3 100644 --- a/drivers/usb/gadget/f_phonet.c +++ b/drivers/usb/gadget/f_phonet.c @@ -346,14 +346,19 @@ static void pn_rx_complete(struct usb_ep *ep, struct usb_request *req) if (unlikely(!skb)) break; - skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, 0, - req->actual); - page = NULL; - if (req->actual < req->length) { /* Last fragment */ + if (skb->len == 0) { /* First fragment */ skb->protocol = htons(ETH_P_PHONET); skb_reset_mac_header(skb); - pskb_pull(skb, 1); + /* Can't use pskb_pull() on page in IRQ */ + memcpy(skb_put(skb, 1), page_address(page), 1); + } + + skb_add_rx_frag(skb, skb_shinfo(skb)->nr_frags, page, + skb->len == 0, req->actual); + page = NULL; + + if (req->actual < req->length) { /* Last fragment */ skb->dev = dev; dev->stats.rx_packets++; dev->stats.rx_bytes += skb->len; diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index 5c2720d64ffa..e896f6359dfe 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -45,12 +45,6 @@ #define gadget_is_goku(g) 0 #endif -#ifdef CONFIG_USB_GADGET_LH7A40X -#define gadget_is_lh7a40x(g) !strcmp("lh7a40x_udc", (g)->name) -#else -#define gadget_is_lh7a40x(g) 0 -#endif - #ifdef CONFIG_USB_GADGET_OMAP #define gadget_is_omap(g) !strcmp("omap_udc", (g)->name) #else @@ -181,8 +175,6 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x06; else if (gadget_is_omap(gadget)) return 0x08; - else if (gadget_is_lh7a40x(gadget)) - return 0x09; else if (gadget_is_pxa27x(gadget)) return 0x11; else if (gadget_is_s3c2410(gadget)) diff --git a/drivers/usb/gadget/lh7a40x_udc.c b/drivers/usb/gadget/lh7a40x_udc.c deleted file mode 100644 index 6b58bd8ce623..000000000000 --- a/drivers/usb/gadget/lh7a40x_udc.c +++ /dev/null @@ -1,2152 +0,0 @@ -/* - * linux/drivers/usb/gadget/lh7a40x_udc.c - * Sharp LH7A40x on-chip full speed USB device controllers - * - * Copyright (C) 2004 Mikko Lahteenmaki, Nordic ID - * Copyright (C) 2004 Bo Henriksen, Nordic ID - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#include <linux/platform_device.h> -#include <linux/slab.h> - -#include "lh7a40x_udc.h" - -//#define DEBUG printk -//#define DEBUG_EP0 printk -//#define DEBUG_SETUP printk - -#ifndef DEBUG_EP0 -# define DEBUG_EP0(fmt,args...) -#endif -#ifndef DEBUG_SETUP -# define DEBUG_SETUP(fmt,args...) -#endif -#ifndef DEBUG -# define NO_STATES -# define DEBUG(fmt,args...) -#endif - -#define DRIVER_DESC "LH7A40x USB Device Controller" -#define DRIVER_VERSION __DATE__ - -#ifndef _BIT /* FIXME - what happended to _BIT in 2.6.7bk18? */ -#define _BIT(x) (1<<(x)) -#endif - -struct lh7a40x_udc *the_controller; - -static const char driver_name[] = "lh7a40x_udc"; -static const char driver_desc[] = DRIVER_DESC; -static const char ep0name[] = "ep0-control"; - -/* - Local definintions. -*/ - -#ifndef NO_STATES -static char *state_names[] = { - "WAIT_FOR_SETUP", - "DATA_STATE_XMIT", - "DATA_STATE_NEED_ZLP", - "WAIT_FOR_OUT_STATUS", - "DATA_STATE_RECV" -}; -#endif - -/* - Local declarations. -*/ -static int lh7a40x_ep_enable(struct usb_ep *ep, - const struct usb_endpoint_descriptor *); -static int lh7a40x_ep_disable(struct usb_ep *ep); -static struct usb_request *lh7a40x_alloc_request(struct usb_ep *ep, gfp_t); -static void lh7a40x_free_request(struct usb_ep *ep, struct usb_request *); -static int lh7a40x_queue(struct usb_ep *ep, struct usb_request *, gfp_t); -static int lh7a40x_dequeue(struct usb_ep *ep, struct usb_request *); -static int lh7a40x_set_halt(struct usb_ep *ep, int); -static int lh7a40x_fifo_status(struct usb_ep *ep); -static void lh7a40x_fifo_flush(struct usb_ep *ep); -static void lh7a40x_ep0_kick(struct lh7a40x_udc *dev, struct lh7a40x_ep *ep); -static void lh7a40x_handle_ep0(struct lh7a40x_udc *dev, u32 intr); - -static void done(struct lh7a40x_ep *ep, struct lh7a40x_request *req, - int status); -static void pio_irq_enable(int bEndpointAddress); -static void pio_irq_disable(int bEndpointAddress); -static void stop_activity(struct lh7a40x_udc *dev, - struct usb_gadget_driver *driver); -static void flush(struct lh7a40x_ep *ep); -static void udc_enable(struct lh7a40x_udc *dev); -static void udc_set_address(struct lh7a40x_udc *dev, unsigned char address); - -static struct usb_ep_ops lh7a40x_ep_ops = { - .enable = lh7a40x_ep_enable, - .disable = lh7a40x_ep_disable, - - .alloc_request = lh7a40x_alloc_request, - .free_request = lh7a40x_free_request, - - .queue = lh7a40x_queue, - .dequeue = lh7a40x_dequeue, - - .set_halt = lh7a40x_set_halt, - .fifo_status = lh7a40x_fifo_status, - .fifo_flush = lh7a40x_fifo_flush, -}; - -/* Inline code */ - -static __inline__ int write_packet(struct lh7a40x_ep *ep, - struct lh7a40x_request *req, int max) -{ - u8 *buf; - int length, count; - volatile u32 *fifo = (volatile u32 *)ep->fifo; - - buf = req->req.buf + req->req.actual; - prefetch(buf); - - length = req->req.length - req->req.actual; - length = min(length, max); - req->req.actual += length; - - DEBUG("Write %d (max %d), fifo %p\n", length, max, fifo); - - count = length; - while (count--) { - *fifo = *buf++; - } - - return length; -} - -static __inline__ void usb_set_index(u32 ep) -{ - *(volatile u32 *)io_p2v(USB_INDEX) = ep; -} - -static __inline__ u32 usb_read(u32 port) -{ - return *(volatile u32 *)io_p2v(port); -} - -static __inline__ void usb_write(u32 val, u32 port) -{ - *(volatile u32 *)io_p2v(port) = val; -} - -static __inline__ void usb_set(u32 val, u32 port) -{ - volatile u32 *ioport = (volatile u32 *)io_p2v(port); - u32 after = (*ioport) | val; - *ioport = after; -} - -static __inline__ void usb_clear(u32 val, u32 port) -{ - volatile u32 *ioport = (volatile u32 *)io_p2v(port); - u32 after = (*ioport) & ~val; - *ioport = after; -} - -/*-------------------------------------------------------------------------*/ - -#define GPIO_PORTC_DR (0x80000E08) -#define GPIO_PORTC_DDR (0x80000E18) -#define GPIO_PORTC_PDR (0x80000E70) - -/* get port C pin data register */ -#define get_portc_pdr(bit) ((usb_read(GPIO_PORTC_PDR) & _BIT(bit)) != 0) -/* get port C data direction register */ -#define get_portc_ddr(bit) ((usb_read(GPIO_PORTC_DDR) & _BIT(bit)) != 0) -/* set port C data register */ -#define set_portc_dr(bit, val) (val ? usb_set(_BIT(bit), GPIO_PORTC_DR) : usb_clear(_BIT(bit), GPIO_PORTC_DR)) -/* set port C data direction register */ -#define set_portc_ddr(bit, val) (val ? usb_set(_BIT(bit), GPIO_PORTC_DDR) : usb_clear(_BIT(bit), GPIO_PORTC_DDR)) - -/* - * LPD7A404 GPIO's: - * Port C bit 1 = USB Port 1 Power Enable - * Port C bit 2 = USB Port 1 Data Carrier Detect - */ -#define is_usb_connected() get_portc_pdr(2) - -#ifdef CONFIG_USB_GADGET_DEBUG_FILES - -static const char proc_node_name[] = "driver/udc"; - -static int -udc_proc_read(char *page, char **start, off_t off, int count, - int *eof, void *_dev) -{ - char *buf = page; - struct lh7a40x_udc *dev = _dev; - char *next = buf; - unsigned size = count; - unsigned long flags; - int t; - - if (off != 0) - return 0; - - local_irq_save(flags); - - /* basic device status */ - t = scnprintf(next, size, - DRIVER_DESC "\n" - "%s version: %s\n" - "Gadget driver: %s\n" - "Host: %s\n\n", - driver_name, DRIVER_VERSION, - dev->driver ? dev->driver->driver.name : "(none)", - is_usb_connected()? "full speed" : "disconnected"); - size -= t; - next += t; - - t = scnprintf(next, size, - "GPIO:\n" - " Port C bit 1: %d, dir %d\n" - " Port C bit 2: %d, dir %d\n\n", - get_portc_pdr(1), get_portc_ddr(1), - get_portc_pdr(2), get_portc_ddr(2) - ); - size -= t; - next += t; - - t = scnprintf(next, size, - "DCP pullup: %d\n\n", - (usb_read(USB_PM) & PM_USB_DCP) != 0); - size -= t; - next += t; - - local_irq_restore(flags); - *eof = 1; - return count - size; -} - -#define create_proc_files() create_proc_read_entry(proc_node_name, 0, NULL, udc_proc_read, dev) -#define remove_proc_files() remove_proc_entry(proc_node_name, NULL) - -#else /* !CONFIG_USB_GADGET_DEBUG_FILES */ - -#define create_proc_files() do {} while (0) -#define remove_proc_files() do {} while (0) - -#endif /* CONFIG_USB_GADGET_DEBUG_FILES */ - -/* - * udc_disable - disable USB device controller - */ -static void udc_disable(struct lh7a40x_udc *dev) -{ - DEBUG("%s, %p\n", __func__, dev); - - udc_set_address(dev, 0); - - /* Disable interrupts */ - usb_write(0, USB_IN_INT_EN); - usb_write(0, USB_OUT_INT_EN); - usb_write(0, USB_INT_EN); - - /* Disable the USB */ - usb_write(0, USB_PM); - -#ifdef CONFIG_ARCH_LH7A404 - /* Disable USB power */ - set_portc_dr(1, 0); -#endif - - /* if hardware supports it, disconnect from usb */ - /* make_usb_disappear(); */ - - dev->ep0state = WAIT_FOR_SETUP; - dev->gadget.speed = USB_SPEED_UNKNOWN; - dev->usb_address = 0; -} - -/* - * udc_reinit - initialize software state - */ -static void udc_reinit(struct lh7a40x_udc *dev) -{ - u32 i; - - DEBUG("%s, %p\n", __func__, dev); - - /* device/ep0 records init */ - INIT_LIST_HEAD(&dev->gadget.ep_list); - INIT_LIST_HEAD(&dev->gadget.ep0->ep_list); - dev->ep0state = WAIT_FOR_SETUP; - - /* basic endpoint records init */ - for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { - struct lh7a40x_ep *ep = &dev->ep[i]; - - if (i != 0) - list_add_tail(&ep->ep.ep_list, &dev->gadget.ep_list); - - ep->desc = 0; - ep->stopped = 0; - INIT_LIST_HEAD(&ep->queue); - ep->pio_irqs = 0; - } - - /* the rest was statically initialized, and is read-only */ -} - -#define BYTES2MAXP(x) (x / 8) -#define MAXP2BYTES(x) (x * 8) - -/* until it's enabled, this UDC should be completely invisible - * to any USB host. - */ -static void udc_enable(struct lh7a40x_udc *dev) -{ - int ep; - - DEBUG("%s, %p\n", __func__, dev); - - dev->gadget.speed = USB_SPEED_UNKNOWN; - -#ifdef CONFIG_ARCH_LH7A404 - /* Set Port C bit 1 & 2 as output */ - set_portc_ddr(1, 1); - set_portc_ddr(2, 1); - - /* Enable USB power */ - set_portc_dr(1, 0); -#endif - - /* - * C.f Chapter 18.1.3.1 Initializing the USB - */ - - /* Disable the USB */ - usb_clear(PM_USB_ENABLE, USB_PM); - - /* Reset APB & I/O sides of the USB */ - usb_set(USB_RESET_APB | USB_RESET_IO, USB_RESET); - mdelay(5); - usb_clear(USB_RESET_APB | USB_RESET_IO, USB_RESET); - - /* Set MAXP values for each */ - for (ep = 0; ep < UDC_MAX_ENDPOINTS; ep++) { - struct lh7a40x_ep *ep_reg = &dev->ep[ep]; - u32 csr; - - usb_set_index(ep); - - switch (ep_reg->ep_type) { - case ep_bulk_in: - case ep_interrupt: - usb_clear(USB_IN_CSR2_USB_DMA_EN | USB_IN_CSR2_AUTO_SET, - ep_reg->csr2); - /* Fall through */ - case ep_control: - usb_write(BYTES2MAXP(ep_maxpacket(ep_reg)), - USB_IN_MAXP); - break; - case ep_bulk_out: - usb_clear(USB_OUT_CSR2_USB_DMA_EN | - USB_OUT_CSR2_AUTO_CLR, ep_reg->csr2); - usb_write(BYTES2MAXP(ep_maxpacket(ep_reg)), - USB_OUT_MAXP); - break; - } - - /* Read & Write CSR1, just in case */ - csr = usb_read(ep_reg->csr1); - usb_write(csr, ep_reg->csr1); - - flush(ep_reg); - } - - /* Disable interrupts */ - usb_write(0, USB_IN_INT_EN); - usb_write(0, USB_OUT_INT_EN); - usb_write(0, USB_INT_EN); - - /* Enable interrupts */ - usb_set(USB_IN_INT_EP0, USB_IN_INT_EN); - usb_set(USB_INT_RESET_INT | USB_INT_RESUME_INT, USB_INT_EN); - /* Dont enable rest of the interrupts */ - /* usb_set(USB_IN_INT_EP3 | USB_IN_INT_EP1 | USB_IN_INT_EP0, USB_IN_INT_EN); - usb_set(USB_OUT_INT_EP2, USB_OUT_INT_EN); */ - - /* Enable SUSPEND */ - usb_set(PM_ENABLE_SUSPEND, USB_PM); - - /* Enable the USB */ - usb_set(PM_USB_ENABLE, USB_PM); - -#ifdef CONFIG_ARCH_LH7A404 - /* NOTE: DOES NOT WORK! */ - /* Let host detect UDC: - * Software must write a 0 to the PMR:DCP_CTRL bit to turn this - * transistor on and pull the USBDP pin HIGH. - */ - /* usb_clear(PM_USB_DCP, USB_PM); - usb_set(PM_USB_DCP, USB_PM); */ -#endif -} - -/* - Register entry point for the peripheral controller driver. -*/ -int usb_gadget_probe_driver(struct usb_gadget_driver *driver, - int (*bind)(struct usb_gadget *)) -{ - struct lh7a40x_udc *dev = the_controller; - int retval; - - DEBUG("%s: %s\n", __func__, driver->driver.name); - - if (!driver - || driver->speed != USB_SPEED_FULL - || !bind - || !driver->disconnect - || !driver->setup) - return -EINVAL; - if (!dev) - return -ENODEV; - if (dev->driver) - return -EBUSY; - - /* first hook up the driver ... */ - dev->driver = driver; - dev->gadget.dev.driver = &driver->driver; - - device_add(&dev->gadget.dev); - retval = bind(&dev->gadget); - if (retval) { - printk(KERN_WARNING "%s: bind to driver %s --> error %d\n", - dev->gadget.name, driver->driver.name, retval); - device_del(&dev->gadget.dev); - - dev->driver = 0; - dev->gadget.dev.driver = 0; - return retval; - } - - /* ... then enable host detection and ep0; and we're ready - * for set_configuration as well as eventual disconnect. - * NOTE: this shouldn't power up until later. - */ - printk(KERN_WARNING "%s: registered gadget driver '%s'\n", - dev->gadget.name, driver->driver.name); - - udc_enable(dev); - - return 0; -} -EXPORT_SYMBOL(usb_gadget_probe_driver); - -/* - Unregister entry point for the peripheral controller driver. -*/ -int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) -{ - struct lh7a40x_udc *dev = the_controller; - unsigned long flags; - - if (!dev) - return -ENODEV; - if (!driver || driver != dev->driver || !driver->unbind) - return -EINVAL; - - spin_lock_irqsave(&dev->lock, flags); - dev->driver = 0; - stop_activity(dev, driver); - spin_unlock_irqrestore(&dev->lock, flags); - - driver->unbind(&dev->gadget); - dev->gadget.dev.driver = NULL; - device_del(&dev->gadget.dev); - - udc_disable(dev); - - DEBUG("unregistered gadget driver '%s'\n", driver->driver.name); - return 0; -} - -EXPORT_SYMBOL(usb_gadget_unregister_driver); - -/*-------------------------------------------------------------------------*/ - -/** Write request to FIFO (max write == maxp size) - * Return: 0 = still running, 1 = completed, negative = errno - * NOTE: INDEX register must be set for EP - */ -static int write_fifo(struct lh7a40x_ep *ep, struct lh7a40x_request *req) -{ - u32 max; - u32 csr; - - max = le16_to_cpu(ep->desc->wMaxPacketSize); - - csr = usb_read(ep->csr1); - DEBUG("CSR: %x %d\n", csr, csr & USB_IN_CSR1_FIFO_NOT_EMPTY); - - if (!(csr & USB_IN_CSR1_FIFO_NOT_EMPTY)) { - unsigned count; - int is_last, is_short; - - count = write_packet(ep, req, max); - usb_set(USB_IN_CSR1_IN_PKT_RDY, ep->csr1); - - /* last packet is usually short (or a zlp) */ - if (unlikely(count != max)) - is_last = is_short = 1; - else { - if (likely(req->req.length != req->req.actual) - || req->req.zero) - is_last = 0; - else - is_last = 1; - /* interrupt/iso maxpacket may not fill the fifo */ - is_short = unlikely(max < ep_maxpacket(ep)); - } - - DEBUG("%s: wrote %s %d bytes%s%s %d left %p\n", __func__, - ep->ep.name, count, - is_last ? "/L" : "", is_short ? "/S" : "", - req->req.length - req->req.actual, req); - - /* requests complete when all IN data is in the FIFO */ - if (is_last) { - done(ep, req, 0); - if (list_empty(&ep->queue)) { - pio_irq_disable(ep_index(ep)); - } - return 1; - } - } else { - DEBUG("Hmm.. %d ep FIFO is not empty!\n", ep_index(ep)); - } - - return 0; -} - -/** Read to request from FIFO (max read == bytes in fifo) - * Return: 0 = still running, 1 = completed, negative = errno - * NOTE: INDEX register must be set for EP - */ -static int read_fifo(struct lh7a40x_ep *ep, struct lh7a40x_request *req) -{ - u32 csr; - u8 *buf; - unsigned bufferspace, count, is_short; - volatile u32 *fifo = (volatile u32 *)ep->fifo; - - /* make sure there's a packet in the FIFO. */ - csr = usb_read(ep->csr1); - if (!(csr & USB_OUT_CSR1_OUT_PKT_RDY)) { - DEBUG("%s: Packet NOT ready!\n", __func__); - return -EINVAL; - } - - buf = req->req.buf + req->req.actual; - prefetchw(buf); - bufferspace = req->req.length - req->req.actual; - - /* read all bytes from this packet */ - count = usb_read(USB_OUT_FIFO_WC1); - req->req.actual += min(count, bufferspace); - - is_short = (count < ep->ep.maxpacket); - DEBUG("read %s %02x, %d bytes%s req %p %d/%d\n", - ep->ep.name, csr, count, - is_short ? "/S" : "", req, req->req.actual, req->req.length); - - while (likely(count-- != 0)) { - u8 byte = (u8) (*fifo & 0xff); - - if (unlikely(bufferspace == 0)) { - /* this happens when the driver's buffer - * is smaller than what the host sent. - * discard the extra data. - */ - if (req->req.status != -EOVERFLOW) - printk(KERN_WARNING "%s overflow %d\n", - ep->ep.name, count); - req->req.status = -EOVERFLOW; - } else { - *buf++ = byte; - bufferspace--; - } - } - - usb_clear(USB_OUT_CSR1_OUT_PKT_RDY, ep->csr1); - - /* completion */ - if (is_short || req->req.actual == req->req.length) { - done(ep, req, 0); - usb_set(USB_OUT_CSR1_FIFO_FLUSH, ep->csr1); - - if (list_empty(&ep->queue)) - pio_irq_disable(ep_index(ep)); - return 1; - } - - /* finished that packet. the next one may be waiting... */ - return 0; -} - -/* - * done - retire a request; caller blocked irqs - * INDEX register is preserved to keep same - */ -static void done(struct lh7a40x_ep *ep, struct lh7a40x_request *req, int status) -{ - unsigned int stopped = ep->stopped; - u32 index; - - DEBUG("%s, %p\n", __func__, ep); - list_del_init(&req->queue); - - if (likely(req->req.status == -EINPROGRESS)) - req->req.status = status; - else - status = req->req.status; - - if (status && status != -ESHUTDOWN) - DEBUG("complete %s req %p stat %d len %u/%u\n", - ep->ep.name, &req->req, status, - req->req.actual, req->req.length); - - /* don't modify queue heads during completion callback */ - ep->stopped = 1; - /* Read current index (completion may modify it) */ - index = usb_read(USB_INDEX); - - spin_unlock(&ep->dev->lock); - req->req.complete(&ep->ep, &req->req); - spin_lock(&ep->dev->lock); - - /* Restore index */ - usb_set_index(index); - ep->stopped = stopped; -} - -/** Enable EP interrupt */ -static void pio_irq_enable(int ep) -{ - DEBUG("%s: %d\n", __func__, ep); - - switch (ep) { - case 1: - usb_set(USB_IN_INT_EP1, USB_IN_INT_EN); - break; - case 2: - usb_set(USB_OUT_INT_EP2, USB_OUT_INT_EN); - break; - case 3: - usb_set(USB_IN_INT_EP3, USB_IN_INT_EN); - break; - default: - DEBUG("Unknown endpoint: %d\n", ep); - break; - } -} - -/** Disable EP interrupt */ -static void pio_irq_disable(int ep) -{ - DEBUG("%s: %d\n", __func__, ep); - - switch (ep) { - case 1: - usb_clear(USB_IN_INT_EP1, USB_IN_INT_EN); - break; - case 2: - usb_clear(USB_OUT_INT_EP2, USB_OUT_INT_EN); - break; - case 3: - usb_clear(USB_IN_INT_EP3, USB_IN_INT_EN); - break; - default: - DEBUG("Unknown endpoint: %d\n", ep); - break; - } -} - -/* - * nuke - dequeue ALL requests - */ -void nuke(struct lh7a40x_ep *ep, int status) -{ - struct lh7a40x_request *req; - - DEBUG("%s, %p\n", __func__, ep); - - /* Flush FIFO */ - flush(ep); - - /* called with irqs blocked */ - while (!list_empty(&ep->queue)) { - req = list_entry(ep->queue.next, struct lh7a40x_request, queue); - done(ep, req, status); - } - - /* Disable IRQ if EP is enabled (has descriptor) */ - if (ep->desc) - pio_irq_disable(ep_index(ep)); -} - -/* -void nuke_all(struct lh7a40x_udc *dev) -{ - int n; - for(n=0; n<UDC_MAX_ENDPOINTS; n++) { - struct lh7a40x_ep *ep = &dev->ep[n]; - usb_set_index(n); - nuke(ep, 0); - } -}*/ - -/* -static void flush_all(struct lh7a40x_udc *dev) -{ - int n; - for (n = 0; n < UDC_MAX_ENDPOINTS; n++) - { - struct lh7a40x_ep *ep = &dev->ep[n]; - flush(ep); - } -} -*/ - -/** Flush EP - * NOTE: INDEX register must be set before this call - */ -static void flush(struct lh7a40x_ep *ep) -{ - DEBUG("%s, %p\n", __func__, ep); - - switch (ep->ep_type) { - case ep_control: - /* check, by implication c.f. 15.1.2.11 */ - break; - - case ep_bulk_in: - case ep_interrupt: - /* if(csr & USB_IN_CSR1_IN_PKT_RDY) */ - usb_set(USB_IN_CSR1_FIFO_FLUSH, ep->csr1); - break; - - case ep_bulk_out: - /* if(csr & USB_OUT_CSR1_OUT_PKT_RDY) */ - usb_set(USB_OUT_CSR1_FIFO_FLUSH, ep->csr1); - break; - } -} - -/** - * lh7a40x_in_epn - handle IN interrupt - */ -static void lh7a40x_in_epn(struct lh7a40x_udc *dev, u32 ep_idx, u32 intr) -{ - u32 csr; - struct lh7a40x_ep *ep = &dev->ep[ep_idx]; - struct lh7a40x_request *req; - - usb_set_index(ep_idx); - - csr = usb_read(ep->csr1); - DEBUG("%s: %d, csr %x\n", __func__, ep_idx, csr); - - if (csr & USB_IN_CSR1_SENT_STALL) { - DEBUG("USB_IN_CSR1_SENT_STALL\n"); - usb_set(USB_IN_CSR1_SENT_STALL /*|USB_IN_CSR1_SEND_STALL */ , - ep->csr1); - return; - } - - if (!ep->desc) { - DEBUG("%s: NO EP DESC\n", __func__); - return; - } - - if (list_empty(&ep->queue)) - req = 0; - else - req = list_entry(ep->queue.next, struct lh7a40x_request, queue); - - DEBUG("req: %p\n", req); - - if (!req) - return; - - write_fifo(ep, req); -} - -/* ********************************************************************************************* */ -/* Bulk OUT (recv) - */ - -static void lh7a40x_out_epn(struct lh7a40x_udc *dev, u32 ep_idx, u32 intr) -{ - struct lh7a40x_ep *ep = &dev->ep[ep_idx]; - struct lh7a40x_request *req; - - DEBUG("%s: %d\n", __func__, ep_idx); - - usb_set_index(ep_idx); - - if (ep->desc) { - u32 csr; - csr = usb_read(ep->csr1); - - while ((csr = - usb_read(ep-> - csr1)) & (USB_OUT_CSR1_OUT_PKT_RDY | - USB_OUT_CSR1_SENT_STALL)) { - DEBUG("%s: %x\n", __func__, csr); - - if (csr & USB_OUT_CSR1_SENT_STALL) { - DEBUG("%s: stall sent, flush fifo\n", - __func__); - /* usb_set(USB_OUT_CSR1_FIFO_FLUSH, ep->csr1); */ - flush(ep); - } else if (csr & USB_OUT_CSR1_OUT_PKT_RDY) { - if (list_empty(&ep->queue)) - req = 0; - else - req = - list_entry(ep->queue.next, - struct lh7a40x_request, - queue); - - if (!req) { - printk(KERN_WARNING - "%s: NULL REQ %d\n", - __func__, ep_idx); - flush(ep); - break; - } else { - read_fifo(ep, req); - } - } - - } - - } else { - /* Throw packet away.. */ - printk(KERN_WARNING "%s: No descriptor?!?\n", __func__); - flush(ep); - } -} - -static void stop_activity(struct lh7a40x_udc *dev, - struct usb_gadget_driver *driver) -{ - int i; - - /* don't disconnect drivers more than once */ - if (dev->gadget.speed == USB_SPEED_UNKNOWN) - driver = 0; - dev->gadget.speed = USB_SPEED_UNKNOWN; - - /* prevent new request submissions, kill any outstanding requests */ - for (i = 0; i < UDC_MAX_ENDPOINTS; i++) { - struct lh7a40x_ep *ep = &dev->ep[i]; - ep->stopped = 1; - - usb_set_index(i); - nuke(ep, -ESHUTDOWN); - } - - /* report disconnect; the driver is already quiesced */ - if (driver) { - spin_unlock(&dev->lock); - driver->disconnect(&dev->gadget); - spin_lock(&dev->lock); - } - - /* re-init driver-visible data structures */ - udc_reinit(dev); -} - -/** Handle USB RESET interrupt - */ -static void lh7a40x_reset_intr(struct lh7a40x_udc *dev) -{ -#if 0 /* def CONFIG_ARCH_LH7A404 */ - /* Does not work always... */ - - DEBUG("%s: %d\n", __func__, dev->usb_address); - - if (!dev->usb_address) { - /*usb_set(USB_RESET_IO, USB_RESET); - mdelay(5); - usb_clear(USB_RESET_IO, USB_RESET); */ - return; - } - /* Put the USB controller into reset. */ - usb_set(USB_RESET_IO, USB_RESET); - - /* Set Device ID to 0 */ - udc_set_address(dev, 0); - - /* Let PLL2 settle down */ - mdelay(5); - - /* Release the USB controller from reset */ - usb_clear(USB_RESET_IO, USB_RESET); - - /* Re-enable UDC */ - udc_enable(dev); - -#endif - dev->gadget.speed = USB_SPEED_FULL; -} - -/* - * lh7a40x usb client interrupt handler. - */ -static irqreturn_t lh7a40x_udc_irq(int irq, void *_dev) -{ - struct lh7a40x_udc *dev = _dev; - - DEBUG("\n\n"); - - spin_lock(&dev->lock); - - for (;;) { - u32 intr_in = usb_read(USB_IN_INT); - u32 intr_out = usb_read(USB_OUT_INT); - u32 intr_int = usb_read(USB_INT); - - /* Test also against enable bits.. (lh7a40x errata).. Sigh.. */ - u32 in_en = usb_read(USB_IN_INT_EN); - u32 out_en = usb_read(USB_OUT_INT_EN); - - if (!intr_out && !intr_in && !intr_int) - break; - - DEBUG("%s (on state %s)\n", __func__, - state_names[dev->ep0state]); - DEBUG("intr_out = %x\n", intr_out); - DEBUG("intr_in = %x\n", intr_in); - DEBUG("intr_int = %x\n", intr_int); - - if (intr_in) { - usb_write(intr_in, USB_IN_INT); - - if ((intr_in & USB_IN_INT_EP1) - && (in_en & USB_IN_INT_EP1)) { - DEBUG("USB_IN_INT_EP1\n"); - lh7a40x_in_epn(dev, 1, intr_in); - } - if ((intr_in & USB_IN_INT_EP3) - && (in_en & USB_IN_INT_EP3)) { - DEBUG("USB_IN_INT_EP3\n"); - lh7a40x_in_epn(dev, 3, intr_in); - } - if (intr_in & USB_IN_INT_EP0) { - DEBUG("USB_IN_INT_EP0 (control)\n"); - lh7a40x_handle_ep0(dev, intr_in); - } - } - - if (intr_out) { - usb_write(intr_out, USB_OUT_INT); - - if ((intr_out & USB_OUT_INT_EP2) - && (out_en & USB_OUT_INT_EP2)) { - DEBUG("USB_OUT_INT_EP2\n"); - lh7a40x_out_epn(dev, 2, intr_out); - } - } - - if (intr_int) { - usb_write(intr_int, USB_INT); - - if (intr_int & USB_INT_RESET_INT) { - lh7a40x_reset_intr(dev); - } - - if (intr_int & USB_INT_RESUME_INT) { - DEBUG("USB resume\n"); - - if (dev->gadget.speed != USB_SPEED_UNKNOWN - && dev->driver - && dev->driver->resume - && is_usb_connected()) { - dev->driver->resume(&dev->gadget); - } - } - - if (intr_int & USB_INT_SUSPEND_INT) { - DEBUG("USB suspend%s\n", - is_usb_connected()? "" : "+disconnect"); - if (!is_usb_connected()) { - stop_activity(dev, dev->driver); - } else if (dev->gadget.speed != - USB_SPEED_UNKNOWN && dev->driver - && dev->driver->suspend) { - dev->driver->suspend(&dev->gadget); - } - } - - } - } - - spin_unlock(&dev->lock); - - return IRQ_HANDLED; -} - -static int lh7a40x_ep_enable(struct usb_ep *_ep, - const struct usb_endpoint_descriptor *desc) -{ - struct lh7a40x_ep *ep; - struct lh7a40x_udc *dev; - unsigned long flags; - - DEBUG("%s, %p\n", __func__, _ep); - - ep = container_of(_ep, struct lh7a40x_ep, ep); - if (!_ep || !desc || ep->desc || _ep->name == ep0name - || desc->bDescriptorType != USB_DT_ENDPOINT - || ep->bEndpointAddress != desc->bEndpointAddress - || ep_maxpacket(ep) < le16_to_cpu(desc->wMaxPacketSize)) { - DEBUG("%s, bad ep or descriptor\n", __func__); - return -EINVAL; - } - - /* xfer types must match, except that interrupt ~= bulk */ - if (ep->bmAttributes != desc->bmAttributes - && ep->bmAttributes != USB_ENDPOINT_XFER_BULK - && desc->bmAttributes != USB_ENDPOINT_XFER_INT) { - DEBUG("%s, %s type mismatch\n", __func__, _ep->name); - return -EINVAL; - } - - /* hardware _could_ do smaller, but driver doesn't */ - if ((desc->bmAttributes == USB_ENDPOINT_XFER_BULK - && le16_to_cpu(desc->wMaxPacketSize) != ep_maxpacket(ep)) - || !desc->wMaxPacketSize) { - DEBUG("%s, bad %s maxpacket\n", __func__, _ep->name); - return -ERANGE; - } - - dev = ep->dev; - if (!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN) { - DEBUG("%s, bogus device state\n", __func__); - return -ESHUTDOWN; - } - - spin_lock_irqsave(&ep->dev->lock, flags); - - ep->stopped = 0; - ep->desc = desc; - ep->pio_irqs = 0; - ep->ep.maxpacket = le16_to_cpu(desc->wMaxPacketSize); - - spin_unlock_irqrestore(&ep->dev->lock, flags); - - /* Reset halt state (does flush) */ - lh7a40x_set_halt(_ep, 0); - - DEBUG("%s: enabled %s\n", __func__, _ep->name); - return 0; -} - -/** Disable EP - * NOTE: Sets INDEX register - */ -static int lh7a40x_ep_disable(struct usb_ep *_ep) -{ - struct lh7a40x_ep *ep; - unsigned long flags; - - DEBUG("%s, %p\n", __func__, _ep); - - ep = container_of(_ep, struct lh7a40x_ep, ep); - if (!_ep || !ep->desc) { - DEBUG("%s, %s not enabled\n", __func__, - _ep ? ep->ep.name : NULL); - return -EINVAL; - } - - spin_lock_irqsave(&ep->dev->lock, flags); - - usb_set_index(ep_index(ep)); - - /* Nuke all pending requests (does flush) */ - nuke(ep, -ESHUTDOWN); - - /* Disable ep IRQ */ - pio_irq_disable(ep_index(ep)); - - ep->desc = 0; - ep->stopped = 1; - - spin_unlock_irqrestore(&ep->dev->lock, flags); - - DEBUG("%s: disabled %s\n", __func__, _ep->name); - return 0; -} - -static struct usb_request *lh7a40x_alloc_request(struct usb_ep *ep, - gfp_t gfp_flags) -{ - struct lh7a40x_request *req; - - DEBUG("%s, %p\n", __func__, ep); - - req = kzalloc(sizeof(*req), gfp_flags); - if (!req) - return 0; - - INIT_LIST_HEAD(&req->queue); - - return &req->req; -} - -static void lh7a40x_free_request(struct usb_ep *ep, struct usb_request *_req) -{ - struct lh7a40x_request *req; - - DEBUG("%s, %p\n", __func__, ep); - - req = container_of(_req, struct lh7a40x_request, req); - WARN_ON(!list_empty(&req->queue)); - kfree(req); -} - -/** Queue one request - * Kickstart transfer if needed - * NOTE: Sets INDEX register - */ -static int lh7a40x_queue(struct usb_ep *_ep, struct usb_request *_req, - gfp_t gfp_flags) -{ - struct lh7a40x_request *req; - struct lh7a40x_ep *ep; - struct lh7a40x_udc *dev; - unsigned long flags; - - DEBUG("\n\n\n%s, %p\n", __func__, _ep); - - req = container_of(_req, struct lh7a40x_request, req); - if (unlikely - (!_req || !_req->complete || !_req->buf - || !list_empty(&req->queue))) { - DEBUG("%s, bad params\n", __func__); - return -EINVAL; - } - - ep = container_of(_ep, struct lh7a40x_ep, ep); - if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { - DEBUG("%s, bad ep\n", __func__); - return -EINVAL; - } - - dev = ep->dev; - if (unlikely(!dev->driver || dev->gadget.speed == USB_SPEED_UNKNOWN)) { - DEBUG("%s, bogus device state %p\n", __func__, dev->driver); - return -ESHUTDOWN; - } - - DEBUG("%s queue req %p, len %d buf %p\n", _ep->name, _req, _req->length, - _req->buf); - - spin_lock_irqsave(&dev->lock, flags); - - _req->status = -EINPROGRESS; - _req->actual = 0; - - /* kickstart this i/o queue? */ - DEBUG("Add to %d Q %d %d\n", ep_index(ep), list_empty(&ep->queue), - ep->stopped); - if (list_empty(&ep->queue) && likely(!ep->stopped)) { - u32 csr; - - if (unlikely(ep_index(ep) == 0)) { - /* EP0 */ - list_add_tail(&req->queue, &ep->queue); - lh7a40x_ep0_kick(dev, ep); - req = 0; - } else if (ep_is_in(ep)) { - /* EP1 & EP3 */ - usb_set_index(ep_index(ep)); - csr = usb_read(ep->csr1); - pio_irq_enable(ep_index(ep)); - if ((csr & USB_IN_CSR1_FIFO_NOT_EMPTY) == 0) { - if (write_fifo(ep, req) == 1) - req = 0; - } - } else { - /* EP2 */ - usb_set_index(ep_index(ep)); - csr = usb_read(ep->csr1); - pio_irq_enable(ep_index(ep)); - if (!(csr & USB_OUT_CSR1_FIFO_FULL)) { - if (read_fifo(ep, req) == 1) - req = 0; - } - } - } - - /* pio or dma irq handler advances the queue. */ - if (likely(req != 0)) - list_add_tail(&req->queue, &ep->queue); - - spin_unlock_irqrestore(&dev->lock, flags); - - return 0; -} - -/* dequeue JUST ONE request */ -static int lh7a40x_dequeue(struct usb_ep *_ep, struct usb_request *_req) -{ - struct lh7a40x_ep *ep; - struct lh7a40x_request *req; - unsigned long flags; - - DEBUG("%s, %p\n", __func__, _ep); - - ep = container_of(_ep, struct lh7a40x_ep, ep); - if (!_ep || ep->ep.name == ep0name) - return -EINVAL; - - spin_lock_irqsave(&ep->dev->lock, flags); - - /* make sure it's actually queued on this endpoint */ - list_for_each_entry(req, &ep->queue, queue) { - if (&req->req == _req) - break; - } - if (&req->req != _req) { - spin_unlock_irqrestore(&ep->dev->lock, flags); - return -EINVAL; - } - - done(ep, req, -ECONNRESET); - - spin_unlock_irqrestore(&ep->dev->lock, flags); - return 0; -} - -/** Halt specific EP - * Return 0 if success - * NOTE: Sets INDEX register to EP ! - */ -static int lh7a40x_set_halt(struct usb_ep *_ep, int value) -{ - struct lh7a40x_ep *ep; - unsigned long flags; - - ep = container_of(_ep, struct lh7a40x_ep, ep); - if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { - DEBUG("%s, bad ep\n", __func__); - return -EINVAL; - } - - usb_set_index(ep_index(ep)); - - DEBUG("%s, ep %d, val %d\n", __func__, ep_index(ep), value); - - spin_lock_irqsave(&ep->dev->lock, flags); - - if (ep_index(ep) == 0) { - /* EP0 */ - usb_set(EP0_SEND_STALL, ep->csr1); - } else if (ep_is_in(ep)) { - u32 csr = usb_read(ep->csr1); - if (value && ((csr & USB_IN_CSR1_FIFO_NOT_EMPTY) - || !list_empty(&ep->queue))) { - /* - * Attempts to halt IN endpoints will fail (returning -EAGAIN) - * if any transfer requests are still queued, or if the controller - * FIFO still holds bytes that the host hasn't collected. - */ - spin_unlock_irqrestore(&ep->dev->lock, flags); - DEBUG - ("Attempt to halt IN endpoint failed (returning -EAGAIN) %d %d\n", - (csr & USB_IN_CSR1_FIFO_NOT_EMPTY), - !list_empty(&ep->queue)); - return -EAGAIN; - } - flush(ep); - if (value) - usb_set(USB_IN_CSR1_SEND_STALL, ep->csr1); - else { - usb_clear(USB_IN_CSR1_SEND_STALL, ep->csr1); - usb_set(USB_IN_CSR1_CLR_DATA_TOGGLE, ep->csr1); - } - - } else { - - flush(ep); - if (value) - usb_set(USB_OUT_CSR1_SEND_STALL, ep->csr1); - else { - usb_clear(USB_OUT_CSR1_SEND_STALL, ep->csr1); - usb_set(USB_OUT_CSR1_CLR_DATA_REG, ep->csr1); - } - } - - if (value) { - ep->stopped = 1; - } else { - ep->stopped = 0; - } - - spin_unlock_irqrestore(&ep->dev->lock, flags); - - DEBUG("%s %s halted\n", _ep->name, value == 0 ? "NOT" : "IS"); - - return 0; -} - -/** Return bytes in EP FIFO - * NOTE: Sets INDEX register to EP - */ -static int lh7a40x_fifo_status(struct usb_ep *_ep) -{ - u32 csr; - int count = 0; - struct lh7a40x_ep *ep; - - ep = container_of(_ep, struct lh7a40x_ep, ep); - if (!_ep) { - DEBUG("%s, bad ep\n", __func__); - return -ENODEV; - } - - DEBUG("%s, %d\n", __func__, ep_index(ep)); - - /* LPD can't report unclaimed bytes from IN fifos */ - if (ep_is_in(ep)) - return -EOPNOTSUPP; - - usb_set_index(ep_index(ep)); - - csr = usb_read(ep->csr1); - if (ep->dev->gadget.speed != USB_SPEED_UNKNOWN || - csr & USB_OUT_CSR1_OUT_PKT_RDY) { - count = usb_read(USB_OUT_FIFO_WC1); - } - - return count; -} - -/** Flush EP FIFO - * NOTE: Sets INDEX register to EP - */ -static void lh7a40x_fifo_flush(struct usb_ep *_ep) -{ - struct lh7a40x_ep *ep; - - ep = container_of(_ep, struct lh7a40x_ep, ep); - if (unlikely(!_ep || (!ep->desc && ep->ep.name != ep0name))) { - DEBUG("%s, bad ep\n", __func__); - return; - } - - usb_set_index(ep_index(ep)); - flush(ep); -} - -/****************************************************************/ -/* End Point 0 related functions */ -/****************************************************************/ - -/* return: 0 = still running, 1 = completed, negative = errno */ -static int write_fifo_ep0(struct lh7a40x_ep *ep, struct lh7a40x_request *req) -{ - u32 max; - unsigned count; - int is_last; - - max = ep_maxpacket(ep); - - DEBUG_EP0("%s\n", __func__); - - count = write_packet(ep, req, max); - - /* last packet is usually short (or a zlp) */ - if (unlikely(count != max)) - is_last = 1; - else { - if (likely(req->req.length != req->req.actual) || req->req.zero) - is_last = 0; - else - is_last = 1; - } - - DEBUG_EP0("%s: wrote %s %d bytes%s %d left %p\n", __func__, - ep->ep.name, count, - is_last ? "/L" : "", req->req.length - req->req.actual, req); - - /* requests complete when all IN data is in the FIFO */ - if (is_last) { - done(ep, req, 0); - return 1; - } - - return 0; -} - -static __inline__ int lh7a40x_fifo_read(struct lh7a40x_ep *ep, - unsigned char *cp, int max) -{ - int bytes; - int count = usb_read(USB_OUT_FIFO_WC1); - volatile u32 *fifo = (volatile u32 *)ep->fifo; - - if (count > max) - count = max; - bytes = count; - while (count--) - *cp++ = *fifo & 0xFF; - return bytes; -} - -static __inline__ void lh7a40x_fifo_write(struct lh7a40x_ep *ep, - unsigned char *cp, int count) -{ - volatile u32 *fifo = (volatile u32 *)ep->fifo; - DEBUG_EP0("fifo_write: %d %d\n", ep_index(ep), count); - while (count--) - *fifo = *cp++; -} - -static int read_fifo_ep0(struct lh7a40x_ep *ep, struct lh7a40x_request *req) -{ - u32 csr; - u8 *buf; - unsigned bufferspace, count, is_short; - volatile u32 *fifo = (volatile u32 *)ep->fifo; - - DEBUG_EP0("%s\n", __func__); - - csr = usb_read(USB_EP0_CSR); - if (!(csr & USB_OUT_CSR1_OUT_PKT_RDY)) - return 0; - - buf = req->req.buf + req->req.actual; - prefetchw(buf); - bufferspace = req->req.length - req->req.actual; - - /* read all bytes from this packet */ - if (likely(csr & EP0_OUT_PKT_RDY)) { - count = usb_read(USB_OUT_FIFO_WC1); - req->req.actual += min(count, bufferspace); - } else /* zlp */ - count = 0; - - is_short = (count < ep->ep.maxpacket); - DEBUG_EP0("read %s %02x, %d bytes%s req %p %d/%d\n", - ep->ep.name, csr, count, - is_short ? "/S" : "", req, req->req.actual, req->req.length); - - while (likely(count-- != 0)) { - u8 byte = (u8) (*fifo & 0xff); - - if (unlikely(bufferspace == 0)) { - /* this happens when the driver's buffer - * is smaller than what the host sent. - * discard the extra data. - */ - if (req->req.status != -EOVERFLOW) - DEBUG_EP0("%s overflow %d\n", ep->ep.name, - count); - req->req.status = -EOVERFLOW; - } else { - *buf++ = byte; - bufferspace--; - } - } - - /* completion */ - if (is_short || req->req.actual == req->req.length) { - done(ep, req, 0); - return 1; - } - - /* finished that packet. the next one may be waiting... */ - return 0; -} - -/** - * udc_set_address - set the USB address for this device - * @address: - * - * Called from control endpoint function after it decodes a set address setup packet. - */ -static void udc_set_address(struct lh7a40x_udc *dev, unsigned char address) -{ - DEBUG_EP0("%s: %d\n", __func__, address); - /* c.f. 15.1.2.2 Table 15-4 address will be used after DATA_END is set */ - dev->usb_address = address; - usb_set((address & USB_FA_FUNCTION_ADDR), USB_FA); - usb_set(USB_FA_ADDR_UPDATE | (address & USB_FA_FUNCTION_ADDR), USB_FA); - /* usb_read(USB_FA); */ -} - -/* - * DATA_STATE_RECV (OUT_PKT_RDY) - * - if error - * set EP0_CLR_OUT | EP0_DATA_END | EP0_SEND_STALL bits - * - else - * set EP0_CLR_OUT bit - if last set EP0_DATA_END bit - */ -static void lh7a40x_ep0_out(struct lh7a40x_udc *dev, u32 csr) -{ - struct lh7a40x_request *req; - struct lh7a40x_ep *ep = &dev->ep[0]; - int ret; - - DEBUG_EP0("%s: %x\n", __func__, csr); - - if (list_empty(&ep->queue)) - req = 0; - else - req = list_entry(ep->queue.next, struct lh7a40x_request, queue); - - if (req) { - - if (req->req.length == 0) { - DEBUG_EP0("ZERO LENGTH OUT!\n"); - usb_set((EP0_CLR_OUT | EP0_DATA_END), USB_EP0_CSR); - dev->ep0state = WAIT_FOR_SETUP; - return; - } - ret = read_fifo_ep0(ep, req); - if (ret) { - /* Done! */ - DEBUG_EP0("%s: finished, waiting for status\n", - __func__); - - usb_set((EP0_CLR_OUT | EP0_DATA_END), USB_EP0_CSR); - dev->ep0state = WAIT_FOR_SETUP; - } else { - /* Not done yet.. */ - DEBUG_EP0("%s: not finished\n", __func__); - usb_set(EP0_CLR_OUT, USB_EP0_CSR); - } - } else { - DEBUG_EP0("NO REQ??!\n"); - } -} - -/* - * DATA_STATE_XMIT - */ -static int lh7a40x_ep0_in(struct lh7a40x_udc *dev, u32 csr) -{ - struct lh7a40x_request *req; - struct lh7a40x_ep *ep = &dev->ep[0]; - int ret, need_zlp = 0; - - DEBUG_EP0("%s: %x\n", __func__, csr); - - if (list_empty(&ep->queue)) - req = 0; - else - req = list_entry(ep->queue.next, struct lh7a40x_request, queue); - - if (!req) { - DEBUG_EP0("%s: NULL REQ\n", __func__); - return 0; - } - - if (req->req.length == 0) { - - usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR); - dev->ep0state = WAIT_FOR_SETUP; - return 1; - } - - if (req->req.length - req->req.actual == EP0_PACKETSIZE) { - /* Next write will end with the packet size, */ - /* so we need Zero-length-packet */ - need_zlp = 1; - } - - ret = write_fifo_ep0(ep, req); - - if (ret == 1 && !need_zlp) { - /* Last packet */ - DEBUG_EP0("%s: finished, waiting for status\n", __func__); - - usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR); - dev->ep0state = WAIT_FOR_SETUP; - } else { - DEBUG_EP0("%s: not finished\n", __func__); - usb_set(EP0_IN_PKT_RDY, USB_EP0_CSR); - } - - if (need_zlp) { - DEBUG_EP0("%s: Need ZLP!\n", __func__); - usb_set(EP0_IN_PKT_RDY, USB_EP0_CSR); - dev->ep0state = DATA_STATE_NEED_ZLP; - } - - return 1; -} - -static int lh7a40x_handle_get_status(struct lh7a40x_udc *dev, - struct usb_ctrlrequest *ctrl) -{ - struct lh7a40x_ep *ep0 = &dev->ep[0]; - struct lh7a40x_ep *qep; - int reqtype = (ctrl->bRequestType & USB_RECIP_MASK); - u16 val = 0; - - if (reqtype == USB_RECIP_INTERFACE) { - /* This is not supported. - * And according to the USB spec, this one does nothing.. - * Just return 0 - */ - DEBUG_SETUP("GET_STATUS: USB_RECIP_INTERFACE\n"); - } else if (reqtype == USB_RECIP_DEVICE) { - DEBUG_SETUP("GET_STATUS: USB_RECIP_DEVICE\n"); - val |= (1 << 0); /* Self powered */ - /*val |= (1<<1); *//* Remote wakeup */ - } else if (reqtype == USB_RECIP_ENDPOINT) { - int ep_num = (ctrl->wIndex & ~USB_DIR_IN); - - DEBUG_SETUP - ("GET_STATUS: USB_RECIP_ENDPOINT (%d), ctrl->wLength = %d\n", - ep_num, ctrl->wLength); - - if (ctrl->wLength > 2 || ep_num > 3) - return -EOPNOTSUPP; - - qep = &dev->ep[ep_num]; - if (ep_is_in(qep) != ((ctrl->wIndex & USB_DIR_IN) ? 1 : 0) - && ep_index(qep) != 0) { - return -EOPNOTSUPP; - } - - usb_set_index(ep_index(qep)); - - /* Return status on next IN token */ - switch (qep->ep_type) { - case ep_control: - val = - (usb_read(qep->csr1) & EP0_SEND_STALL) == - EP0_SEND_STALL; - break; - case ep_bulk_in: - case ep_interrupt: - val = - (usb_read(qep->csr1) & USB_IN_CSR1_SEND_STALL) == - USB_IN_CSR1_SEND_STALL; - break; - case ep_bulk_out: - val = - (usb_read(qep->csr1) & USB_OUT_CSR1_SEND_STALL) == - USB_OUT_CSR1_SEND_STALL; - break; - } - - /* Back to EP0 index */ - usb_set_index(0); - - DEBUG_SETUP("GET_STATUS, ep: %d (%x), val = %d\n", ep_num, - ctrl->wIndex, val); - } else { - DEBUG_SETUP("Unknown REQ TYPE: %d\n", reqtype); - return -EOPNOTSUPP; - } - - /* Clear "out packet ready" */ - usb_set((EP0_CLR_OUT), USB_EP0_CSR); - /* Put status to FIFO */ - lh7a40x_fifo_write(ep0, (u8 *) & val, sizeof(val)); - /* Issue "In packet ready" */ - usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR); - - return 0; -} - -/* - * WAIT_FOR_SETUP (OUT_PKT_RDY) - * - read data packet from EP0 FIFO - * - decode command - * - if error - * set EP0_CLR_OUT | EP0_DATA_END | EP0_SEND_STALL bits - * - else - * set EP0_CLR_OUT | EP0_DATA_END bits - */ -static void lh7a40x_ep0_setup(struct lh7a40x_udc *dev, u32 csr) -{ - struct lh7a40x_ep *ep = &dev->ep[0]; - struct usb_ctrlrequest ctrl; - int i, bytes, is_in; - - DEBUG_SETUP("%s: %x\n", __func__, csr); - - /* Nuke all previous transfers */ - nuke(ep, -EPROTO); - - /* read control req from fifo (8 bytes) */ - bytes = lh7a40x_fifo_read(ep, (unsigned char *)&ctrl, 8); - - DEBUG_SETUP("Read CTRL REQ %d bytes\n", bytes); - DEBUG_SETUP("CTRL.bRequestType = %d (is_in %d)\n", ctrl.bRequestType, - ctrl.bRequestType == USB_DIR_IN); - DEBUG_SETUP("CTRL.bRequest = %d\n", ctrl.bRequest); - DEBUG_SETUP("CTRL.wLength = %d\n", ctrl.wLength); - DEBUG_SETUP("CTRL.wValue = %d (%d)\n", ctrl.wValue, ctrl.wValue >> 8); - DEBUG_SETUP("CTRL.wIndex = %d\n", ctrl.wIndex); - - /* Set direction of EP0 */ - if (likely(ctrl.bRequestType & USB_DIR_IN)) { - ep->bEndpointAddress |= USB_DIR_IN; - is_in = 1; - } else { - ep->bEndpointAddress &= ~USB_DIR_IN; - is_in = 0; - } - - dev->req_pending = 1; - - /* Handle some SETUP packets ourselves */ - switch (ctrl.bRequest) { - case USB_REQ_SET_ADDRESS: - if (ctrl.bRequestType != (USB_TYPE_STANDARD | USB_RECIP_DEVICE)) - break; - - DEBUG_SETUP("USB_REQ_SET_ADDRESS (%d)\n", ctrl.wValue); - udc_set_address(dev, ctrl.wValue); - usb_set((EP0_CLR_OUT | EP0_DATA_END), USB_EP0_CSR); - return; - - case USB_REQ_GET_STATUS:{ - if (lh7a40x_handle_get_status(dev, &ctrl) == 0) - return; - - case USB_REQ_CLEAR_FEATURE: - case USB_REQ_SET_FEATURE: - if (ctrl.bRequestType == USB_RECIP_ENDPOINT) { - struct lh7a40x_ep *qep; - int ep_num = (ctrl.wIndex & 0x0f); - - /* Support only HALT feature */ - if (ctrl.wValue != 0 || ctrl.wLength != 0 - || ep_num > 3 || ep_num < 1) - break; - - qep = &dev->ep[ep_num]; - spin_unlock(&dev->lock); - if (ctrl.bRequest == USB_REQ_SET_FEATURE) { - DEBUG_SETUP("SET_FEATURE (%d)\n", - ep_num); - lh7a40x_set_halt(&qep->ep, 1); - } else { - DEBUG_SETUP("CLR_FEATURE (%d)\n", - ep_num); - lh7a40x_set_halt(&qep->ep, 0); - } - spin_lock(&dev->lock); - usb_set_index(0); - - /* Reply with a ZLP on next IN token */ - usb_set((EP0_CLR_OUT | EP0_DATA_END), - USB_EP0_CSR); - return; - } - break; - } - - default: - break; - } - - if (likely(dev->driver)) { - /* device-2-host (IN) or no data setup command, process immediately */ - spin_unlock(&dev->lock); - i = dev->driver->setup(&dev->gadget, &ctrl); - spin_lock(&dev->lock); - - if (i < 0) { - /* setup processing failed, force stall */ - DEBUG_SETUP - (" --> ERROR: gadget setup FAILED (stalling), setup returned %d\n", - i); - usb_set_index(0); - usb_set((EP0_CLR_OUT | EP0_DATA_END | EP0_SEND_STALL), - USB_EP0_CSR); - - /* ep->stopped = 1; */ - dev->ep0state = WAIT_FOR_SETUP; - } - } -} - -/* - * DATA_STATE_NEED_ZLP - */ -static void lh7a40x_ep0_in_zlp(struct lh7a40x_udc *dev, u32 csr) -{ - DEBUG_EP0("%s: %x\n", __func__, csr); - - /* c.f. Table 15-14 */ - usb_set((EP0_IN_PKT_RDY | EP0_DATA_END), USB_EP0_CSR); - dev->ep0state = WAIT_FOR_SETUP; -} - -/* - * handle ep0 interrupt - */ -static void lh7a40x_handle_ep0(struct lh7a40x_udc *dev, u32 intr) -{ - struct lh7a40x_ep *ep = &dev->ep[0]; - u32 csr; - - /* Set index 0 */ - usb_set_index(0); - csr = usb_read(USB_EP0_CSR); - - DEBUG_EP0("%s: csr = %x\n", __func__, csr); - - /* - * For overview of what we should be doing see c.f. Chapter 18.1.2.4 - * We will follow that outline here modified by our own global state - * indication which provides hints as to what we think should be - * happening.. - */ - - /* - * if SENT_STALL is set - * - clear the SENT_STALL bit - */ - if (csr & EP0_SENT_STALL) { - DEBUG_EP0("%s: EP0_SENT_STALL is set: %x\n", __func__, csr); - usb_clear((EP0_SENT_STALL | EP0_SEND_STALL), USB_EP0_CSR); - nuke(ep, -ECONNABORTED); - dev->ep0state = WAIT_FOR_SETUP; - return; - } - - /* - * if a transfer is in progress && IN_PKT_RDY and OUT_PKT_RDY are clear - * - fill EP0 FIFO - * - if last packet - * - set IN_PKT_RDY | DATA_END - * - else - * set IN_PKT_RDY - */ - if (!(csr & (EP0_IN_PKT_RDY | EP0_OUT_PKT_RDY))) { - DEBUG_EP0("%s: IN_PKT_RDY and OUT_PKT_RDY are clear\n", - __func__); - - switch (dev->ep0state) { - case DATA_STATE_XMIT: - DEBUG_EP0("continue with DATA_STATE_XMIT\n"); - lh7a40x_ep0_in(dev, csr); - return; - case DATA_STATE_NEED_ZLP: - DEBUG_EP0("continue with DATA_STATE_NEED_ZLP\n"); - lh7a40x_ep0_in_zlp(dev, csr); - return; - default: - /* Stall? */ - DEBUG_EP0("Odd state!! state = %s\n", - state_names[dev->ep0state]); - dev->ep0state = WAIT_FOR_SETUP; - /* nuke(ep, 0); */ - /* usb_set(EP0_SEND_STALL, ep->csr1); */ - break; - } - } - - /* - * if SETUP_END is set - * - abort the last transfer - * - set SERVICED_SETUP_END_BIT - */ - if (csr & EP0_SETUP_END) { - DEBUG_EP0("%s: EP0_SETUP_END is set: %x\n", __func__, csr); - - usb_set(EP0_CLR_SETUP_END, USB_EP0_CSR); - - nuke(ep, 0); - dev->ep0state = WAIT_FOR_SETUP; - } - - /* - * if EP0_OUT_PKT_RDY is set - * - read data packet from EP0 FIFO - * - decode command - * - if error - * set SERVICED_OUT_PKT_RDY | DATA_END bits | SEND_STALL - * - else - * set SERVICED_OUT_PKT_RDY | DATA_END bits - */ - if (csr & EP0_OUT_PKT_RDY) { - - DEBUG_EP0("%s: EP0_OUT_PKT_RDY is set: %x\n", __func__, - csr); - - switch (dev->ep0state) { - case WAIT_FOR_SETUP: - DEBUG_EP0("WAIT_FOR_SETUP\n"); - lh7a40x_ep0_setup(dev, csr); - break; - - case DATA_STATE_RECV: - DEBUG_EP0("DATA_STATE_RECV\n"); - lh7a40x_ep0_out(dev, csr); - break; - - default: - /* send stall? */ - DEBUG_EP0("strange state!! 2. send stall? state = %d\n", - dev->ep0state); - break; - } - } -} - -static void lh7a40x_ep0_kick(struct lh7a40x_udc *dev, struct lh7a40x_ep *ep) -{ - u32 csr; - - usb_set_index(0); - csr = usb_read(USB_EP0_CSR); - - DEBUG_EP0("%s: %x\n", __func__, csr); - - /* Clear "out packet ready" */ - usb_set(EP0_CLR_OUT, USB_EP0_CSR); - - if (ep_is_in(ep)) { - dev->ep0state = DATA_STATE_XMIT; - lh7a40x_ep0_in(dev, csr); - } else { - dev->ep0state = DATA_STATE_RECV; - lh7a40x_ep0_out(dev, csr); - } -} - -/* --------------------------------------------------------------------------- - * device-scoped parts of the api to the usb controller hardware - * --------------------------------------------------------------------------- - */ - -static int lh7a40x_udc_get_frame(struct usb_gadget *_gadget) -{ - u32 frame1 = usb_read(USB_FRM_NUM1); /* Least significant 8 bits */ - u32 frame2 = usb_read(USB_FRM_NUM2); /* Most significant 3 bits */ - DEBUG("%s, %p\n", __func__, _gadget); - return ((frame2 & 0x07) << 8) | (frame1 & 0xff); -} - -static int lh7a40x_udc_wakeup(struct usb_gadget *_gadget) -{ - /* host may not have enabled remote wakeup */ - /*if ((UDCCS0 & UDCCS0_DRWF) == 0) - return -EHOSTUNREACH; - udc_set_mask_UDCCR(UDCCR_RSM); */ - return -ENOTSUPP; -} - -static const struct usb_gadget_ops lh7a40x_udc_ops = { - .get_frame = lh7a40x_udc_get_frame, - .wakeup = lh7a40x_udc_wakeup, - /* current versions must always be self-powered */ -}; - -static void nop_release(struct device *dev) -{ - DEBUG("%s %s\n", __func__, dev_name(dev)); -} - -static struct lh7a40x_udc memory = { - .usb_address = 0, - - .gadget = { - .ops = &lh7a40x_udc_ops, - .ep0 = &memory.ep[0].ep, - .name = driver_name, - .dev = { - .init_name = "gadget", - .release = nop_release, - }, - }, - - /* control endpoint */ - .ep[0] = { - .ep = { - .name = ep0name, - .ops = &lh7a40x_ep_ops, - .maxpacket = EP0_PACKETSIZE, - }, - .dev = &memory, - - .bEndpointAddress = 0, - .bmAttributes = 0, - - .ep_type = ep_control, - .fifo = io_p2v(USB_EP0_FIFO), - .csr1 = USB_EP0_CSR, - .csr2 = USB_EP0_CSR, - }, - - /* first group of endpoints */ - .ep[1] = { - .ep = { - .name = "ep1in-bulk", - .ops = &lh7a40x_ep_ops, - .maxpacket = 64, - }, - .dev = &memory, - - .bEndpointAddress = USB_DIR_IN | 1, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - - .ep_type = ep_bulk_in, - .fifo = io_p2v(USB_EP1_FIFO), - .csr1 = USB_IN_CSR1, - .csr2 = USB_IN_CSR2, - }, - - .ep[2] = { - .ep = { - .name = "ep2out-bulk", - .ops = &lh7a40x_ep_ops, - .maxpacket = 64, - }, - .dev = &memory, - - .bEndpointAddress = 2, - .bmAttributes = USB_ENDPOINT_XFER_BULK, - - .ep_type = ep_bulk_out, - .fifo = io_p2v(USB_EP2_FIFO), - .csr1 = USB_OUT_CSR1, - .csr2 = USB_OUT_CSR2, - }, - - .ep[3] = { - .ep = { - .name = "ep3in-int", - .ops = &lh7a40x_ep_ops, - .maxpacket = 64, - }, - .dev = &memory, - - .bEndpointAddress = USB_DIR_IN | 3, - .bmAttributes = USB_ENDPOINT_XFER_INT, - - .ep_type = ep_interrupt, - .fifo = io_p2v(USB_EP3_FIFO), - .csr1 = USB_IN_CSR1, - .csr2 = USB_IN_CSR2, - }, -}; - -/* - * probe - binds to the platform device - */ -static int lh7a40x_udc_probe(struct platform_device *pdev) -{ - struct lh7a40x_udc *dev = &memory; - int retval; - - DEBUG("%s: %p\n", __func__, pdev); - - spin_lock_init(&dev->lock); - dev->dev = &pdev->dev; - - device_initialize(&dev->gadget.dev); - dev->gadget.dev.parent = &pdev->dev; - - the_controller = dev; - platform_set_drvdata(pdev, dev); - - udc_disable(dev); - udc_reinit(dev); - - /* irq setup after old hardware state is cleaned up */ - retval = - request_irq(IRQ_USBINTR, lh7a40x_udc_irq, IRQF_DISABLED, driver_name, - dev); - if (retval != 0) { - DEBUG(KERN_ERR "%s: can't get irq %i, err %d\n", driver_name, - IRQ_USBINTR, retval); - return -EBUSY; - } - - create_proc_files(); - - return retval; -} - -static int lh7a40x_udc_remove(struct platform_device *pdev) -{ - struct lh7a40x_udc *dev = platform_get_drvdata(pdev); - - DEBUG("%s: %p\n", __func__, pdev); - - if (dev->driver) - return -EBUSY; - - udc_disable(dev); - remove_proc_files(); - - free_irq(IRQ_USBINTR, dev); - - platform_set_drvdata(pdev, 0); - - the_controller = 0; - - return 0; -} - -/*-------------------------------------------------------------------------*/ - -static struct platform_driver udc_driver = { - .probe = lh7a40x_udc_probe, - .remove = lh7a40x_udc_remove, - /* FIXME power management support */ - /* .suspend = ... disable UDC */ - /* .resume = ... re-enable UDC */ - .driver = { - .name = (char *)driver_name, - .owner = THIS_MODULE, - }, -}; - -static int __init udc_init(void) -{ - DEBUG("%s: %s version %s\n", __func__, driver_name, DRIVER_VERSION); - return platform_driver_register(&udc_driver); -} - -static void __exit udc_exit(void) -{ - platform_driver_unregister(&udc_driver); -} - -module_init(udc_init); -module_exit(udc_exit); - -MODULE_DESCRIPTION(DRIVER_DESC); -MODULE_AUTHOR("Mikko Lahteenmaki, Bo Henriksen"); -MODULE_LICENSE("GPL"); -MODULE_ALIAS("platform:lh7a40x_udc"); diff --git a/drivers/usb/gadget/lh7a40x_udc.h b/drivers/usb/gadget/lh7a40x_udc.h deleted file mode 100644 index ca861203a301..000000000000 --- a/drivers/usb/gadget/lh7a40x_udc.h +++ /dev/null @@ -1,259 +0,0 @@ -/* - * linux/drivers/usb/gadget/lh7a40x_udc.h - * Sharp LH7A40x on-chip full speed USB device controllers - * - * Copyright (C) 2004 Mikko Lahteenmaki, Nordic ID - * Copyright (C) 2004 Bo Henriksen, Nordic ID - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - * - */ - -#ifndef __LH7A40X_H_ -#define __LH7A40X_H_ - -#include <linux/module.h> -#include <linux/kernel.h> -#include <linux/ioport.h> -#include <linux/types.h> -#include <linux/errno.h> -#include <linux/delay.h> -#include <linux/sched.h> -#include <linux/slab.h> -#include <linux/init.h> -#include <linux/timer.h> -#include <linux/list.h> -#include <linux/interrupt.h> -#include <linux/proc_fs.h> -#include <linux/mm.h> -#include <linux/device.h> -#include <linux/dma-mapping.h> - -#include <asm/byteorder.h> -#include <asm/dma.h> -#include <asm/io.h> -#include <asm/irq.h> -#include <asm/system.h> -#include <asm/unaligned.h> -#include <mach/hardware.h> - -#include <linux/usb/ch9.h> -#include <linux/usb/gadget.h> - -/* - * Memory map - */ - -#define USB_FA 0x80000200 // function address register -#define USB_PM 0x80000204 // power management register - -#define USB_IN_INT 0x80000208 // IN interrupt register bank (EP0-EP3) -#define USB_OUT_INT 0x80000210 // OUT interrupt register bank (EP2) -#define USB_INT 0x80000218 // interrupt register bank - -#define USB_IN_INT_EN 0x8000021C // IN interrupt enable register bank -#define USB_OUT_INT_EN 0x80000224 // OUT interrupt enable register bank -#define USB_INT_EN 0x8000022C // USB interrupt enable register bank - -#define USB_FRM_NUM1 0x80000230 // Frame number1 register -#define USB_FRM_NUM2 0x80000234 // Frame number2 register -#define USB_INDEX 0x80000238 // index register - -#define USB_IN_MAXP 0x80000240 // IN MAXP register -#define USB_IN_CSR1 0x80000244 // IN CSR1 register/EP0 CSR register -#define USB_EP0_CSR 0x80000244 // IN CSR1 register/EP0 CSR register -#define USB_IN_CSR2 0x80000248 // IN CSR2 register -#define USB_OUT_MAXP 0x8000024C // OUT MAXP register - -#define USB_OUT_CSR1 0x80000250 // OUT CSR1 register -#define USB_OUT_CSR2 0x80000254 // OUT CSR2 register -#define USB_OUT_FIFO_WC1 0x80000258 // OUT FIFO write count1 register -#define USB_OUT_FIFO_WC2 0x8000025C // OUT FIFO write count2 register - -#define USB_RESET 0x8000044C // USB reset register - -#define USB_EP0_FIFO 0x80000280 -#define USB_EP1_FIFO 0x80000284 -#define USB_EP2_FIFO 0x80000288 -#define USB_EP3_FIFO 0x8000028c - -/* - * USB reset register - */ -#define USB_RESET_APB (1<<1) //resets USB APB control side WRITE -#define USB_RESET_IO (1<<0) //resets USB IO side WRITE - -/* - * USB function address register - */ -#define USB_FA_ADDR_UPDATE (1<<7) -#define USB_FA_FUNCTION_ADDR (0x7F) - -/* - * Power Management register - */ -#define PM_USB_DCP (1<<5) -#define PM_USB_ENABLE (1<<4) -#define PM_USB_RESET (1<<3) -#define PM_UC_RESUME (1<<2) -#define PM_SUSPEND_MODE (1<<1) -#define PM_ENABLE_SUSPEND (1<<0) - -/* - * IN interrupt register - */ -#define USB_IN_INT_EP3 (1<<3) -#define USB_IN_INT_EP1 (1<<1) -#define USB_IN_INT_EP0 (1<<0) - -/* - * OUT interrupt register - */ -#define USB_OUT_INT_EP2 (1<<2) - -/* - * USB interrupt register - */ -#define USB_INT_RESET_INT (1<<2) -#define USB_INT_RESUME_INT (1<<1) -#define USB_INT_SUSPEND_INT (1<<0) - -/* - * USB interrupt enable register - */ -#define USB_INT_EN_USB_RESET_INTER (1<<2) -#define USB_INT_EN_RESUME_INTER (1<<1) -#define USB_INT_EN_SUSPEND_INTER (1<<0) - -/* - * INCSR1 register - */ -#define USB_IN_CSR1_CLR_DATA_TOGGLE (1<<6) -#define USB_IN_CSR1_SENT_STALL (1<<5) -#define USB_IN_CSR1_SEND_STALL (1<<4) -#define USB_IN_CSR1_FIFO_FLUSH (1<<3) -#define USB_IN_CSR1_FIFO_NOT_EMPTY (1<<1) -#define USB_IN_CSR1_IN_PKT_RDY (1<<0) - -/* - * INCSR2 register - */ -#define USB_IN_CSR2_AUTO_SET (1<<7) -#define USB_IN_CSR2_USB_DMA_EN (1<<4) - -/* - * OUT CSR1 register - */ -#define USB_OUT_CSR1_CLR_DATA_REG (1<<7) -#define USB_OUT_CSR1_SENT_STALL (1<<6) -#define USB_OUT_CSR1_SEND_STALL (1<<5) -#define USB_OUT_CSR1_FIFO_FLUSH (1<<4) -#define USB_OUT_CSR1_FIFO_FULL (1<<1) -#define USB_OUT_CSR1_OUT_PKT_RDY (1<<0) - -/* - * OUT CSR2 register - */ -#define USB_OUT_CSR2_AUTO_CLR (1<<7) -#define USB_OUT_CSR2_USB_DMA_EN (1<<4) - -/* - * EP0 CSR - */ -#define EP0_CLR_SETUP_END (1<<7) /* Clear "Setup Ends" Bit (w) */ -#define EP0_CLR_OUT (1<<6) /* Clear "Out packet ready" Bit (w) */ -#define EP0_SEND_STALL (1<<5) /* Send STALL Handshake (rw) */ -#define EP0_SETUP_END (1<<4) /* Setup Ends (r) */ - -#define EP0_DATA_END (1<<3) /* Data end (rw) */ -#define EP0_SENT_STALL (1<<2) /* Sent Stall Handshake (r) */ -#define EP0_IN_PKT_RDY (1<<1) /* In packet ready (rw) */ -#define EP0_OUT_PKT_RDY (1<<0) /* Out packet ready (r) */ - -/* general CSR */ -#define OUT_PKT_RDY (1<<0) -#define IN_PKT_RDY (1<<0) - -/* - * IN/OUT MAXP register - */ -#define USB_OUT_MAXP_MAXP (0xF) -#define USB_IN_MAXP_MAXP (0xF) - -// Max packet size -//#define EP0_PACKETSIZE 0x10 -#define EP0_PACKETSIZE 0x8 -#define EP0_MAXPACKETSIZE 0x10 - -#define UDC_MAX_ENDPOINTS 4 - -#define WAIT_FOR_SETUP 0 -#define DATA_STATE_XMIT 1 -#define DATA_STATE_NEED_ZLP 2 -#define WAIT_FOR_OUT_STATUS 3 -#define DATA_STATE_RECV 4 - -/* ********************************************************************************************* */ -/* IO - */ - -typedef enum ep_type { - ep_control, ep_bulk_in, ep_bulk_out, ep_interrupt -} ep_type_t; - -struct lh7a40x_ep { - struct usb_ep ep; - struct lh7a40x_udc *dev; - - const struct usb_endpoint_descriptor *desc; - struct list_head queue; - unsigned long pio_irqs; - - u8 stopped; - u8 bEndpointAddress; - u8 bmAttributes; - - ep_type_t ep_type; - u32 fifo; - u32 csr1; - u32 csr2; -}; - -struct lh7a40x_request { - struct usb_request req; - struct list_head queue; -}; - -struct lh7a40x_udc { - struct usb_gadget gadget; - struct usb_gadget_driver *driver; - struct device *dev; - spinlock_t lock; - - int ep0state; - struct lh7a40x_ep ep[UDC_MAX_ENDPOINTS]; - - unsigned char usb_address; - - unsigned req_pending:1, req_std:1, req_config:1; -}; - -extern struct lh7a40x_udc *the_controller; - -#define ep_is_in(EP) (((EP)->bEndpointAddress&USB_DIR_IN)==USB_DIR_IN) -#define ep_index(EP) ((EP)->bEndpointAddress&0xF) -#define ep_maxpacket(EP) ((EP)->ep.maxpacket) - -#endif diff --git a/drivers/usb/host/ehci-mxc.c b/drivers/usb/host/ehci-mxc.c index c8e360d7d975..25c8c10bb689 100644 --- a/drivers/usb/host/ehci-mxc.c +++ b/drivers/usb/host/ehci-mxc.c @@ -203,11 +203,6 @@ static int ehci_mxc_drv_probe(struct platform_device *pdev) mdelay(10); } - /* setup specific usb hw */ - ret = mxc_initialize_usb_hw(pdev->id, pdata->flags); - if (ret < 0) - goto err_init; - ehci = hcd_to_ehci(hcd); /* EHCI registers start at offset 0x100 */ diff --git a/drivers/usb/host/ehci-xilinx-of.c b/drivers/usb/host/ehci-xilinx-of.c index e8f4f36fdf0b..a6f21b891f68 100644 --- a/drivers/usb/host/ehci-xilinx-of.c +++ b/drivers/usb/host/ehci-xilinx-of.c @@ -29,6 +29,7 @@ #include <linux/of.h> #include <linux/of_platform.h> +#include <linux/of_address.h> /** * ehci_xilinx_of_setup - Initialize the device for ehci_reset() diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 759a12ff8048..b426c1e8a679 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -1023,11 +1023,6 @@ MODULE_LICENSE ("GPL"); #define OMAP3_PLATFORM_DRIVER ohci_hcd_omap3_driver #endif -#ifdef CONFIG_ARCH_LH7A404 -#include "ohci-lh7a404.c" -#define PLATFORM_DRIVER ohci_hcd_lh7a404_driver -#endif - #if defined(CONFIG_PXA27x) || defined(CONFIG_PXA3xx) #include "ohci-pxa27x.c" #define PLATFORM_DRIVER ohci_hcd_pxa27x_driver diff --git a/drivers/usb/host/ohci-lh7a404.c b/drivers/usb/host/ohci-lh7a404.c deleted file mode 100644 index 18d39f0463ee..000000000000 --- a/drivers/usb/host/ohci-lh7a404.c +++ /dev/null @@ -1,252 +0,0 @@ -/* - * OHCI HCD (Host Controller Driver) for USB. - * - * (C) Copyright 1999 Roman Weissgaerber <weissg@vienna.at> - * (C) Copyright 2000-2002 David Brownell <dbrownell@users.sourceforge.net> - * (C) Copyright 2002 Hewlett-Packard Company - * - * Bus Glue for Sharp LH7A404 - * - * Written by Christopher Hoover <ch@hpl.hp.com> - * Based on fragments of previous driver by Russell King et al. - * - * Modified for LH7A404 from ohci-sa1111.c - * by Durgesh Pattamatta <pattamattad@sharpsec.com> - * - * This file is licenced under the GPL. - */ - -#include <linux/platform_device.h> -#include <linux/signal.h> - -#include <mach/hardware.h> - - -extern int usb_disabled(void); - -/*-------------------------------------------------------------------------*/ - -static void lh7a404_start_hc(struct platform_device *dev) -{ - printk(KERN_DEBUG "%s: starting LH7A404 OHCI USB Controller\n", - __FILE__); - - /* - * Now, carefully enable the USB clock, and take - * the USB host controller out of reset. - */ - CSC_PWRCNT |= CSC_PWRCNT_USBH_EN; /* Enable clock */ - udelay(1000); - USBH_CMDSTATUS = OHCI_HCR; - - printk(KERN_DEBUG "%s: Clock to USB host has been enabled \n", __FILE__); -} - -static void lh7a404_stop_hc(struct platform_device *dev) -{ - printk(KERN_DEBUG "%s: stopping LH7A404 OHCI USB Controller\n", - __FILE__); - - CSC_PWRCNT &= ~CSC_PWRCNT_USBH_EN; /* Disable clock */ -} - - -/*-------------------------------------------------------------------------*/ - -/* configure so an HC device and id are always provided */ -/* always called with process context; sleeping is OK */ - - -/** - * usb_hcd_lh7a404_probe - initialize LH7A404-based HCDs - * Context: !in_interrupt() - * - * Allocates basic resources for this USB host controller, and - * then invokes the start() method for the HCD associated with it - * through the hotplug entry's driver_data. - * - */ -int usb_hcd_lh7a404_probe (const struct hc_driver *driver, - struct platform_device *dev) -{ - int retval; - struct usb_hcd *hcd; - - if (dev->resource[1].flags != IORESOURCE_IRQ) { - pr_debug("resource[1] is not IORESOURCE_IRQ"); - return -ENOMEM; - } - - hcd = usb_create_hcd(driver, &dev->dev, "lh7a404"); - if (!hcd) - return -ENOMEM; - hcd->rsrc_start = dev->resource[0].start; - hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1; - - if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { - pr_debug("request_mem_region failed"); - retval = -EBUSY; - goto err1; - } - - hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); - if (!hcd->regs) { - pr_debug("ioremap failed"); - retval = -ENOMEM; - goto err2; - } - - lh7a404_start_hc(dev); - ohci_hcd_init(hcd_to_ohci(hcd)); - - retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED); - if (retval == 0) - return retval; - - lh7a404_stop_hc(dev); - iounmap(hcd->regs); - err2: - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - err1: - usb_put_hcd(hcd); - return retval; -} - - -/* may be called without controller electrically present */ -/* may be called with controller, bus, and devices active */ - -/** - * usb_hcd_lh7a404_remove - shutdown processing for LH7A404-based HCDs - * @dev: USB Host Controller being removed - * Context: !in_interrupt() - * - * Reverses the effect of usb_hcd_lh7a404_probe(), first invoking - * the HCD's stop() method. It is always called from a thread - * context, normally "rmmod", "apmd", or something similar. - * - */ -void usb_hcd_lh7a404_remove (struct usb_hcd *hcd, struct platform_device *dev) -{ - usb_remove_hcd(hcd); - lh7a404_stop_hc(dev); - iounmap(hcd->regs); - release_mem_region(hcd->rsrc_start, hcd->rsrc_len); - usb_put_hcd(hcd); -} - -/*-------------------------------------------------------------------------*/ - -static int __devinit -ohci_lh7a404_start (struct usb_hcd *hcd) -{ - struct ohci_hcd *ohci = hcd_to_ohci (hcd); - int ret; - - ohci_dbg (ohci, "ohci_lh7a404_start, ohci:%p", ohci); - if ((ret = ohci_init(ohci)) < 0) - return ret; - - if ((ret = ohci_run (ohci)) < 0) { - err ("can't start %s", hcd->self.bus_name); - ohci_stop (hcd); - return ret; - } - return 0; -} - -/*-------------------------------------------------------------------------*/ - -static const struct hc_driver ohci_lh7a404_hc_driver = { - .description = hcd_name, - .product_desc = "LH7A404 OHCI", - .hcd_priv_size = sizeof(struct ohci_hcd), - - /* - * generic hardware linkage - */ - .irq = ohci_irq, - .flags = HCD_USB11 | HCD_MEMORY, - - /* - * basic lifecycle operations - */ - .start = ohci_lh7a404_start, - .stop = ohci_stop, - .shutdown = ohci_shutdown, - - /* - * managing i/o requests and associated device resources - */ - .urb_enqueue = ohci_urb_enqueue, - .urb_dequeue = ohci_urb_dequeue, - .endpoint_disable = ohci_endpoint_disable, - - /* - * scheduling support - */ - .get_frame_number = ohci_get_frame, - - /* - * root hub support - */ - .hub_status_data = ohci_hub_status_data, - .hub_control = ohci_hub_control, -#ifdef CONFIG_PM - .bus_suspend = ohci_bus_suspend, - .bus_resume = ohci_bus_resume, -#endif - .start_port_reset = ohci_start_port_reset, -}; - -/*-------------------------------------------------------------------------*/ - -static int ohci_hcd_lh7a404_drv_probe(struct platform_device *pdev) -{ - int ret; - - pr_debug ("In ohci_hcd_lh7a404_drv_probe"); - - if (usb_disabled()) - return -ENODEV; - - ret = usb_hcd_lh7a404_probe(&ohci_lh7a404_hc_driver, pdev); - return ret; -} - -static int ohci_hcd_lh7a404_drv_remove(struct platform_device *pdev) -{ - struct usb_hcd *hcd = platform_get_drvdata(pdev); - - usb_hcd_lh7a404_remove(hcd, pdev); - return 0; -} - /*TBD*/ -/*static int ohci_hcd_lh7a404_drv_suspend(struct platform_device *dev) -{ - struct usb_hcd *hcd = platform_get_drvdata(dev); - - return 0; -} -static int ohci_hcd_lh7a404_drv_resume(struct platform_device *dev) -{ - struct usb_hcd *hcd = platform_get_drvdata(dev); - - - return 0; -} -*/ - -static struct platform_driver ohci_hcd_lh7a404_driver = { - .probe = ohci_hcd_lh7a404_drv_probe, - .remove = ohci_hcd_lh7a404_drv_remove, - .shutdown = usb_hcd_platform_shutdown, - /*.suspend = ohci_hcd_lh7a404_drv_suspend, */ - /*.resume = ohci_hcd_lh7a404_drv_resume, */ - .driver = { - .name = "lh7a404-ohci", - .owner = THIS_MODULE, - }, -}; - -MODULE_ALIAS("platform:lh7a404-ohci"); diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index 51facb985c84..04812b42fe6f 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -575,18 +575,8 @@ static inline void _ohci_writel (const struct ohci_hcd *ohci, #endif } -#ifdef CONFIG_ARCH_LH7A404 -/* Marc Singer: at the time this code was written, the LH7A404 - * had a problem reading the USB host registers. This - * implementation of the ohci_readl function performs the read - * twice as a work-around. - */ -#define ohci_readl(o,r) (_ohci_readl(o,r),_ohci_readl(o,r)) -#define ohci_writel(o,v,r) _ohci_writel(o,v,r) -#else #define ohci_readl(o,r) _ohci_readl(o,r) #define ohci_writel(o,v,r) _ohci_writel(o,v,r) -#endif /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/xhci-dbg.c b/drivers/usb/host/xhci-dbg.c index fcbf4abbf381..0231814a97a5 100644 --- a/drivers/usb/host/xhci-dbg.c +++ b/drivers/usb/host/xhci-dbg.c @@ -169,9 +169,10 @@ static void xhci_print_ports(struct xhci_hcd *xhci) } } -void xhci_print_ir_set(struct xhci_hcd *xhci, struct xhci_intr_reg *ir_set, int set_num) +void xhci_print_ir_set(struct xhci_hcd *xhci, int set_num) { - void *addr; + struct xhci_intr_reg __iomem *ir_set = &xhci->run_regs->ir_set[set_num]; + void __iomem *addr; u32 temp; u64 temp_64; @@ -449,7 +450,7 @@ char *xhci_get_slot_state(struct xhci_hcd *xhci, } } -void xhci_dbg_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx) +static void xhci_dbg_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx) { /* Fields are 32 bits wide, DMA addresses are in bytes */ int field_size = 32 / 8; @@ -488,7 +489,7 @@ void xhci_dbg_slot_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx) dbg_rsvd64(xhci, (u64 *)slot_ctx, dma); } -void xhci_dbg_ep_ctx(struct xhci_hcd *xhci, +static void xhci_dbg_ep_ctx(struct xhci_hcd *xhci, struct xhci_container_ctx *ctx, unsigned int last_ep) { diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c index 1d0f45f0e7a6..a9534396e85b 100644 --- a/drivers/usb/host/xhci-mem.c +++ b/drivers/usb/host/xhci-mem.c @@ -307,7 +307,7 @@ struct xhci_ep_ctx *xhci_get_ep_ctx(struct xhci_hcd *xhci, /***************** Streams structures manipulation *************************/ -void xhci_free_stream_ctx(struct xhci_hcd *xhci, +static void xhci_free_stream_ctx(struct xhci_hcd *xhci, unsigned int num_stream_ctxs, struct xhci_stream_ctx *stream_ctx, dma_addr_t dma) { @@ -335,7 +335,7 @@ void xhci_free_stream_ctx(struct xhci_hcd *xhci, * The stream context array must be a power of 2, and can be as small as * 64 bytes or as large as 1MB. */ -struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci, +static struct xhci_stream_ctx *xhci_alloc_stream_ctx(struct xhci_hcd *xhci, unsigned int num_stream_ctxs, dma_addr_t *dma, gfp_t mem_flags) { @@ -1900,11 +1900,11 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) val &= DBOFF_MASK; xhci_dbg(xhci, "// Doorbell array is located at offset 0x%x" " from cap regs base addr\n", val); - xhci->dba = (void *) xhci->cap_regs + val; + xhci->dba = (void __iomem *) xhci->cap_regs + val; xhci_dbg_regs(xhci); xhci_print_run_regs(xhci); /* Set ir_set to interrupt register set 0 */ - xhci->ir_set = (void *) xhci->run_regs->ir_set; + xhci->ir_set = &xhci->run_regs->ir_set[0]; /* * Event ring setup: Allocate a normal ring, but also setup @@ -1961,7 +1961,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags) /* Set the event ring dequeue address */ xhci_set_hc_event_deq(xhci); xhci_dbg(xhci, "Wrote ERST address to ir_set 0.\n"); - xhci_print_ir_set(xhci, xhci->ir_set, 0); + xhci_print_ir_set(xhci, 0); /* * XXX: Might need to set the Interrupter Moderation Register to diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c index 3e8211c1ce5a..3289bf4832c9 100644 --- a/drivers/usb/host/xhci-ring.c +++ b/drivers/usb/host/xhci-ring.c @@ -474,8 +474,11 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, state->new_deq_seg = find_trb_seg(cur_td->start_seg, dev->eps[ep_index].stopped_trb, &state->new_cycle_state); - if (!state->new_deq_seg) - BUG(); + if (!state->new_deq_seg) { + WARN_ON(1); + return; + } + /* Dig out the cycle state saved by the xHC during the stop ep cmd */ xhci_dbg(xhci, "Finding endpoint context\n"); ep_ctx = xhci_get_ep_ctx(xhci, dev->out_ctx, ep_index); @@ -486,8 +489,10 @@ void xhci_find_new_dequeue_state(struct xhci_hcd *xhci, state->new_deq_seg = find_trb_seg(state->new_deq_seg, state->new_deq_ptr, &state->new_cycle_state); - if (!state->new_deq_seg) - BUG(); + if (!state->new_deq_seg) { + WARN_ON(1); + return; + } trb = &state->new_deq_ptr->generic; if ((trb->field[3] & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK) && @@ -2363,12 +2368,13 @@ static unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) /* Scatter gather list entries may cross 64KB boundaries */ running_total = TRB_MAX_BUFF_SIZE - - (sg_dma_address(sg) & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + (sg_dma_address(sg) & (TRB_MAX_BUFF_SIZE - 1)); + running_total &= TRB_MAX_BUFF_SIZE - 1; if (running_total != 0) num_trbs++; /* How many more 64KB chunks to transfer, how many more TRBs? */ - while (running_total < sg_dma_len(sg)) { + while (running_total < sg_dma_len(sg) && running_total < temp) { num_trbs++; running_total += TRB_MAX_BUFF_SIZE; } @@ -2394,11 +2400,11 @@ static unsigned int count_sg_trbs_needed(struct xhci_hcd *xhci, struct urb *urb) static void check_trb_math(struct urb *urb, int num_trbs, int running_total) { if (num_trbs != 0) - dev_dbg(&urb->dev->dev, "%s - ep %#x - Miscalculated number of " + dev_err(&urb->dev->dev, "%s - ep %#x - Miscalculated number of " "TRBs, %d left\n", __func__, urb->ep->desc.bEndpointAddress, num_trbs); if (running_total != urb->transfer_buffer_length) - dev_dbg(&urb->dev->dev, "%s - ep %#x - Miscalculated tx length, " + dev_err(&urb->dev->dev, "%s - ep %#x - Miscalculated tx length, " "queued %#x (%d), asked for %#x (%d)\n", __func__, urb->ep->desc.bEndpointAddress, @@ -2533,8 +2539,7 @@ static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, sg = urb->sg; addr = (u64) sg_dma_address(sg); this_sg_len = sg_dma_len(sg); - trb_buff_len = TRB_MAX_BUFF_SIZE - - (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + trb_buff_len = TRB_MAX_BUFF_SIZE - (addr & (TRB_MAX_BUFF_SIZE - 1)); trb_buff_len = min_t(int, trb_buff_len, this_sg_len); if (trb_buff_len > urb->transfer_buffer_length) trb_buff_len = urb->transfer_buffer_length; @@ -2572,7 +2577,7 @@ static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, (unsigned int) (addr + TRB_MAX_BUFF_SIZE) & ~(TRB_MAX_BUFF_SIZE - 1), (unsigned int) addr + trb_buff_len); if (TRB_MAX_BUFF_SIZE - - (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)) < trb_buff_len) { + (addr & (TRB_MAX_BUFF_SIZE - 1)) < trb_buff_len) { xhci_warn(xhci, "WARN: sg dma xfer crosses 64KB boundaries!\n"); xhci_dbg(xhci, "Next boundary at %#x, end dma = %#x\n", (unsigned int) (addr + TRB_MAX_BUFF_SIZE) & ~(TRB_MAX_BUFF_SIZE - 1), @@ -2616,7 +2621,7 @@ static int queue_bulk_sg_tx(struct xhci_hcd *xhci, gfp_t mem_flags, } trb_buff_len = TRB_MAX_BUFF_SIZE - - (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + (addr & (TRB_MAX_BUFF_SIZE - 1)); trb_buff_len = min_t(int, trb_buff_len, this_sg_len); if (running_total + trb_buff_len > urb->transfer_buffer_length) trb_buff_len = @@ -2656,7 +2661,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, num_trbs = 0; /* How much data is (potentially) left before the 64KB boundary? */ running_total = TRB_MAX_BUFF_SIZE - - (urb->transfer_dma & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + (urb->transfer_dma & (TRB_MAX_BUFF_SIZE - 1)); + running_total &= TRB_MAX_BUFF_SIZE - 1; /* If there's some data on this 64KB chunk, or we have to send a * zero-length transfer, we need at least one TRB @@ -2700,8 +2706,8 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags, /* How much data is in the first TRB? */ addr = (u64) urb->transfer_dma; trb_buff_len = TRB_MAX_BUFF_SIZE - - (urb->transfer_dma & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); - if (urb->transfer_buffer_length < trb_buff_len) + (urb->transfer_dma & (TRB_MAX_BUFF_SIZE - 1)); + if (trb_buff_len > urb->transfer_buffer_length) trb_buff_len = urb->transfer_buffer_length; first_trb = true; @@ -2879,8 +2885,8 @@ static int count_isoc_trbs_needed(struct xhci_hcd *xhci, addr = (u64) (urb->transfer_dma + urb->iso_frame_desc[i].offset); td_len = urb->iso_frame_desc[i].length; - running_total = TRB_MAX_BUFF_SIZE - - (addr & ((1 << TRB_MAX_BUFF_SHIFT) - 1)); + running_total = TRB_MAX_BUFF_SIZE - (addr & (TRB_MAX_BUFF_SIZE - 1)); + running_total &= TRB_MAX_BUFF_SIZE - 1; if (running_total != 0) num_trbs++; diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 34cf4e165877..2083fc2179b2 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -109,7 +109,7 @@ int xhci_halt(struct xhci_hcd *xhci) /* * Set the run bit and wait for the host to be running. */ -int xhci_start(struct xhci_hcd *xhci) +static int xhci_start(struct xhci_hcd *xhci) { u32 temp; int ret; @@ -329,7 +329,7 @@ int xhci_init(struct usb_hcd *hcd) #ifdef CONFIG_USB_XHCI_HCD_DEBUGGING -void xhci_event_ring_work(unsigned long arg) +static void xhci_event_ring_work(unsigned long arg) { unsigned long flags; int temp; @@ -473,7 +473,7 @@ int xhci_run(struct usb_hcd *hcd) xhci->ir_set, (unsigned int) ER_IRQ_ENABLE(temp)); xhci_writel(xhci, ER_IRQ_ENABLE(temp), &xhci->ir_set->irq_pending); - xhci_print_ir_set(xhci, xhci->ir_set, 0); + xhci_print_ir_set(xhci, 0); if (NUM_TEST_NOOPS > 0) doorbell = xhci_setup_one_noop(xhci); @@ -528,7 +528,7 @@ void xhci_stop(struct usb_hcd *hcd) temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); xhci_writel(xhci, ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending); - xhci_print_ir_set(xhci, xhci->ir_set, 0); + xhci_print_ir_set(xhci, 0); xhci_dbg(xhci, "cleaning up memory\n"); xhci_mem_cleanup(xhci); @@ -755,7 +755,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool hibernated) temp = xhci_readl(xhci, &xhci->ir_set->irq_pending); xhci_writel(xhci, ER_IRQ_DISABLE(temp), &xhci->ir_set->irq_pending); - xhci_print_ir_set(xhci, xhci->ir_set, 0); + xhci_print_ir_set(xhci, 0); xhci_dbg(xhci, "cleaning up memory\n"); xhci_mem_cleanup(xhci); @@ -857,7 +857,7 @@ unsigned int xhci_last_valid_endpoint(u32 added_ctxs) /* Returns 1 if the arguments are OK; * returns 0 this is a root hub; returns -EINVAL for NULL pointers. */ -int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev, +static int xhci_check_args(struct usb_hcd *hcd, struct usb_device *udev, struct usb_host_endpoint *ep, int check_ep, bool check_virt_dev, const char *func) { struct xhci_hcd *xhci; @@ -1693,7 +1693,7 @@ static void xhci_setup_input_ctx_for_config_ep(struct xhci_hcd *xhci, xhci_dbg_ctx(xhci, in_ctx, xhci_last_valid_endpoint(add_flags)); } -void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, +static void xhci_setup_input_ctx_for_quirk(struct xhci_hcd *xhci, unsigned int slot_id, unsigned int ep_index, struct xhci_dequeue_state *deq_state) { diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 7f236fd22015..7f127df6dd55 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -1348,7 +1348,7 @@ static inline int xhci_link_trb_quirk(struct xhci_hcd *xhci) } /* xHCI debugging */ -void xhci_print_ir_set(struct xhci_hcd *xhci, struct xhci_intr_reg *ir_set, int set_num); +void xhci_print_ir_set(struct xhci_hcd *xhci, int set_num); void xhci_print_registers(struct xhci_hcd *xhci); void xhci_dbg_regs(struct xhci_hcd *xhci); void xhci_print_run_regs(struct xhci_hcd *xhci); diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 54a8bd1047d6..e550f35eff0f 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -1530,7 +1530,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) /*-------------------------------------------------------------------------*/ -#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3430) || \ +#if defined(CONFIG_SOC_OMAP2430) || defined(CONFIG_SOC_OMAP3430) || \ defined(CONFIG_ARCH_OMAP4) || defined(CONFIG_ARCH_U8500) || \ defined(CONFIG_ARCH_U5500) @@ -1864,6 +1864,7 @@ allocate_instance(struct device *dev, INIT_LIST_HEAD(&musb->out_bulk); hcd->uses_new_polling = 1; + hcd->has_tt = 1; musb->vbuserr_retry = VBUSERR_RETRY_COUNT; musb->a_wait_bcon = OTG_TIME_A_WAIT_BCON; diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index d74a8113ae74..3fb617ef9e72 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -212,8 +212,8 @@ enum musb_g_ep0_state { * directly with the "flat" model, or after setting up an index register. */ -#if defined(CONFIG_ARCH_DAVINCI) || defined(CONFIG_ARCH_OMAP2430) \ - || defined(CONFIG_ARCH_OMAP3430) || defined(CONFIG_BLACKFIN) \ +#if defined(CONFIG_ARCH_DAVINCI) || defined(CONFIG_SOC_OMAP2430) \ + || defined(CONFIG_SOC_OMAP3430) || defined(CONFIG_BLACKFIN) \ || defined(CONFIG_ARCH_OMAP4) /* REVISIT indexed access seemed to * misbehave (on DaVinci) for at least peripheral IN ... @@ -358,7 +358,7 @@ struct musb_csr_regs { struct musb_context_registers { -#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3) || \ +#if defined(CONFIG_SOC_OMAP2430) || defined(CONFIG_ARCH_OMAP3) || \ defined(CONFIG_ARCH_OMAP4) u32 otg_sysconfig, otg_forcestandby; #endif @@ -488,6 +488,15 @@ struct musb { unsigned set_address:1; unsigned test_mode:1; unsigned softconnect:1; + + u8 address; + u8 test_mode_nr; + u16 ackpend; /* ep0 */ + enum musb_g_ep0_state ep0_state; + struct usb_gadget g; /* the gadget */ + struct usb_gadget_driver *gadget_driver; /* its driver */ +#endif + /* * FIXME: Remove this flag. * @@ -501,14 +510,6 @@ struct musb { */ unsigned double_buffer_not_ok:1 __deprecated; - u8 address; - u8 test_mode_nr; - u16 ackpend; /* ep0 */ - enum musb_g_ep0_state ep0_state; - struct usb_gadget g; /* the gadget */ - struct usb_gadget_driver *gadget_driver; /* its driver */ -#endif - struct musb_hdrc_config *config; #ifdef MUSB_CONFIG_PROC_FS diff --git a/drivers/usb/musb/musbhsdma.h b/drivers/usb/musb/musbhsdma.h index 21056c924c74..320fd4afb93f 100644 --- a/drivers/usb/musb/musbhsdma.h +++ b/drivers/usb/musb/musbhsdma.h @@ -31,7 +31,7 @@ * */ -#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3430) +#if defined(CONFIG_SOC_OMAP2430) || defined(CONFIG_SOC_OMAP3430) #include "omap2430.h" #endif diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index a3f12333fc41..bc8badd16897 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -362,6 +362,7 @@ static int omap2430_musb_init(struct musb *musb) static int omap2430_musb_exit(struct musb *musb) { + del_timer_sync(&musb_idle_timer); omap2430_low_level_exit(musb); otg_put_transceiver(musb->xceiv); diff --git a/drivers/usb/otg/isp1301_omap.c b/drivers/usb/otg/isp1301_omap.c index e00fa1b22ecd..8c6fdef61d1c 100644 --- a/drivers/usb/otg/isp1301_omap.c +++ b/drivers/usb/otg/isp1301_omap.c @@ -1510,7 +1510,7 @@ isp1301_start_hnp(struct otg_transceiver *dev) /*-------------------------------------------------------------------------*/ -static int __init +static int __devinit isp1301_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { int status; diff --git a/drivers/usb/serial/sierra.c b/drivers/usb/serial/sierra.c index 7481ff8a49e4..0457813eebee 100644 --- a/drivers/usb/serial/sierra.c +++ b/drivers/usb/serial/sierra.c @@ -301,6 +301,9 @@ static const struct usb_device_id id_table[] = { { USB_DEVICE(0x1199, 0x68A3), /* Sierra Wireless Direct IP modems */ .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist }, + { USB_DEVICE(0x0f3d, 0x68A3), /* Airprime/Sierra Wireless Direct IP modems */ + .driver_info = (kernel_ulong_t)&direct_ip_interface_blacklist + }, { USB_DEVICE(0x413C, 0x08133) }, /* Dell Computer Corp. Wireless 5720 VZW Mobile Broadband (EVDO Rev-A) Minicard GPS Port */ { } diff --git a/drivers/usb/serial/usb_wwan.c b/drivers/usb/serial/usb_wwan.c index b004b2a485c3..9c014e2ecd68 100644 --- a/drivers/usb/serial/usb_wwan.c +++ b/drivers/usb/serial/usb_wwan.c @@ -295,12 +295,15 @@ static void usb_wwan_indat_callback(struct urb *urb) __func__, status, endpoint); } else { tty = tty_port_tty_get(&port->port); - if (urb->actual_length) { - tty_insert_flip_string(tty, data, urb->actual_length); - tty_flip_buffer_push(tty); - } else - dbg("%s: empty read urb received", __func__); - tty_kref_put(tty); + if (tty) { + if (urb->actual_length) { + tty_insert_flip_string(tty, data, + urb->actual_length); + tty_flip_buffer_push(tty); + } else + dbg("%s: empty read urb received", __func__); + tty_kref_put(tty); + } /* Resubmit urb so we continue receiving */ if (status != -ESHUTDOWN) { diff --git a/drivers/usb/serial/visor.c b/drivers/usb/serial/visor.c index 15a5d89b7f39..1c11959a7d58 100644 --- a/drivers/usb/serial/visor.c +++ b/drivers/usb/serial/visor.c @@ -27,6 +27,7 @@ #include <linux/uaccess.h> #include <linux/usb.h> #include <linux/usb/serial.h> +#include <linux/usb/cdc.h> #include "visor.h" /* @@ -479,6 +480,17 @@ static int visor_probe(struct usb_serial *serial, dbg("%s", __func__); + /* + * some Samsung Android phones in modem mode have the same ID + * as SPH-I500, but they are ACM devices, so dont bind to them + */ + if (id->idVendor == SAMSUNG_VENDOR_ID && + id->idProduct == SAMSUNG_SPH_I500_ID && + serial->dev->descriptor.bDeviceClass == USB_CLASS_COMM && + serial->dev->descriptor.bDeviceSubClass == + USB_CDC_SUBCLASS_ACM) + return -ENODEV; + if (serial->dev->actconfig->desc.bConfigurationValue != 1) { dev_err(&serial->dev->dev, "active config #%d != 1 ??\n", serial->dev->actconfig->desc.bConfigurationValue); diff --git a/drivers/video/Kconfig b/drivers/video/Kconfig index 6bafb51bb437..d64a4ca0b23e 100644 --- a/drivers/video/Kconfig +++ b/drivers/video/Kconfig @@ -322,69 +322,6 @@ config FB_ARMCLCD here and read <file:Documentation/kbuild/modules.txt>. The module will be called amba-clcd. -choice - - depends on FB_ARMCLCD && (ARCH_LH7A40X || ARCH_LH7952X) - prompt "LCD Panel" - default FB_ARMCLCD_SHARP_LQ035Q7DB02 - -config FB_ARMCLCD_SHARP_LQ035Q7DB02_HRTFT - bool "LogicPD LCD 3.5\" QVGA w/HRTFT IC" - help - This is an implementation of the Sharp LQ035Q7DB02, a 3.5" - color QVGA, HRTFT panel. The LogicPD device includes - an integrated HRTFT controller IC. - The native resolution is 240x320. - -config FB_ARMCLCD_SHARP_LQ057Q3DC02 - bool "LogicPD LCD 5.7\" QVGA" - help - This is an implementation of the Sharp LQ057Q3DC02, a 5.7" - color QVGA, TFT panel. The LogicPD device includes an - The native resolution is 320x240. - -config FB_ARMCLCD_SHARP_LQ64D343 - bool "LogicPD LCD 6.4\" VGA" - help - This is an implementation of the Sharp LQ64D343, a 6.4" - color VGA, TFT panel. The LogicPD device includes an - The native resolution is 640x480. - -config FB_ARMCLCD_SHARP_LQ10D368 - bool "LogicPD LCD 10.4\" VGA" - help - This is an implementation of the Sharp LQ10D368, a 10.4" - color VGA, TFT panel. The LogicPD device includes an - The native resolution is 640x480. - - -config FB_ARMCLCD_SHARP_LQ121S1DG41 - bool "LogicPD LCD 12.1\" SVGA" - help - This is an implementation of the Sharp LQ121S1DG41, a 12.1" - color SVGA, TFT panel. The LogicPD device includes an - The native resolution is 800x600. - - This panel requires a clock rate may be an integer fraction - of the base LCDCLK frequency. The driver will select the - highest frequency available that is lower than the maximum - allowed. The panel may flicker if the clock rate is - slower than the recommended minimum. - -config FB_ARMCLCD_AUO_A070VW01_WIDE - bool "AU Optronics A070VW01 LCD 7.0\" WIDE" - help - This is an implementation of the AU Optronics, a 7.0" - WIDE Color. The native resolution is 234x480. - -config FB_ARMCLCD_HITACHI - bool "Hitachi Wide Screen 800x480" - help - This is an implementation of the Hitachi 800x480. - -endchoice - - config FB_ACORN bool "Acorn VIDC support" depends on (FB = y) && ARM && ARCH_ACORN @@ -439,6 +376,24 @@ config FB_CYBER2000 Say Y if you have a NetWinder or a graphics card containing this device, otherwise say N. +config FB_CYBER2000_DDC + bool "DDC for CyberPro support" + depends on FB_CYBER2000 + select FB_DDC + default y + help + Say Y here if you want DDC support for your CyberPro graphics + card. This is only I2C bus support, driver does not use EDID. + +config FB_CYBER2000_I2C + bool "CyberPro 2000/2010/5000 I2C support" + depends on FB_CYBER2000 && I2C && ARCH_NETWINDER + select I2C_ALGOBIT + help + Enable support for the I2C video decoder interface on the + Integraphics CyberPro 20x0 and 5000 VGA chips. This is used + on the Netwinder machines for the SAA7111 video capture. + config FB_APOLLO bool depends on (FB = y) && APOLLO @@ -2365,6 +2320,17 @@ config FB_JZ4740 help Framebuffer support for the JZ4740 SoC. +config FB_PUV3_UNIGFX + tristate "PKUnity v3 Unigfx framebuffer support" + depends on FB && UNICORE32 && ARCH_PUV3 + select FB_SYS_FILLRECT + select FB_SYS_COPYAREA + select FB_SYS_IMAGEBLIT + select FB_SYS_FOPS + help + Choose this option if you want to use the Unigfx device as a + framebuffer device. Without the support of PCI & AGP. + source "drivers/video/omap/Kconfig" source "drivers/video/omap2/Kconfig" diff --git a/drivers/video/Makefile b/drivers/video/Makefile index 8c8fabdff9d0..b0eb3da24670 100644 --- a/drivers/video/Makefile +++ b/drivers/video/Makefile @@ -139,6 +139,7 @@ obj-$(CONFIG_FB_MB862XX) += mb862xx/ obj-$(CONFIG_FB_MSM) += msm/ obj-$(CONFIG_FB_NUC900) += nuc900fb.o obj-$(CONFIG_FB_JZ4740) += jz4740_fb.o +obj-$(CONFIG_FB_PUV3_UNIGFX) += fb-puv3.o # Platform or fallback drivers go here obj-$(CONFIG_FB_UVESA) += uvesafb.o diff --git a/drivers/video/amba-clcd.c b/drivers/video/amba-clcd.c index 1c2c68356ea7..5fc983c5b92c 100644 --- a/drivers/video/amba-clcd.c +++ b/drivers/video/amba-clcd.c @@ -120,8 +120,23 @@ static void clcdfb_enable(struct clcd_fb *fb, u32 cntl) static int clcdfb_set_bitfields(struct clcd_fb *fb, struct fb_var_screeninfo *var) { + u32 caps; int ret = 0; + if (fb->panel->caps && fb->board->caps) + caps = fb->panel->caps & fb->board->caps; + else { + /* Old way of specifying what can be used */ + caps = fb->panel->cntl & CNTL_BGR ? + CLCD_CAP_BGR : CLCD_CAP_RGB; + /* But mask out 444 modes as they weren't supported */ + caps &= ~CLCD_CAP_444; + } + + /* Only TFT panels can do RGB888/BGR888 */ + if (!(fb->panel->cntl & CNTL_LCDTFT)) + caps &= ~CLCD_CAP_888; + memset(&var->transp, 0, sizeof(var->transp)); var->red.msb_right = 0; @@ -133,6 +148,13 @@ clcdfb_set_bitfields(struct clcd_fb *fb, struct fb_var_screeninfo *var) case 2: case 4: case 8: + /* If we can't do 5551, reject */ + caps &= CLCD_CAP_5551; + if (!caps) { + ret = -EINVAL; + break; + } + var->red.length = var->bits_per_pixel; var->red.offset = 0; var->green.length = var->bits_per_pixel; @@ -140,23 +162,61 @@ clcdfb_set_bitfields(struct clcd_fb *fb, struct fb_var_screeninfo *var) var->blue.length = var->bits_per_pixel; var->blue.offset = 0; break; + case 16: - var->red.length = 5; - var->blue.length = 5; + /* If we can't do 444, 5551 or 565, reject */ + if (!(caps & (CLCD_CAP_444 | CLCD_CAP_5551 | CLCD_CAP_565))) { + ret = -EINVAL; + break; + } + /* - * Green length can be 5 or 6 depending whether - * we're operating in RGB555 or RGB565 mode. + * Green length can be 4, 5 or 6 depending whether + * we're operating in 444, 5551 or 565 mode. */ - if (var->green.length != 5 && var->green.length != 6) - var->green.length = 6; + if (var->green.length == 4 && caps & CLCD_CAP_444) + caps &= CLCD_CAP_444; + if (var->green.length == 5 && caps & CLCD_CAP_5551) + caps &= CLCD_CAP_5551; + else if (var->green.length == 6 && caps & CLCD_CAP_565) + caps &= CLCD_CAP_565; + else { + /* + * PL110 officially only supports RGB555, + * but may be wired up to allow RGB565. + */ + if (caps & CLCD_CAP_565) { + var->green.length = 6; + caps &= CLCD_CAP_565; + } else if (caps & CLCD_CAP_5551) { + var->green.length = 5; + caps &= CLCD_CAP_5551; + } else { + var->green.length = 4; + caps &= CLCD_CAP_444; + } + } + + if (var->green.length >= 5) { + var->red.length = 5; + var->blue.length = 5; + } else { + var->red.length = 4; + var->blue.length = 4; + } break; case 32: - if (fb->panel->cntl & CNTL_LCDTFT) { - var->red.length = 8; - var->green.length = 8; - var->blue.length = 8; + /* If we can't do 888, reject */ + caps &= CLCD_CAP_888; + if (!caps) { + ret = -EINVAL; break; } + + var->red.length = 8; + var->green.length = 8; + var->blue.length = 8; + break; default: ret = -EINVAL; break; @@ -168,7 +228,20 @@ clcdfb_set_bitfields(struct clcd_fb *fb, struct fb_var_screeninfo *var) * the bitfield length defined above. */ if (ret == 0 && var->bits_per_pixel >= 16) { - if (fb->panel->cntl & CNTL_BGR) { + bool bgr, rgb; + + bgr = caps & CLCD_CAP_BGR && var->blue.offset == 0; + rgb = caps & CLCD_CAP_RGB && var->red.offset == 0; + + if (!bgr && !rgb) + /* + * The requested format was not possible, try just + * our capabilities. One of BGR or RGB must be + * supported. + */ + bgr = caps & CLCD_CAP_BGR; + + if (bgr) { var->blue.offset = 0; var->green.offset = var->blue.offset + var->blue.length; var->red.offset = var->green.offset + var->green.length; @@ -443,8 +516,8 @@ static int clcdfb_register(struct clcd_fb *fb) fb_set_var(&fb->fb, &fb->fb.var); - printk(KERN_INFO "CLCD: %s hardware, %s display\n", - fb->board->name, fb->panel->mode.name); + dev_info(&fb->dev->dev, "%s hardware, %s display\n", + fb->board->name, fb->panel->mode.name); ret = register_framebuffer(&fb->fb); if (ret == 0) @@ -461,7 +534,7 @@ static int clcdfb_register(struct clcd_fb *fb) return ret; } -static int clcdfb_probe(struct amba_device *dev, struct amba_id *id) +static int clcdfb_probe(struct amba_device *dev, const struct amba_id *id) { struct clcd_board *board = dev->dev.platform_data; struct clcd_fb *fb; @@ -486,6 +559,10 @@ static int clcdfb_probe(struct amba_device *dev, struct amba_id *id) fb->dev = dev; fb->board = board; + dev_info(&fb->dev->dev, "PL%03x rev%u at 0x%08llx\n", + amba_part(dev), amba_rev(dev), + (unsigned long long)dev->res.start); + ret = fb->board->setup(fb); if (ret) goto free_fb; diff --git a/drivers/video/cyber2000fb.c b/drivers/video/cyber2000fb.c index 0c1afd13ddd3..850380795b05 100644 --- a/drivers/video/cyber2000fb.c +++ b/drivers/video/cyber2000fb.c @@ -47,6 +47,8 @@ #include <linux/pci.h> #include <linux/init.h> #include <linux/io.h> +#include <linux/i2c.h> +#include <linux/i2c-algo-bit.h> #include <asm/pgtable.h> #include <asm/system.h> @@ -61,10 +63,10 @@ struct cfb_info { struct fb_info fb; struct display_switch *dispsw; struct display *display; - struct pci_dev *dev; unsigned char __iomem *region; unsigned char __iomem *regs; u_int id; + u_int irq; int func_use_count; u_long ref_ps; @@ -88,6 +90,19 @@ struct cfb_info { u_char ramdac_powerdown; u32 pseudo_palette[16]; + + spinlock_t reg_b0_lock; + +#ifdef CONFIG_FB_CYBER2000_DDC + bool ddc_registered; + struct i2c_adapter ddc_adapter; + struct i2c_algo_bit_data ddc_algo; +#endif + +#ifdef CONFIG_FB_CYBER2000_I2C + struct i2c_adapter i2c_adapter; + struct i2c_algo_bit_data i2c_algo; +#endif }; static char *default_font = "Acorn8x8"; @@ -494,6 +509,7 @@ static void cyber2000fb_set_timing(struct cfb_info *cfb, struct par_info *hw) cyber2000_attrw(0x14, 0x00, cfb); /* PLL registers */ + spin_lock(&cfb->reg_b0_lock); cyber2000_grphw(EXT_DCLK_MULT, hw->clock_mult, cfb); cyber2000_grphw(EXT_DCLK_DIV, hw->clock_div, cfb); cyber2000_grphw(EXT_MCLK_MULT, cfb->mclk_mult, cfb); @@ -501,6 +517,7 @@ static void cyber2000fb_set_timing(struct cfb_info *cfb, struct par_info *hw) cyber2000_grphw(0x90, 0x01, cfb); cyber2000_grphw(0xb9, 0x80, cfb); cyber2000_grphw(0xb9, 0x00, cfb); + spin_unlock(&cfb->reg_b0_lock); cfb->ramdac_ctrl = hw->ramdac; cyber2000fb_write_ramdac_ctrl(cfb); @@ -681,9 +698,9 @@ cyber2000fb_decode_clock(struct par_info *hw, struct cfb_info *cfb, * pll_ps_calc = best_div1 / (ref_ps * best_mult) */ best_diff = 0x7fffffff; - best_mult = 32; - best_div1 = 255; - for (t_div1 = 32; t_div1 > 1; t_div1 -= 1) { + best_mult = 2; + best_div1 = 32; + for (t_div1 = 2; t_div1 < 32; t_div1 += 1) { u_int rr, t_mult, t_pll_ps; int diff; @@ -1105,24 +1122,22 @@ void cyber2000fb_disable_extregs(struct cfb_info *cfb) } EXPORT_SYMBOL(cyber2000fb_disable_extregs); -void cyber2000fb_get_fb_var(struct cfb_info *cfb, struct fb_var_screeninfo *var) -{ - memcpy(var, &cfb->fb.var, sizeof(struct fb_var_screeninfo)); -} -EXPORT_SYMBOL(cyber2000fb_get_fb_var); - /* * Attach a capture/tv driver to the core CyberX0X0 driver. */ int cyber2000fb_attach(struct cyberpro_info *info, int idx) { if (int_cfb_info != NULL) { - info->dev = int_cfb_info->dev; + info->dev = int_cfb_info->fb.device; +#ifdef CONFIG_FB_CYBER2000_I2C + info->i2c = &int_cfb_info->i2c_adapter; +#else + info->i2c = NULL; +#endif info->regs = int_cfb_info->regs; + info->irq = int_cfb_info->irq; info->fb = int_cfb_info->fb.screen_base; info->fb_size = int_cfb_info->fb.fix.smem_len; - info->enable_extregs = cyber2000fb_enable_extregs; - info->disable_extregs = cyber2000fb_disable_extregs; info->info = int_cfb_info; strlcpy(info->dev_name, int_cfb_info->fb.fix.id, @@ -1141,6 +1156,183 @@ void cyber2000fb_detach(int idx) } EXPORT_SYMBOL(cyber2000fb_detach); +#ifdef CONFIG_FB_CYBER2000_DDC + +#define DDC_REG 0xb0 +#define DDC_SCL_OUT (1 << 0) +#define DDC_SDA_OUT (1 << 4) +#define DDC_SCL_IN (1 << 2) +#define DDC_SDA_IN (1 << 6) + +static void cyber2000fb_enable_ddc(struct cfb_info *cfb) +{ + spin_lock(&cfb->reg_b0_lock); + cyber2000fb_writew(0x1bf, 0x3ce, cfb); +} + +static void cyber2000fb_disable_ddc(struct cfb_info *cfb) +{ + cyber2000fb_writew(0x0bf, 0x3ce, cfb); + spin_unlock(&cfb->reg_b0_lock); +} + + +static void cyber2000fb_ddc_setscl(void *data, int val) +{ + struct cfb_info *cfb = data; + unsigned char reg; + + cyber2000fb_enable_ddc(cfb); + reg = cyber2000_grphr(DDC_REG, cfb); + if (!val) /* bit is inverted */ + reg |= DDC_SCL_OUT; + else + reg &= ~DDC_SCL_OUT; + cyber2000_grphw(DDC_REG, reg, cfb); + cyber2000fb_disable_ddc(cfb); +} + +static void cyber2000fb_ddc_setsda(void *data, int val) +{ + struct cfb_info *cfb = data; + unsigned char reg; + + cyber2000fb_enable_ddc(cfb); + reg = cyber2000_grphr(DDC_REG, cfb); + if (!val) /* bit is inverted */ + reg |= DDC_SDA_OUT; + else + reg &= ~DDC_SDA_OUT; + cyber2000_grphw(DDC_REG, reg, cfb); + cyber2000fb_disable_ddc(cfb); +} + +static int cyber2000fb_ddc_getscl(void *data) +{ + struct cfb_info *cfb = data; + int retval; + + cyber2000fb_enable_ddc(cfb); + retval = !!(cyber2000_grphr(DDC_REG, cfb) & DDC_SCL_IN); + cyber2000fb_disable_ddc(cfb); + + return retval; +} + +static int cyber2000fb_ddc_getsda(void *data) +{ + struct cfb_info *cfb = data; + int retval; + + cyber2000fb_enable_ddc(cfb); + retval = !!(cyber2000_grphr(DDC_REG, cfb) & DDC_SDA_IN); + cyber2000fb_disable_ddc(cfb); + + return retval; +} + +static int __devinit cyber2000fb_setup_ddc_bus(struct cfb_info *cfb) +{ + strlcpy(cfb->ddc_adapter.name, cfb->fb.fix.id, + sizeof(cfb->ddc_adapter.name)); + cfb->ddc_adapter.owner = THIS_MODULE; + cfb->ddc_adapter.class = I2C_CLASS_DDC; + cfb->ddc_adapter.algo_data = &cfb->ddc_algo; + cfb->ddc_adapter.dev.parent = cfb->fb.device; + cfb->ddc_algo.setsda = cyber2000fb_ddc_setsda; + cfb->ddc_algo.setscl = cyber2000fb_ddc_setscl; + cfb->ddc_algo.getsda = cyber2000fb_ddc_getsda; + cfb->ddc_algo.getscl = cyber2000fb_ddc_getscl; + cfb->ddc_algo.udelay = 10; + cfb->ddc_algo.timeout = 20; + cfb->ddc_algo.data = cfb; + + i2c_set_adapdata(&cfb->ddc_adapter, cfb); + + return i2c_bit_add_bus(&cfb->ddc_adapter); +} +#endif /* CONFIG_FB_CYBER2000_DDC */ + +#ifdef CONFIG_FB_CYBER2000_I2C +static void cyber2000fb_i2c_setsda(void *data, int state) +{ + struct cfb_info *cfb = data; + unsigned int latch2; + + spin_lock(&cfb->reg_b0_lock); + latch2 = cyber2000_grphr(EXT_LATCH2, cfb); + latch2 &= EXT_LATCH2_I2C_CLKEN; + if (state) + latch2 |= EXT_LATCH2_I2C_DATEN; + cyber2000_grphw(EXT_LATCH2, latch2, cfb); + spin_unlock(&cfb->reg_b0_lock); +} + +static void cyber2000fb_i2c_setscl(void *data, int state) +{ + struct cfb_info *cfb = data; + unsigned int latch2; + + spin_lock(&cfb->reg_b0_lock); + latch2 = cyber2000_grphr(EXT_LATCH2, cfb); + latch2 &= EXT_LATCH2_I2C_DATEN; + if (state) + latch2 |= EXT_LATCH2_I2C_CLKEN; + cyber2000_grphw(EXT_LATCH2, latch2, cfb); + spin_unlock(&cfb->reg_b0_lock); +} + +static int cyber2000fb_i2c_getsda(void *data) +{ + struct cfb_info *cfb = data; + int ret; + + spin_lock(&cfb->reg_b0_lock); + ret = !!(cyber2000_grphr(EXT_LATCH2, cfb) & EXT_LATCH2_I2C_DAT); + spin_unlock(&cfb->reg_b0_lock); + + return ret; +} + +static int cyber2000fb_i2c_getscl(void *data) +{ + struct cfb_info *cfb = data; + int ret; + + spin_lock(&cfb->reg_b0_lock); + ret = !!(cyber2000_grphr(EXT_LATCH2, cfb) & EXT_LATCH2_I2C_CLK); + spin_unlock(&cfb->reg_b0_lock); + + return ret; +} + +static int __devinit cyber2000fb_i2c_register(struct cfb_info *cfb) +{ + strlcpy(cfb->i2c_adapter.name, cfb->fb.fix.id, + sizeof(cfb->i2c_adapter.name)); + cfb->i2c_adapter.owner = THIS_MODULE; + cfb->i2c_adapter.algo_data = &cfb->i2c_algo; + cfb->i2c_adapter.dev.parent = cfb->fb.device; + cfb->i2c_algo.setsda = cyber2000fb_i2c_setsda; + cfb->i2c_algo.setscl = cyber2000fb_i2c_setscl; + cfb->i2c_algo.getsda = cyber2000fb_i2c_getsda; + cfb->i2c_algo.getscl = cyber2000fb_i2c_getscl; + cfb->i2c_algo.udelay = 5; + cfb->i2c_algo.timeout = msecs_to_jiffies(100); + cfb->i2c_algo.data = cfb; + + return i2c_bit_add_bus(&cfb->i2c_adapter); +} + +static void cyber2000fb_i2c_unregister(struct cfb_info *cfb) +{ + i2c_del_adapter(&cfb->i2c_adapter); +} +#else +#define cyber2000fb_i2c_register(cfb) (0) +#define cyber2000fb_i2c_unregister(cfb) do { } while (0) +#endif + /* * These parameters give * 640x480, hsync 31.5kHz, vsync 60Hz @@ -1275,6 +1467,8 @@ static struct cfb_info __devinit *cyberpro_alloc_fb_info(unsigned int id, cfb->fb.flags = FBINFO_DEFAULT | FBINFO_HWACCEL_YPAN; cfb->fb.pseudo_palette = cfb->pseudo_palette; + spin_lock_init(&cfb->reg_b0_lock); + fb_alloc_cmap(&cfb->fb.cmap, NR_PALETTE, 0); return cfb; @@ -1369,6 +1563,11 @@ static int __devinit cyberpro_common_probe(struct cfb_info *cfb) cfb->fb.fix.mmio_len = MMIO_SIZE; cfb->fb.screen_base = cfb->region; +#ifdef CONFIG_FB_CYBER2000_DDC + if (cyber2000fb_setup_ddc_bus(cfb) == 0) + cfb->ddc_registered = true; +#endif + err = -EINVAL; if (!fb_find_mode(&cfb->fb.var, &cfb->fb, NULL, NULL, 0, &cyber2000fb_default_mode, 8)) { @@ -1401,14 +1600,32 @@ static int __devinit cyberpro_common_probe(struct cfb_info *cfb) cfb->fb.var.xres, cfb->fb.var.yres, h_sync / 1000, h_sync % 1000, v_sync); - if (cfb->dev) - cfb->fb.device = &cfb->dev->dev; + err = cyber2000fb_i2c_register(cfb); + if (err) + goto failed; + err = register_framebuffer(&cfb->fb); + if (err) + cyber2000fb_i2c_unregister(cfb); failed: +#ifdef CONFIG_FB_CYBER2000_DDC + if (err && cfb->ddc_registered) + i2c_del_adapter(&cfb->ddc_adapter); +#endif return err; } +static void __devexit cyberpro_common_remove(struct cfb_info *cfb) +{ + unregister_framebuffer(&cfb->fb); +#ifdef CONFIG_FB_CYBER2000_DDC + if (cfb->ddc_registered) + i2c_del_adapter(&cfb->ddc_adapter); +#endif + cyber2000fb_i2c_unregister(cfb); +} + static void cyberpro_common_resume(struct cfb_info *cfb) { cyberpro_init_hw(cfb); @@ -1442,12 +1659,13 @@ static int __devinit cyberpro_vl_probe(void) if (!cfb) goto failed_release; - cfb->dev = NULL; + cfb->irq = -1; cfb->region = ioremap(FB_START, FB_SIZE); if (!cfb->region) goto failed_ioremap; cfb->regs = cfb->region + MMIO_OFFSET; + cfb->fb.device = NULL; cfb->fb.fix.mmio_start = FB_START + MMIO_OFFSET; cfb->fb.fix.smem_start = FB_START; @@ -1585,12 +1803,13 @@ cyberpro_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) if (err) goto failed_regions; - cfb->dev = dev; + cfb->irq = dev->irq; cfb->region = pci_ioremap_bar(dev, 0); if (!cfb->region) goto failed_ioremap; cfb->regs = cfb->region + MMIO_OFFSET; + cfb->fb.device = &dev->dev; cfb->fb.fix.mmio_start = pci_resource_start(dev, 0) + MMIO_OFFSET; cfb->fb.fix.smem_start = pci_resource_start(dev, 0); @@ -1648,15 +1867,7 @@ static void __devexit cyberpro_pci_remove(struct pci_dev *dev) struct cfb_info *cfb = pci_get_drvdata(dev); if (cfb) { - /* - * If unregister_framebuffer fails, then - * we will be leaving hooks that could cause - * oopsen laying around. - */ - if (unregister_framebuffer(&cfb->fb)) - printk(KERN_WARNING "%s: danger Will Robinson, " - "danger danger! Oopsen imminent!\n", - cfb->fb.fix.id); + cyberpro_common_remove(cfb); iounmap(cfb->region); cyberpro_free_fb_info(cfb); diff --git a/drivers/video/cyber2000fb.h b/drivers/video/cyber2000fb.h index de4fc43e51c1..bad69102e774 100644 --- a/drivers/video/cyber2000fb.h +++ b/drivers/video/cyber2000fb.h @@ -464,12 +464,14 @@ static void debug_printf(char *fmt, ...) struct cfb_info; struct cyberpro_info { - struct pci_dev *dev; + struct device *dev; + struct i2c_adapter *i2c; unsigned char __iomem *regs; char __iomem *fb; char dev_name[32]; unsigned int fb_size; unsigned int chip_id; + unsigned int irq; /* * The following is a pointer to be passed into the @@ -478,15 +480,6 @@ struct cyberpro_info { * is within this structure. */ struct cfb_info *info; - - /* - * Use these to enable the BM or TV registers. In an SMP - * environment, these two function pointers should only be - * called from the module_init() or module_exit() - * functions. - */ - void (*enable_extregs)(struct cfb_info *); - void (*disable_extregs)(struct cfb_info *); }; #define ID_IGA_1682 0 @@ -494,8 +487,6 @@ struct cyberpro_info { #define ID_CYBERPRO_2010 2 #define ID_CYBERPRO_5000 3 -struct fb_var_screeninfo; - /* * Note! Writing to the Cyber20x0 registers from an interrupt * routine is definitely a bad idea atm. @@ -504,4 +495,3 @@ int cyber2000fb_attach(struct cyberpro_info *info, int idx); void cyber2000fb_detach(int idx); void cyber2000fb_enable_extregs(struct cfb_info *cfb); void cyber2000fb_disable_extregs(struct cfb_info *cfb); -void cyber2000fb_get_fb_var(struct cfb_info *cfb, struct fb_var_screeninfo *var); diff --git a/drivers/video/fb-puv3.c b/drivers/video/fb-puv3.c new file mode 100644 index 000000000000..dbd2dc4745d1 --- /dev/null +++ b/drivers/video/fb-puv3.c @@ -0,0 +1,846 @@ +/* + * Frame Buffer Driver for PKUnity-v3 Unigfx + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/vmalloc.h> +#include <linux/platform_device.h> +#include <linux/clk.h> +#include <linux/fb.h> +#include <linux/init.h> +#include <linux/console.h> + +#include <asm/sizes.h> +#include <mach/hardware.h> + +/* Platform_data reserved for unifb registers. */ +#define UNIFB_REGS_NUM 10 +/* RAM reserved for the frame buffer. */ +#define UNIFB_MEMSIZE (SZ_4M) /* 4 MB for 1024*768*32b */ + +/* + * cause UNIGFX don not have EDID + * all the modes are organized as follow + */ +static const struct fb_videomode unifb_modes[] = { + /* 0 640x480-60 VESA */ + { "640x480@60", 60, 640, 480, 25175000, 48, 16, 34, 10, 96, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 1 640x480-75 VESA */ + { "640x480@75", 75, 640, 480, 31500000, 120, 16, 18, 1, 64, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 2 800x600-60 VESA */ + { "800x600@60", 60, 800, 600, 40000000, 88, 40, 26, 1, 128, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 3 800x600-75 VESA */ + { "800x600@75", 75, 800, 600, 49500000, 160, 16, 23, 1, 80, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 4 1024x768-60 VESA */ + { "1024x768@60", 60, 1024, 768, 65000000, 160, 24, 34, 3, 136, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 5 1024x768-75 VESA */ + { "1024x768@75", 75, 1024, 768, 78750000, 176, 16, 30, 1, 96, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 6 1280x960-60 VESA */ + { "1280x960@60", 60, 1280, 960, 108000000, 312, 96, 38, 1, 112, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 7 1440x900-60 VESA */ + { "1440x900@60", 60, 1440, 900, 106500000, 232, 80, 30, 3, 152, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 8 FIXME 9 1024x600-60 VESA UNTESTED */ + { "1024x600@60", 60, 1024, 600, 50650000, 160, 24, 26, 1, 136, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 9 FIXME 10 1024x600-75 VESA UNTESTED */ + { "1024x600@75", 75, 1024, 600, 61500000, 176, 16, 23, 1, 96, 1, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, + /* 10 FIXME 11 1366x768-60 VESA UNTESTED */ + { "1366x768@60", 60, 1366, 768, 85500000, 256, 58, 18, 1, 112, 3, + 0, FB_VMODE_NONINTERLACED, FB_MODE_IS_VESA }, +}; + +static struct fb_var_screeninfo unifb_default = { + .xres = 640, + .yres = 480, + .xres_virtual = 640, + .yres_virtual = 480, + .bits_per_pixel = 16, + .red = { 11, 5, 0 }, + .green = { 5, 6, 0 }, + .blue = { 0, 5, 0 }, + .activate = FB_ACTIVATE_NOW, + .height = -1, + .width = -1, + .pixclock = 25175000, + .left_margin = 48, + .right_margin = 16, + .upper_margin = 33, + .lower_margin = 10, + .hsync_len = 96, + .vsync_len = 2, + .vmode = FB_VMODE_NONINTERLACED, +}; + +static struct fb_fix_screeninfo unifb_fix = { + .id = "UNIGFX FB", + .type = FB_TYPE_PACKED_PIXELS, + .visual = FB_VISUAL_TRUECOLOR, + .xpanstep = 1, + .ypanstep = 1, + .ywrapstep = 1, + .accel = FB_ACCEL_NONE, +}; + +static void unifb_sync(struct fb_info *info) +{ + /* TODO: may, this can be replaced by interrupt */ + int cnt; + + for (cnt = 0; cnt < 0x10000000; cnt++) { + if (readl(UGE_COMMAND) & 0x1000000) + return; + } + + if (cnt > 0x8000000) + dev_warn(info->device, "Warning: UniGFX GE time out ...\n"); +} + +static void unifb_prim_fillrect(struct fb_info *info, + const struct fb_fillrect *region) +{ + int awidth = region->width; + int aheight = region->height; + int m_iBpp = info->var.bits_per_pixel; + int screen_width = info->var.xres; + int src_sel = 1; /* from fg_color */ + int pat_sel = 1; + int src_x0 = 0; + int dst_x0 = region->dx; + int src_y0 = 0; + int dst_y0 = region->dy; + int rop_alpha_sel = 0; + int rop_alpha_code = 0xCC; + int x_dir = 1; + int y_dir = 1; + int alpha_r = 0; + int alpha_sel = 0; + int dst_pitch = screen_width * (m_iBpp / 8); + int dst_offset = dst_y0 * dst_pitch + dst_x0 * (m_iBpp / 8); + int src_pitch = screen_width * (m_iBpp / 8); + int src_offset = src_y0 * src_pitch + src_x0 * (m_iBpp / 8); + unsigned int command = 0; + int clip_region = 0; + int clip_en = 0; + int tp_en = 0; + int fg_color = 0; + int bottom = info->var.yres - 1; + int right = info->var.xres - 1; + int top = 0; + + bottom = (bottom << 16) | right; + command = (rop_alpha_sel << 26) | (pat_sel << 18) | (src_sel << 16) + | (x_dir << 20) | (y_dir << 21) | (command << 24) + | (clip_region << 23) | (clip_en << 22) | (tp_en << 27); + src_pitch = (dst_pitch << 16) | src_pitch; + awidth = awidth | (aheight << 16); + alpha_r = ((rop_alpha_code & 0xff) << 8) | (alpha_r & 0xff) + | (alpha_sel << 16); + src_x0 = (src_x0 & 0x1fff) | ((src_y0 & 0x1fff) << 16); + dst_x0 = (dst_x0 & 0x1fff) | ((dst_y0 & 0x1fff) << 16); + fg_color = region->color; + + unifb_sync(info); + + writel(((u32 *)(info->pseudo_palette))[fg_color], UGE_FCOLOR); + writel(0, UGE_BCOLOR); + writel(src_pitch, UGE_PITCH); + writel(src_offset, UGE_SRCSTART); + writel(dst_offset, UGE_DSTSTART); + writel(awidth, UGE_WIDHEIGHT); + writel(top, UGE_CLIP0); + writel(bottom, UGE_CLIP1); + writel(alpha_r, UGE_ROPALPHA); + writel(src_x0, UGE_SRCXY); + writel(dst_x0, UGE_DSTXY); + writel(command, UGE_COMMAND); +} + +static void unifb_fillrect(struct fb_info *info, + const struct fb_fillrect *region) +{ + struct fb_fillrect modded; + int vxres, vyres; + + if (info->flags & FBINFO_HWACCEL_DISABLED) { + sys_fillrect(info, region); + return; + } + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + memcpy(&modded, region, sizeof(struct fb_fillrect)); + + if (!modded.width || !modded.height || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + unifb_prim_fillrect(info, &modded); +} + +static void unifb_prim_copyarea(struct fb_info *info, + const struct fb_copyarea *area) +{ + int awidth = area->width; + int aheight = area->height; + int m_iBpp = info->var.bits_per_pixel; + int screen_width = info->var.xres; + int src_sel = 2; /* from mem */ + int pat_sel = 0; + int src_x0 = area->sx; + int dst_x0 = area->dx; + int src_y0 = area->sy; + int dst_y0 = area->dy; + + int rop_alpha_sel = 0; + int rop_alpha_code = 0xCC; + int x_dir = 1; + int y_dir = 1; + + int alpha_r = 0; + int alpha_sel = 0; + int dst_pitch = screen_width * (m_iBpp / 8); + int dst_offset = dst_y0 * dst_pitch + dst_x0 * (m_iBpp / 8); + int src_pitch = screen_width * (m_iBpp / 8); + int src_offset = src_y0 * src_pitch + src_x0 * (m_iBpp / 8); + unsigned int command = 0; + int clip_region = 0; + int clip_en = 1; + int tp_en = 0; + int top = 0; + int bottom = info->var.yres; + int right = info->var.xres; + int fg_color = 0; + int bg_color = 0; + + if (src_x0 < 0) + src_x0 = 0; + if (src_y0 < 0) + src_y0 = 0; + + if (src_y0 - dst_y0 > 0) { + y_dir = 1; + } else { + y_dir = 0; + src_offset = (src_y0 + aheight) * src_pitch + + src_x0 * (m_iBpp / 8); + dst_offset = (dst_y0 + aheight) * dst_pitch + + dst_x0 * (m_iBpp / 8); + src_y0 += aheight; + dst_y0 += aheight; + } + + command = (rop_alpha_sel << 26) | (pat_sel << 18) | (src_sel << 16) | + (x_dir << 20) | (y_dir << 21) | (command << 24) | + (clip_region << 23) | (clip_en << 22) | (tp_en << 27); + src_pitch = (dst_pitch << 16) | src_pitch; + awidth = awidth | (aheight << 16); + alpha_r = ((rop_alpha_code & 0xff) << 8) | (alpha_r & 0xff) | + (alpha_sel << 16); + src_x0 = (src_x0 & 0x1fff) | ((src_y0 & 0x1fff) << 16); + dst_x0 = (dst_x0 & 0x1fff) | ((dst_y0 & 0x1fff) << 16); + bottom = (bottom << 16) | right; + + unifb_sync(info); + + writel(src_pitch, UGE_PITCH); + writel(src_offset, UGE_SRCSTART); + writel(dst_offset, UGE_DSTSTART); + writel(awidth, UGE_WIDHEIGHT); + writel(top, UGE_CLIP0); + writel(bottom, UGE_CLIP1); + writel(bg_color, UGE_BCOLOR); + writel(fg_color, UGE_FCOLOR); + writel(alpha_r, UGE_ROPALPHA); + writel(src_x0, UGE_SRCXY); + writel(dst_x0, UGE_DSTXY); + writel(command, UGE_COMMAND); +} + +static void unifb_copyarea(struct fb_info *info, const struct fb_copyarea *area) +{ + struct fb_copyarea modded; + u32 vxres, vyres; + modded.sx = area->sx; + modded.sy = area->sy; + modded.dx = area->dx; + modded.dy = area->dy; + modded.width = area->width; + modded.height = area->height; + + if (info->flags & FBINFO_HWACCEL_DISABLED) { + sys_copyarea(info, area); + return; + } + + vxres = info->var.xres_virtual; + vyres = info->var.yres_virtual; + + if (!modded.width || !modded.height || + modded.sx >= vxres || modded.sy >= vyres || + modded.dx >= vxres || modded.dy >= vyres) + return; + + if (modded.sx + modded.width > vxres) + modded.width = vxres - modded.sx; + if (modded.dx + modded.width > vxres) + modded.width = vxres - modded.dx; + if (modded.sy + modded.height > vyres) + modded.height = vyres - modded.sy; + if (modded.dy + modded.height > vyres) + modded.height = vyres - modded.dy; + + unifb_prim_copyarea(info, &modded); +} + +static void unifb_imageblit(struct fb_info *info, const struct fb_image *image) +{ + sys_imageblit(info, image); +} + +static u_long get_line_length(int xres_virtual, int bpp) +{ + u_long length; + + length = xres_virtual * bpp; + length = (length + 31) & ~31; + length >>= 3; + return length; +} + +/* + * Setting the video mode has been split into two parts. + * First part, xxxfb_check_var, must not write anything + * to hardware, it should only verify and adjust var. + * This means it doesn't alter par but it does use hardware + * data from it to check this var. + */ +static int unifb_check_var(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + u_long line_length; + + /* + * FB_VMODE_CONUPDATE and FB_VMODE_SMOOTH_XPAN are equal! + * as FB_VMODE_SMOOTH_XPAN is only used internally + */ + + if (var->vmode & FB_VMODE_CONUPDATE) { + var->vmode |= FB_VMODE_YWRAP; + var->xoffset = info->var.xoffset; + var->yoffset = info->var.yoffset; + } + + /* + * Some very basic checks + */ + if (!var->xres) + var->xres = 1; + if (!var->yres) + var->yres = 1; + if (var->xres > var->xres_virtual) + var->xres_virtual = var->xres; + if (var->yres > var->yres_virtual) + var->yres_virtual = var->yres; + if (var->bits_per_pixel <= 1) + var->bits_per_pixel = 1; + else if (var->bits_per_pixel <= 8) + var->bits_per_pixel = 8; + else if (var->bits_per_pixel <= 16) + var->bits_per_pixel = 16; + else if (var->bits_per_pixel <= 24) + var->bits_per_pixel = 24; + else if (var->bits_per_pixel <= 32) + var->bits_per_pixel = 32; + else + return -EINVAL; + + if (var->xres_virtual < var->xoffset + var->xres) + var->xres_virtual = var->xoffset + var->xres; + if (var->yres_virtual < var->yoffset + var->yres) + var->yres_virtual = var->yoffset + var->yres; + + /* + * Memory limit + */ + line_length = + get_line_length(var->xres_virtual, var->bits_per_pixel); + if (line_length * var->yres_virtual > UNIFB_MEMSIZE) + return -ENOMEM; + + /* + * Now that we checked it we alter var. The reason being is that the + * video mode passed in might not work but slight changes to it might + * make it work. This way we let the user know what is acceptable. + */ + switch (var->bits_per_pixel) { + case 1: + case 8: + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 0; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 16: /* RGBA 5551 */ + if (var->transp.length) { + var->red.offset = 0; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 5; + var->blue.offset = 10; + var->blue.length = 5; + var->transp.offset = 15; + var->transp.length = 1; + } else { /* RGB 565 */ + var->red.offset = 11; + var->red.length = 5; + var->green.offset = 5; + var->green.length = 6; + var->blue.offset = 0; + var->blue.length = 5; + var->transp.offset = 0; + var->transp.length = 0; + } + break; + case 24: /* RGB 888 */ + var->red.offset = 0; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 16; + var->blue.length = 8; + var->transp.offset = 0; + var->transp.length = 0; + break; + case 32: /* RGBA 8888 */ + var->red.offset = 16; + var->red.length = 8; + var->green.offset = 8; + var->green.length = 8; + var->blue.offset = 0; + var->blue.length = 8; + var->transp.offset = 24; + var->transp.length = 8; + break; + } + var->red.msb_right = 0; + var->green.msb_right = 0; + var->blue.msb_right = 0; + var->transp.msb_right = 0; + + return 0; +} + +/* + * This routine actually sets the video mode. It's in here where we + * the hardware state info->par and fix which can be affected by the + * change in par. For this driver it doesn't do much. + */ +static int unifb_set_par(struct fb_info *info) +{ + int hTotal, vTotal, hSyncStart, hSyncEnd, vSyncStart, vSyncEnd; + int format; + +#ifdef CONFIG_PUV3_PM + struct clk *clk_vga; + u32 pixclk = 0; + int i; + + for (i = 0; i <= 10; i++) { + if (info->var.xres == unifb_modes[i].xres + && info->var.yres == unifb_modes[i].yres + && info->var.upper_margin == unifb_modes[i].upper_margin + && info->var.lower_margin == unifb_modes[i].lower_margin + && info->var.left_margin == unifb_modes[i].left_margin + && info->var.right_margin == unifb_modes[i].right_margin + && info->var.hsync_len == unifb_modes[i].hsync_len + && info->var.vsync_len == unifb_modes[i].vsync_len) { + pixclk = unifb_modes[i].pixclock; + break; + } + } + + /* set clock rate */ + clk_vga = clk_get(info->device, "VGA_CLK"); + if (clk_vga == ERR_PTR(-ENOENT)) + return -ENOENT; + + if (pixclk != 0) { + if (clk_set_rate(clk_vga, pixclk)) { /* set clock failed */ + info->fix = unifb_fix; + info->var = unifb_default; + if (clk_set_rate(clk_vga, unifb_default.pixclock)) + return -EINVAL; + } + } +#endif + + info->fix.line_length = get_line_length(info->var.xres_virtual, + info->var.bits_per_pixel); + + hSyncStart = info->var.xres + info->var.right_margin; + hSyncEnd = hSyncStart + info->var.hsync_len; + hTotal = hSyncEnd + info->var.left_margin; + + vSyncStart = info->var.yres + info->var.lower_margin; + vSyncEnd = vSyncStart + info->var.vsync_len; + vTotal = vSyncEnd + info->var.upper_margin; + + switch (info->var.bits_per_pixel) { + case 8: + format = UDE_CFG_DST8; + break; + case 16: + format = UDE_CFG_DST16; + break; + case 24: + format = UDE_CFG_DST24; + break; + case 32: + format = UDE_CFG_DST32; + break; + default: + return -EINVAL; + } + + writel(PKUNITY_UNIGFX_MMAP_BASE, UDE_FSA); + writel(info->var.yres, UDE_LS); + writel(get_line_length(info->var.xres, + info->var.bits_per_pixel) >> 3, UDE_PS); + /* >> 3 for hardware required. */ + writel((hTotal << 16) | (info->var.xres), UDE_HAT); + writel(((hTotal - 1) << 16) | (info->var.xres - 1), UDE_HBT); + writel(((hSyncEnd - 1) << 16) | (hSyncStart - 1), UDE_HST); + writel((vTotal << 16) | (info->var.yres), UDE_VAT); + writel(((vTotal - 1) << 16) | (info->var.yres - 1), UDE_VBT); + writel(((vSyncEnd - 1) << 16) | (vSyncStart - 1), UDE_VST); + writel(UDE_CFG_GDEN_ENABLE | UDE_CFG_TIMEUP_ENABLE + | format | 0xC0000001, UDE_CFG); + + return 0; +} + +/* + * Set a single color register. The values supplied are already + * rounded down to the hardware's capabilities (according to the + * entries in the var structure). Return != 0 for invalid regno. + */ +static int unifb_setcolreg(u_int regno, u_int red, u_int green, u_int blue, + u_int transp, struct fb_info *info) +{ + if (regno >= 256) /* no. of hw registers */ + return 1; + + /* grayscale works only partially under directcolor */ + if (info->var.grayscale) { + /* grayscale = 0.30*R + 0.59*G + 0.11*B */ + red = green = blue = + (red * 77 + green * 151 + blue * 28) >> 8; + } + +#define CNVT_TOHW(val, width) ((((val)<<(width))+0x7FFF-(val))>>16) + switch (info->fix.visual) { + case FB_VISUAL_TRUECOLOR: + case FB_VISUAL_PSEUDOCOLOR: + red = CNVT_TOHW(red, info->var.red.length); + green = CNVT_TOHW(green, info->var.green.length); + blue = CNVT_TOHW(blue, info->var.blue.length); + transp = CNVT_TOHW(transp, info->var.transp.length); + break; + case FB_VISUAL_DIRECTCOLOR: + red = CNVT_TOHW(red, 8); /* expect 8 bit DAC */ + green = CNVT_TOHW(green, 8); + blue = CNVT_TOHW(blue, 8); + /* hey, there is bug in transp handling... */ + transp = CNVT_TOHW(transp, 8); + break; + } +#undef CNVT_TOHW + /* Truecolor has hardware independent palette */ + if (info->fix.visual == FB_VISUAL_TRUECOLOR) { + u32 v; + + if (regno >= 16) + return 1; + + v = (red << info->var.red.offset) | + (green << info->var.green.offset) | + (blue << info->var.blue.offset) | + (transp << info->var.transp.offset); + switch (info->var.bits_per_pixel) { + case 8: + break; + case 16: + case 24: + case 32: + ((u32 *) (info->pseudo_palette))[regno] = v; + break; + default: + return 1; + } + return 0; + } + return 0; +} + +/* + * Pan or Wrap the Display + * + * This call looks only at xoffset, yoffset and the FB_VMODE_YWRAP flag + */ +static int unifb_pan_display(struct fb_var_screeninfo *var, + struct fb_info *info) +{ + if (var->vmode & FB_VMODE_YWRAP) { + if (var->yoffset < 0 + || var->yoffset >= info->var.yres_virtual + || var->xoffset) + return -EINVAL; + } else { + if (var->xoffset + var->xres > info->var.xres_virtual || + var->yoffset + var->yres > info->var.yres_virtual) + return -EINVAL; + } + info->var.xoffset = var->xoffset; + info->var.yoffset = var->yoffset; + if (var->vmode & FB_VMODE_YWRAP) + info->var.vmode |= FB_VMODE_YWRAP; + else + info->var.vmode &= ~FB_VMODE_YWRAP; + return 0; +} + +int unifb_mmap(struct fb_info *info, + struct vm_area_struct *vma) +{ + unsigned long size = vma->vm_end - vma->vm_start; + unsigned long offset = vma->vm_pgoff << PAGE_SHIFT; + unsigned long pos = info->fix.smem_start + offset; + + if (offset + size > info->fix.smem_len) + return -EINVAL; + + vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); + + if (io_remap_pfn_range(vma, vma->vm_start, pos >> PAGE_SHIFT, size, + vma->vm_page_prot)) + return -EAGAIN; + + vma->vm_flags |= VM_RESERVED; /* avoid to swap out this VMA */ + return 0; + +} + +static struct fb_ops unifb_ops = { + .fb_read = fb_sys_read, + .fb_write = fb_sys_write, + .fb_check_var = unifb_check_var, + .fb_set_par = unifb_set_par, + .fb_setcolreg = unifb_setcolreg, + .fb_pan_display = unifb_pan_display, + .fb_fillrect = unifb_fillrect, + .fb_copyarea = unifb_copyarea, + .fb_imageblit = unifb_imageblit, + .fb_mmap = unifb_mmap, +}; + +/* + * Initialisation + */ +static int unifb_probe(struct platform_device *dev) +{ + struct fb_info *info; + u32 unifb_regs[UNIFB_REGS_NUM]; + int retval = -ENOMEM; + struct resource *iomem, *mapmem; + + info = framebuffer_alloc(sizeof(u32)*256, &dev->dev); + if (!info) + goto err; + + info->screen_base = (char __iomem *)KUSER_UNIGFX_BASE; + info->fbops = &unifb_ops; + + retval = fb_find_mode(&info->var, info, NULL, + unifb_modes, 10, &unifb_modes[0], 16); + + if (!retval || (retval == 4)) + info->var = unifb_default; + + iomem = platform_get_resource(dev, IORESOURCE_MEM, 0); + unifb_fix.mmio_start = iomem->start; + + mapmem = platform_get_resource(dev, IORESOURCE_MEM, 1); + unifb_fix.smem_start = mapmem->start; + unifb_fix.smem_len = UNIFB_MEMSIZE; + + info->fix = unifb_fix; + info->pseudo_palette = info->par; + info->par = NULL; + info->flags = FBINFO_FLAG_DEFAULT; +#ifdef FB_ACCEL_PUV3_UNIGFX + info->fix.accel = FB_ACCEL_PUV3_UNIGFX; +#endif + + retval = fb_alloc_cmap(&info->cmap, 256, 0); + if (retval < 0) + goto err1; + + retval = register_framebuffer(info); + if (retval < 0) + goto err2; + platform_set_drvdata(dev, info); + platform_device_add_data(dev, unifb_regs, sizeof(u32) * UNIFB_REGS_NUM); + + printk(KERN_INFO + "fb%d: Virtual frame buffer device, using %dM of video memory\n", + info->node, UNIFB_MEMSIZE >> 20); + return 0; +err2: + fb_dealloc_cmap(&info->cmap); +err1: + framebuffer_release(info); +err: + return retval; +} + +static int unifb_remove(struct platform_device *dev) +{ + struct fb_info *info = platform_get_drvdata(dev); + + if (info) { + unregister_framebuffer(info); + fb_dealloc_cmap(&info->cmap); + framebuffer_release(info); + } + return 0; +} + +#ifdef CONFIG_PM +static int unifb_resume(struct platform_device *dev) +{ + int rc = 0; + u32 *unifb_regs = dev->dev.platform_data; + + if (dev->dev.power.power_state.event == PM_EVENT_ON) + return 0; + + console_lock(); + + if (dev->dev.power.power_state.event == PM_EVENT_SUSPEND) { + writel(unifb_regs[0], UDE_FSA); + writel(unifb_regs[1], UDE_LS); + writel(unifb_regs[2], UDE_PS); + writel(unifb_regs[3], UDE_HAT); + writel(unifb_regs[4], UDE_HBT); + writel(unifb_regs[5], UDE_HST); + writel(unifb_regs[6], UDE_VAT); + writel(unifb_regs[7], UDE_VBT); + writel(unifb_regs[8], UDE_VST); + writel(unifb_regs[9], UDE_CFG); + } + dev->dev.power.power_state = PMSG_ON; + + console_unlock(); + + return rc; +} + +static int unifb_suspend(struct platform_device *dev, pm_message_t mesg) +{ + u32 *unifb_regs = dev->dev.platform_data; + + unifb_regs[0] = readl(UDE_FSA); + unifb_regs[1] = readl(UDE_LS); + unifb_regs[2] = readl(UDE_PS); + unifb_regs[3] = readl(UDE_HAT); + unifb_regs[4] = readl(UDE_HBT); + unifb_regs[5] = readl(UDE_HST); + unifb_regs[6] = readl(UDE_VAT); + unifb_regs[7] = readl(UDE_VBT); + unifb_regs[8] = readl(UDE_VST); + unifb_regs[9] = readl(UDE_CFG); + + if (mesg.event == dev->dev.power.power_state.event) + return 0; + + switch (mesg.event) { + case PM_EVENT_FREEZE: /* about to take snapshot */ + case PM_EVENT_PRETHAW: /* before restoring snapshot */ + goto done; + } + + console_lock(); + + /* do nothing... */ + + console_unlock(); + +done: + dev->dev.power.power_state = mesg; + + return 0; +} +#else +#define unifb_resume NULL +#define unifb_suspend NULL +#endif + +static struct platform_driver unifb_driver = { + .probe = unifb_probe, + .remove = unifb_remove, + .resume = unifb_resume, + .suspend = unifb_suspend, + .driver = { + .name = "PKUnity-v3-UNIGFX", + }, +}; + +static int __init unifb_init(void) +{ +#ifndef MODULE + if (fb_get_options("unifb", NULL)) + return -ENODEV; +#endif + + return platform_driver_register(&unifb_driver); +} + +module_init(unifb_init); + +static void __exit unifb_exit(void) +{ + platform_driver_unregister(&unifb_driver); +} + +module_exit(unifb_exit); + +MODULE_LICENSE("GPL v2"); diff --git a/drivers/video/msm/msm_fb.c b/drivers/video/msm/msm_fb.c index debe5933fd2e..5436aeb94456 100644 --- a/drivers/video/msm/msm_fb.c +++ b/drivers/video/msm/msm_fb.c @@ -81,7 +81,6 @@ struct msmfb_info { spinlock_t update_lock; struct mutex panel_init_lock; wait_queue_head_t frame_wq; - struct workqueue_struct *resume_workqueue; struct work_struct resume_work; struct msmfb_callback dma_callback; struct msmfb_callback vsync_callback; @@ -111,7 +110,7 @@ static void msmfb_handle_dma_interrupt(struct msmfb_callback *callback) if (msmfb->sleeping == UPDATING && msmfb->frame_done == msmfb->update_frame) { DLOG(SUSPEND_RESUME, "full update completed\n"); - queue_work(msmfb->resume_workqueue, &msmfb->resume_work); + schedule_work(&msmfb->resume_work); } spin_unlock_irqrestore(&msmfb->update_lock, irq_flags); wake_up(&msmfb->frame_wq); @@ -559,12 +558,6 @@ static int msmfb_probe(struct platform_device *pdev) spin_lock_init(&msmfb->update_lock); mutex_init(&msmfb->panel_init_lock); init_waitqueue_head(&msmfb->frame_wq); - msmfb->resume_workqueue = create_workqueue("panel_on"); - if (msmfb->resume_workqueue == NULL) { - printk(KERN_ERR "failed to create panel_on workqueue\n"); - ret = -ENOMEM; - goto error_create_workqueue; - } INIT_WORK(&msmfb->resume_work, power_on_panel); msmfb->black = kzalloc(msmfb->fb->var.bits_per_pixel*msmfb->xres, GFP_KERNEL); @@ -589,8 +582,6 @@ static int msmfb_probe(struct platform_device *pdev) return 0; error_register_framebuffer: - destroy_workqueue(msmfb->resume_workqueue); -error_create_workqueue: iounmap(fb->screen_base); error_setup_fbmem: framebuffer_release(msmfb->fb); diff --git a/drivers/w1/masters/Kconfig b/drivers/w1/masters/Kconfig index 80b3b123dd7f..7c608c5ccf84 100644 --- a/drivers/w1/masters/Kconfig +++ b/drivers/w1/masters/Kconfig @@ -60,7 +60,7 @@ config W1_MASTER_GPIO config HDQ_MASTER_OMAP tristate "OMAP HDQ driver" - depends on ARCH_OMAP2430 || ARCH_OMAP3 + depends on SOC_OMAP2430 || ARCH_OMAP3 help Say Y here if you want support for the 1-wire or HDQ Interface on an OMAP processor. diff --git a/drivers/watchdog/Kconfig b/drivers/watchdog/Kconfig index 31649b7b672f..908160e938dc 100644 --- a/drivers/watchdog/Kconfig +++ b/drivers/watchdog/Kconfig @@ -1083,14 +1083,6 @@ config SH_WDT To compile this driver as a module, choose M here: the module will be called shwdt. -config SH_WDT_MMAP - bool "Allow mmap of SH WDT" - default n - depends on SH_WDT - help - If you say Y here, user applications will be able to mmap the - WDT/CPG registers. - # SPARC Architecture # SPARC64 Architecture diff --git a/drivers/watchdog/shwdt.c b/drivers/watchdog/shwdt.c index 6fc74065abee..4e3e7eb5919c 100644 --- a/drivers/watchdog/shwdt.c +++ b/drivers/watchdog/shwdt.c @@ -1,9 +1,9 @@ /* - * drivers/char/watchdog/shwdt.c + * drivers/watchdog/shwdt.c * * Watchdog driver for integrated watchdog in the SuperH processors. * - * Copyright (C) 2001, 2002, 2003 Paul Mundt <lethal@linux-sh.org> + * Copyright (C) 2001 - 2010 Paul Mundt <lethal@linux-sh.org> * * 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 @@ -19,6 +19,7 @@ */ #include <linux/module.h> #include <linux/moduleparam.h> +#include <linux/platform_device.h> #include <linux/init.h> #include <linux/types.h> #include <linux/miscdevice.h> @@ -28,11 +29,12 @@ #include <linux/ioport.h> #include <linux/fs.h> #include <linux/mm.h> +#include <linux/slab.h> #include <linux/io.h> #include <linux/uaccess.h> #include <asm/watchdog.h> -#define PFX "shwdt: " +#define DRV_NAME "sh-wdt" /* * Default clock division ratio is 5.25 msecs. For an additional table of @@ -62,37 +64,36 @@ * misses its deadline, the kernel timer will allow the WDT to overflow. */ static int clock_division_ratio = WTCSR_CKS_4096; - #define next_ping_period(cks) msecs_to_jiffies(cks - 4) -static void sh_wdt_ping(unsigned long data); - -static unsigned long shwdt_is_open; static const struct watchdog_info sh_wdt_info; -static char shwdt_expect_close; -static DEFINE_TIMER(timer, sh_wdt_ping, 0, 0); -static unsigned long next_heartbeat; +static struct platform_device *sh_wdt_dev; static DEFINE_SPINLOCK(shwdt_lock); #define WATCHDOG_HEARTBEAT 30 /* 30 sec default heartbeat */ static int heartbeat = WATCHDOG_HEARTBEAT; /* in seconds */ - static int nowayout = WATCHDOG_NOWAYOUT; +static unsigned long next_heartbeat; -/** - * sh_wdt_start - Start the Watchdog - * - * Starts the watchdog. - */ -static void sh_wdt_start(void) +struct sh_wdt { + void __iomem *base; + struct device *dev; + + struct timer_list timer; + + unsigned long enabled; + char expect_close; +}; + +static void sh_wdt_start(struct sh_wdt *wdt) { - __u8 csr; unsigned long flags; + u8 csr; spin_lock_irqsave(&shwdt_lock, flags); next_heartbeat = jiffies + (heartbeat * HZ); - mod_timer(&timer, next_ping_period(clock_division_ratio)); + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); csr = sh_wdt_read_csr(); csr |= WTCSR_WT | clock_division_ratio; @@ -114,15 +115,6 @@ static void sh_wdt_start(void) sh_wdt_write_csr(csr); #ifdef CONFIG_CPU_SH2 - /* - * Whoever came up with the RSTCSR semantics must've been smoking - * some of the good stuff, since in addition to the WTCSR/WTCNT write - * brain-damage, it's managed to fuck things up one step further.. - * - * If we need to clear the WOVF bit, the upper byte has to be 0xa5.. - * but if we want to touch RSTE or RSTS, the upper byte has to be - * 0x5a.. - */ csr = sh_wdt_read_rstcsr(); csr &= ~RSTCSR_RSTS; sh_wdt_write_rstcsr(csr); @@ -130,30 +122,23 @@ static void sh_wdt_start(void) spin_unlock_irqrestore(&shwdt_lock, flags); } -/** - * sh_wdt_stop - Stop the Watchdog - * Stops the watchdog. - */ -static void sh_wdt_stop(void) +static void sh_wdt_stop(struct sh_wdt *wdt) { - __u8 csr; unsigned long flags; + u8 csr; spin_lock_irqsave(&shwdt_lock, flags); - del_timer(&timer); + del_timer(&wdt->timer); csr = sh_wdt_read_csr(); csr &= ~WTCSR_TME; sh_wdt_write_csr(csr); + spin_unlock_irqrestore(&shwdt_lock, flags); } -/** - * sh_wdt_keepalive - Keep the Userspace Watchdog Alive - * The Userspace watchdog got a KeepAlive: schedule the next heartbeat. - */ -static inline void sh_wdt_keepalive(void) +static inline void sh_wdt_keepalive(struct sh_wdt *wdt) { unsigned long flags; @@ -162,10 +147,6 @@ static inline void sh_wdt_keepalive(void) spin_unlock_irqrestore(&shwdt_lock, flags); } -/** - * sh_wdt_set_heartbeat - Set the Userspace Watchdog heartbeat - * Set the Userspace Watchdog heartbeat - */ static int sh_wdt_set_heartbeat(int t) { unsigned long flags; @@ -179,19 +160,14 @@ static int sh_wdt_set_heartbeat(int t) return 0; } -/** - * sh_wdt_ping - Ping the Watchdog - * @data: Unused - * - * Clears overflow bit, resets timer counter. - */ static void sh_wdt_ping(unsigned long data) { + struct sh_wdt *wdt = (struct sh_wdt *)data; unsigned long flags; spin_lock_irqsave(&shwdt_lock, flags); if (time_before(jiffies, next_heartbeat)) { - __u8 csr; + u8 csr; csr = sh_wdt_read_csr(); csr &= ~WTCSR_IOVF; @@ -199,148 +175,76 @@ static void sh_wdt_ping(unsigned long data) sh_wdt_write_cnt(0); - mod_timer(&timer, next_ping_period(clock_division_ratio)); + mod_timer(&wdt->timer, next_ping_period(clock_division_ratio)); } else - printk(KERN_WARNING PFX "Heartbeat lost! Will not ping " - "the watchdog\n"); + dev_warn(wdt->dev, "Heartbeat lost! Will not ping " + "the watchdog\n"); spin_unlock_irqrestore(&shwdt_lock, flags); } -/** - * sh_wdt_open - Open the Device - * @inode: inode of device - * @file: file handle of device - * - * Watchdog device is opened and started. - */ static int sh_wdt_open(struct inode *inode, struct file *file) { - if (test_and_set_bit(0, &shwdt_is_open)) + struct sh_wdt *wdt = platform_get_drvdata(sh_wdt_dev); + + if (test_and_set_bit(0, &wdt->enabled)) return -EBUSY; if (nowayout) __module_get(THIS_MODULE); - sh_wdt_start(); + file->private_data = wdt; + + sh_wdt_start(wdt); return nonseekable_open(inode, file); } -/** - * sh_wdt_close - Close the Device - * @inode: inode of device - * @file: file handle of device - * - * Watchdog device is closed and stopped. - */ static int sh_wdt_close(struct inode *inode, struct file *file) { - if (shwdt_expect_close == 42) { - sh_wdt_stop(); + struct sh_wdt *wdt = file->private_data; + + if (wdt->expect_close == 42) { + sh_wdt_stop(wdt); } else { - printk(KERN_CRIT PFX "Unexpected close, not " - "stopping watchdog!\n"); - sh_wdt_keepalive(); + dev_crit(wdt->dev, "Unexpected close, not " + "stopping watchdog!\n"); + sh_wdt_keepalive(wdt); } - clear_bit(0, &shwdt_is_open); - shwdt_expect_close = 0; + clear_bit(0, &wdt->enabled); + wdt->expect_close = 0; return 0; } -/** - * sh_wdt_write - Write to Device - * @file: file handle of device - * @buf: buffer to write - * @count: length of buffer - * @ppos: offset - * - * Pings the watchdog on write. - */ static ssize_t sh_wdt_write(struct file *file, const char *buf, size_t count, loff_t *ppos) { + struct sh_wdt *wdt = file->private_data; + if (count) { if (!nowayout) { size_t i; - shwdt_expect_close = 0; + wdt->expect_close = 0; for (i = 0; i != count; i++) { char c; if (get_user(c, buf + i)) return -EFAULT; if (c == 'V') - shwdt_expect_close = 42; + wdt->expect_close = 42; } } - sh_wdt_keepalive(); + sh_wdt_keepalive(wdt); } return count; } -/** - * sh_wdt_mmap - map WDT/CPG registers into userspace - * @file: file structure for the device - * @vma: VMA to map the registers into - * - * A simple mmap() implementation for the corner cases where the counter - * needs to be mapped in userspace directly. Due to the relatively small - * size of the area, neighbouring registers not necessarily tied to the - * CPG will also be accessible through the register page, so this remains - * configurable for users that really know what they're doing. - * - * Additionaly, the register page maps in the CPG register base relative - * to the nearest page-aligned boundary, which requires that userspace do - * the appropriate CPU subtype math for calculating the page offset for - * the counter value. - */ -static int sh_wdt_mmap(struct file *file, struct vm_area_struct *vma) -{ - int ret = -ENOSYS; - -#ifdef CONFIG_SH_WDT_MMAP - unsigned long addr; - - /* Only support the simple cases where we map in a register page. */ - if (((vma->vm_end - vma->vm_start) != PAGE_SIZE) || vma->vm_pgoff) - return -EINVAL; - - /* - * Pick WTCNT as the start, it's usually the first register after the - * FRQCR, and neither one are generally page-aligned out of the box. - */ - addr = WTCNT & ~(PAGE_SIZE - 1); - - vma->vm_flags |= VM_IO; - vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot); - - if (io_remap_pfn_range(vma, vma->vm_start, addr >> PAGE_SHIFT, - PAGE_SIZE, vma->vm_page_prot)) { - printk(KERN_ERR PFX "%s: io_remap_pfn_range failed\n", - __func__); - return -EAGAIN; - } - - ret = 0; -#endif - - return ret; -} - -/** - * sh_wdt_ioctl - Query Device - * @file: file handle of device - * @cmd: watchdog command - * @arg: argument - * - * Query basic information from the device or ping it, as outlined by the - * watchdog API. - */ static long sh_wdt_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { + struct sh_wdt *wdt = file->private_data; int new_heartbeat; int options, retval = -EINVAL; @@ -356,18 +260,18 @@ static long sh_wdt_ioctl(struct file *file, unsigned int cmd, return -EFAULT; if (options & WDIOS_DISABLECARD) { - sh_wdt_stop(); + sh_wdt_stop(wdt); retval = 0; } if (options & WDIOS_ENABLECARD) { - sh_wdt_start(); + sh_wdt_start(wdt); retval = 0; } return retval; case WDIOC_KEEPALIVE: - sh_wdt_keepalive(); + sh_wdt_keepalive(wdt); return 0; case WDIOC_SETTIMEOUT: if (get_user(new_heartbeat, (int *)arg)) @@ -376,7 +280,7 @@ static long sh_wdt_ioctl(struct file *file, unsigned int cmd, if (sh_wdt_set_heartbeat(new_heartbeat)) return -EINVAL; - sh_wdt_keepalive(); + sh_wdt_keepalive(wdt); /* Fall */ case WDIOC_GETTIMEOUT: return put_user(heartbeat, (int *)arg); @@ -386,20 +290,13 @@ static long sh_wdt_ioctl(struct file *file, unsigned int cmd, return 0; } -/** - * sh_wdt_notify_sys - Notifier Handler - * @this: notifier block - * @code: notifier event - * @unused: unused - * - * Handles specific events, such as turning off the watchdog during a - * shutdown event. - */ static int sh_wdt_notify_sys(struct notifier_block *this, unsigned long code, void *unused) { + struct sh_wdt *wdt = platform_get_drvdata(sh_wdt_dev); + if (code == SYS_DOWN || code == SYS_HALT) - sh_wdt_stop(); + sh_wdt_stop(wdt); return NOTIFY_DONE; } @@ -411,7 +308,6 @@ static const struct file_operations sh_wdt_fops = { .unlocked_ioctl = sh_wdt_ioctl, .open = sh_wdt_open, .release = sh_wdt_close, - .mmap = sh_wdt_mmap, }; static const struct watchdog_info sh_wdt_info = { @@ -431,66 +327,148 @@ static struct miscdevice sh_wdt_miscdev = { .fops = &sh_wdt_fops, }; -/** - * sh_wdt_init - Initialize module - * Registers the device and notifier handler. Actual device - * initialization is handled by sh_wdt_open(). - */ -static int __init sh_wdt_init(void) +static int __devinit sh_wdt_probe(struct platform_device *pdev) { + struct sh_wdt *wdt; + struct resource *res; int rc; - if (clock_division_ratio < 0x5 || clock_division_ratio > 0x7) { - clock_division_ratio = WTCSR_CKS_4096; - printk(KERN_INFO PFX - "clock_division_ratio value must be 0x5<=x<=0x7, using %d\n", - clock_division_ratio); + /* + * As this driver only covers the global watchdog case, reject + * any attempts to register per-CPU watchdogs. + */ + if (pdev->id != -1) + return -EINVAL; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (unlikely(!res)) + return -EINVAL; + + if (!devm_request_mem_region(&pdev->dev, res->start, + resource_size(res), DRV_NAME)) + return -EBUSY; + + wdt = devm_kzalloc(&pdev->dev, sizeof(struct sh_wdt), GFP_KERNEL); + if (unlikely(!wdt)) { + rc = -ENOMEM; + goto out_release; } - rc = sh_wdt_set_heartbeat(heartbeat); - if (unlikely(rc)) { - heartbeat = WATCHDOG_HEARTBEAT; - printk(KERN_INFO PFX - "heartbeat value must be 1<=x<=3600, using %d\n", - heartbeat); + wdt->dev = &pdev->dev; + + wdt->base = devm_ioremap(&pdev->dev, res->start, resource_size(res)); + if (unlikely(!wdt->base)) { + rc = -ENXIO; + goto out_err; } rc = register_reboot_notifier(&sh_wdt_notifier); if (unlikely(rc)) { - printk(KERN_ERR PFX + dev_err(&pdev->dev, "Can't register reboot notifier (err=%d)\n", rc); - return rc; + goto out_unmap; } + sh_wdt_miscdev.parent = wdt->dev; + rc = misc_register(&sh_wdt_miscdev); if (unlikely(rc)) { - printk(KERN_ERR PFX + dev_err(&pdev->dev, "Can't register miscdev on minor=%d (err=%d)\n", sh_wdt_miscdev.minor, rc); - unregister_reboot_notifier(&sh_wdt_notifier); - return rc; + goto out_unreg; } - printk(KERN_INFO PFX "initialized. heartbeat=%d sec (nowayout=%d)\n", - heartbeat, nowayout); + init_timer(&wdt->timer); + wdt->timer.function = sh_wdt_ping; + wdt->timer.data = (unsigned long)wdt; + wdt->timer.expires = next_ping_period(clock_division_ratio); + + platform_set_drvdata(pdev, wdt); + sh_wdt_dev = pdev; + + dev_info(&pdev->dev, "initialized.\n"); return 0; + +out_unreg: + unregister_reboot_notifier(&sh_wdt_notifier); +out_unmap: + devm_iounmap(&pdev->dev, wdt->base); +out_err: + devm_kfree(&pdev->dev, wdt); +out_release: + devm_release_mem_region(&pdev->dev, res->start, resource_size(res)); + + return rc; } -/** - * sh_wdt_exit - Deinitialize module - * Unregisters the device and notifier handler. Actual device - * deinitialization is handled by sh_wdt_close(). - */ -static void __exit sh_wdt_exit(void) +static int __devexit sh_wdt_remove(struct platform_device *pdev) { + struct sh_wdt *wdt = platform_get_drvdata(pdev); + struct resource *res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + + platform_set_drvdata(pdev, NULL); + misc_deregister(&sh_wdt_miscdev); + + sh_wdt_dev = NULL; + unregister_reboot_notifier(&sh_wdt_notifier); + devm_release_mem_region(&pdev->dev, res->start, resource_size(res)); + devm_iounmap(&pdev->dev, wdt->base); + devm_kfree(&pdev->dev, wdt); + + return 0; } +static struct platform_driver sh_wdt_driver = { + .driver = { + .name = DRV_NAME, + .owner = THIS_MODULE, + }, + + .probe = sh_wdt_probe, + .remove = __devexit_p(sh_wdt_remove), +}; + +static int __init sh_wdt_init(void) +{ + int rc; + + if (unlikely(clock_division_ratio < 0x5 || + clock_division_ratio > 0x7)) { + clock_division_ratio = WTCSR_CKS_4096; + + pr_info("%s: divisor must be 0x5<=x<=0x7, using %d\n", + DRV_NAME, clock_division_ratio); + } + + rc = sh_wdt_set_heartbeat(heartbeat); + if (unlikely(rc)) { + heartbeat = WATCHDOG_HEARTBEAT; + + pr_info("%s: heartbeat value must be 1<=x<=3600, using %d\n", + DRV_NAME, heartbeat); + } + + pr_info("%s: configured with heartbeat=%d sec (nowayout=%d)\n", + DRV_NAME, heartbeat, nowayout); + + return platform_driver_register(&sh_wdt_driver); +} + +static void __exit sh_wdt_exit(void) +{ + platform_driver_unregister(&sh_wdt_driver); +} +module_init(sh_wdt_init); +module_exit(sh_wdt_exit); + MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); MODULE_DESCRIPTION("SuperH watchdog driver"); MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:" DRV_NAME); MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR); module_param(clock_division_ratio, int, 0); @@ -507,6 +485,3 @@ module_param(nowayout, int, 0); MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); - -module_init(sh_wdt_init); -module_exit(sh_wdt_exit); diff --git a/drivers/watchdog/sp805_wdt.c b/drivers/watchdog/sp805_wdt.c index 9127eda2145b..0a0efe713bc8 100644 --- a/drivers/watchdog/sp805_wdt.c +++ b/drivers/watchdog/sp805_wdt.c @@ -278,7 +278,7 @@ static struct miscdevice sp805_wdt_miscdev = { }; static int __devinit -sp805_wdt_probe(struct amba_device *adev, struct amba_id *id) +sp805_wdt_probe(struct amba_device *adev, const struct amba_id *id) { int ret = 0; |