diff options
Diffstat (limited to 'drivers')
272 files changed, 26838 insertions, 2098 deletions
diff --git a/drivers/Makefile b/drivers/Makefile index fceb71a741c3..e121b66ef082 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_ATA_OVER_ETH) += block/aoe/ obj-$(CONFIG_PARIDE) += block/paride/ obj-$(CONFIG_TC) += tc/ obj-$(CONFIG_UWB) += uwb/ +obj-$(CONFIG_USB_OTG_UTILS) += usb/otg/ obj-$(CONFIG_USB) += usb/ obj-$(CONFIG_USB_MUSB_HDRC) += usb/musb/ obj-$(CONFIG_PCI) += usb/ diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index 63a17b55b39b..7a0f4aa4fa1e 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -20,7 +20,7 @@ * * * ACPI based HotPlug driver that supports Memory Hotplug - * This driver fields notifications from firmare for memory add + * This driver fields notifications from firmware for memory add * and remove operations and alerts the VM of the affected memory * ranges. */ diff --git a/drivers/acpi/executer/exprep.c b/drivers/acpi/executer/exprep.c index 5d438c32989d..a7dc87ecee37 100644 --- a/drivers/acpi/executer/exprep.c +++ b/drivers/acpi/executer/exprep.c @@ -404,7 +404,7 @@ acpi_ex_prep_common_field_object(union acpi_operand_object *obj_desc, * * RETURN: Status * - * DESCRIPTION: Construct an union acpi_operand_object of type def_field and + * DESCRIPTION: Construct a union acpi_operand_object of type def_field and * connect it to the parent Node. * ******************************************************************************/ diff --git a/drivers/acpi/executer/exresolv.c b/drivers/acpi/executer/exresolv.c index 89571b92a522..60e8c47128e9 100644 --- a/drivers/acpi/executer/exresolv.c +++ b/drivers/acpi/executer/exresolv.c @@ -146,7 +146,7 @@ acpi_ex_resolve_object_to_value(union acpi_operand_object **stack_ptr, stack_desc = *stack_ptr; - /* This is an union acpi_operand_object */ + /* This is a union acpi_operand_object */ switch (ACPI_GET_OBJECT_TYPE(stack_desc)) { case ACPI_TYPE_LOCAL_REFERENCE: diff --git a/drivers/acpi/executer/exstore.c b/drivers/acpi/executer/exstore.c index 3318df4cbd98..1c118ba78adb 100644 --- a/drivers/acpi/executer/exstore.c +++ b/drivers/acpi/executer/exstore.c @@ -274,7 +274,7 @@ acpi_ex_do_debug_object(union acpi_operand_object *source_desc, * * PARAMETERS: *source_desc - Value to be stored * *dest_desc - Where to store it. Must be an NS node - * or an union acpi_operand_object of type + * or a union acpi_operand_object of type * Reference; * walk_state - Current walk state * diff --git a/drivers/acpi/resources/rscreate.c b/drivers/acpi/resources/rscreate.c index c0bbfa2c4193..08b8d73e6ee5 100644 --- a/drivers/acpi/resources/rscreate.c +++ b/drivers/acpi/resources/rscreate.c @@ -124,7 +124,7 @@ acpi_rs_create_resource_list(union acpi_operand_object *aml_buffer, * * FUNCTION: acpi_rs_create_pci_routing_table * - * PARAMETERS: package_object - Pointer to an union acpi_operand_object + * PARAMETERS: package_object - Pointer to a union acpi_operand_object * package * output_buffer - Pointer to the user's buffer * diff --git a/drivers/acpi/utilities/utobject.c b/drivers/acpi/utilities/utobject.c index c354e7a42bcd..4bef3cfbaccb 100644 --- a/drivers/acpi/utilities/utobject.c +++ b/drivers/acpi/utilities/utobject.c @@ -297,7 +297,7 @@ union acpi_operand_object *acpi_ut_create_string_object(acpi_size string_size) * * RETURN: TRUE if object is valid, FALSE otherwise * - * DESCRIPTION: Validate a pointer to be an union acpi_operand_object + * DESCRIPTION: Validate a pointer to be a union acpi_operand_object * ******************************************************************************/ @@ -389,7 +389,7 @@ void acpi_ut_delete_object_desc(union acpi_operand_object *object) { ACPI_FUNCTION_TRACE_PTR(ut_delete_object_desc, object); - /* Object must be an union acpi_operand_object */ + /* Object must be a union acpi_operand_object */ if (ACPI_GET_DESCRIPTOR_TYPE(object) != ACPI_DESC_TYPE_OPERAND) { ACPI_ERROR((AE_INFO, diff --git a/drivers/ata/libata-core.c b/drivers/ata/libata-core.c index fecca4223f8e..f178a450ec08 100644 --- a/drivers/ata/libata-core.c +++ b/drivers/ata/libata-core.c @@ -56,6 +56,7 @@ #include <linux/workqueue.h> #include <linux/scatterlist.h> #include <linux/io.h> +#include <linux/async.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> #include <scsi/scsi_host.h> @@ -5909,6 +5910,54 @@ 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 rc; + struct ata_port *ap = data; + /* probe */ + if (ap->ops->error_handler) { + struct ata_eh_info *ehi = &ap->link.eh_info; + unsigned long flags; + + ata_port_probe(ap); + + /* kick EH for boot probing */ + spin_lock_irqsave(ap->lock, flags); + + ehi->probe_mask |= ATA_ALL_DEVICES; + ehi->action |= ATA_EH_RESET | ATA_EH_LPM; + ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET; + + ap->pflags &= ~ATA_PFLAG_INITIALIZING; + ap->pflags |= ATA_PFLAG_LOADING; + ata_port_schedule_eh(ap); + + spin_unlock_irqrestore(ap->lock, flags); + + /* wait for EH to finish */ + ata_port_wait_eh(ap); + } else { + 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. + */ + } + } + + /* 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 @@ -5988,52 +6037,9 @@ int ata_host_register(struct ata_host *host, struct scsi_host_template *sht) DPRINTK("probe begin\n"); for (i = 0; i < host->n_ports; i++) { struct ata_port *ap = host->ports[i]; - - /* probe */ - if (ap->ops->error_handler) { - struct ata_eh_info *ehi = &ap->link.eh_info; - unsigned long flags; - - ata_port_probe(ap); - - /* kick EH for boot probing */ - spin_lock_irqsave(ap->lock, flags); - - ehi->probe_mask |= ATA_ALL_DEVICES; - ehi->action |= ATA_EH_RESET | ATA_EH_LPM; - ehi->flags |= ATA_EHI_NO_AUTOPSY | ATA_EHI_QUIET; - - ap->pflags &= ~ATA_PFLAG_INITIALIZING; - ap->pflags |= ATA_PFLAG_LOADING; - ata_port_schedule_eh(ap); - - spin_unlock_irqrestore(ap->lock, flags); - - /* wait for EH to finish */ - ata_port_wait_eh(ap); - } else { - 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. - */ - } - } - } - - /* probes are done, now scan each port's disk(s) */ - DPRINTK("host probe begin\n"); - for (i = 0; i < host->n_ports; i++) { - struct ata_port *ap = host->ports[i]; - - ata_scsi_scan_host(ap, 1); + async_schedule(async_port_probe, ap); } + DPRINTK("probe end\n"); return 0; } diff --git a/drivers/base/Kconfig b/drivers/base/Kconfig index d8e8c49c0cbd..8f006f96ff53 100644 --- a/drivers/base/Kconfig +++ b/drivers/base/Kconfig @@ -54,7 +54,7 @@ config FIRMWARE_IN_KERNEL such firmware, and do not wish to use an initrd. This single option controls the inclusion of firmware for - every driver which uses request_firmare() and ships its + every driver which uses request_firmware() and ships its firmware in the kernel source tree, to avoid a proliferation of 'Include firmware for xxx device' options. diff --git a/drivers/base/topology.c b/drivers/base/topology.c index a8bc1cbcfa7c..a778fb52b11f 100644 --- a/drivers/base/topology.c +++ b/drivers/base/topology.c @@ -28,6 +28,7 @@ #include <linux/mm.h> #include <linux/cpu.h> #include <linux/module.h> +#include <linux/hardirq.h> #include <linux/topology.h> #define define_one_ro(_name) \ diff --git a/drivers/block/ub.c b/drivers/block/ub.c index 048d71d244d7..12fb816db7b0 100644 --- a/drivers/block/ub.c +++ b/drivers/block/ub.c @@ -1579,7 +1579,7 @@ static void ub_reset_task(struct work_struct *work) struct ub_dev *sc = container_of(work, struct ub_dev, reset_work); unsigned long flags; struct ub_lun *lun; - int lkr, rc; + int rc; if (!sc->reset) { printk(KERN_WARNING "%s: Running reset unrequested\n", @@ -1597,10 +1597,11 @@ static void ub_reset_task(struct work_struct *work) } else if (sc->dev->actconfig->desc.bNumInterfaces != 1) { ; } else { - if ((lkr = usb_lock_device_for_reset(sc->dev, sc->intf)) < 0) { + rc = usb_lock_device_for_reset(sc->dev, sc->intf); + if (rc < 0) { printk(KERN_NOTICE "%s: usb_lock_device_for_reset failed (%d)\n", - sc->name, lkr); + sc->name, rc); } else { rc = usb_reset_device(sc->dev); if (rc < 0) { @@ -1608,9 +1609,7 @@ static void ub_reset_task(struct work_struct *work) "usb_lock_device_for_reset failed (%d)\n", sc->name, rc); } - - if (lkr) - usb_unlock_device(sc->dev); + usb_unlock_device(sc->dev); } } diff --git a/drivers/char/epca.c b/drivers/char/epca.c index 39ad820b2350..af7c13ca9493 100644 --- a/drivers/char/epca.c +++ b/drivers/char/epca.c @@ -769,7 +769,7 @@ static int pc_open(struct tty_struct *tty, struct file *filp) /* Check status of board configured in system. */ /* - * I check to see if the epca_setup routine detected an user error. It + * I check to see if the epca_setup routine detected a user error. It * might be better to put this in pc_init, but for the moment it goes * here. */ diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index 5f076aef74fa..a8c8d9c19d74 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -83,7 +83,7 @@ config CPU_FREQ_DEFAULT_GOV_USERSPACE select CPU_FREQ_GOV_USERSPACE help Use the CPUFreq governor 'userspace' as default. This allows - you to set the CPU frequency manually or when an userspace + you to set the CPU frequency manually or when a userspace program shall be able to set the CPU dynamically without having to enable the userspace governor manually. @@ -138,7 +138,7 @@ config CPU_FREQ_GOV_USERSPACE tristate "'userspace' governor for userspace frequency scaling" help Enable this cpufreq governor when you either want to set the - CPU frequency manually or when an userspace program shall + CPU frequency manually or when a userspace program shall be able to set the CPU dynamically, like on LART <http://www.lartmaker.nl/>. diff --git a/drivers/firewire/fw-card.c b/drivers/firewire/fw-card.c index 418c18f07e9d..799f94424c8a 100644 --- a/drivers/firewire/fw-card.c +++ b/drivers/firewire/fw-card.c @@ -75,7 +75,7 @@ generate_config_rom(struct fw_card *card, size_t *config_rom_length) * controller, block reads to the config rom accesses the host * memory, but quadlet read access the hardware bus info block * registers. That's just crack, but it means we should make - * sure the contents of bus info block in host memory mathces + * sure the contents of bus info block in host memory matches * the version stored in the OHCI registers. */ @@ -189,6 +189,17 @@ static const char gap_count_table[] = { 63, 5, 7, 8, 10, 13, 16, 18, 21, 24, 26, 29, 32, 35, 37, 40 }; +void +fw_schedule_bm_work(struct fw_card *card, unsigned long delay) +{ + int scheduled; + + fw_card_get(card); + scheduled = schedule_delayed_work(&card->work, delay); + if (!scheduled) + fw_card_put(card); +} + static void fw_card_bm_work(struct work_struct *work) { @@ -206,7 +217,7 @@ fw_card_bm_work(struct work_struct *work) if (local_node == NULL) { spin_unlock_irqrestore(&card->lock, flags); - return; + goto out_put_card; } fw_node_get(local_node); fw_node_get(root_node); @@ -280,7 +291,7 @@ fw_card_bm_work(struct work_struct *work) * this task 100ms from now. */ spin_unlock_irqrestore(&card->lock, flags); - schedule_delayed_work(&card->work, DIV_ROUND_UP(HZ, 10)); + fw_schedule_bm_work(card, DIV_ROUND_UP(HZ, 10)); goto out; } @@ -355,6 +366,8 @@ fw_card_bm_work(struct work_struct *work) fw_device_put(root_device); fw_node_put(root_node); fw_node_put(local_node); + out_put_card: + fw_card_put(card); } static void @@ -510,7 +523,6 @@ fw_core_remove_card(struct fw_card *card) fw_card_put(card); wait_for_completion(&card->done); - cancel_delayed_work_sync(&card->work); WARN_ON(!list_empty(&card->transaction_list)); del_timer_sync(&card->flush_timer); } diff --git a/drivers/firewire/fw-device.c b/drivers/firewire/fw-device.c index 6b9be42c7b98..c173be383725 100644 --- a/drivers/firewire/fw-device.c +++ b/drivers/firewire/fw-device.c @@ -617,7 +617,7 @@ static int shutdown_unit(struct device *device, void *data) */ DECLARE_RWSEM(fw_device_rwsem); -static DEFINE_IDR(fw_device_idr); +DEFINE_IDR(fw_device_idr); int fw_cdev_major; struct fw_device *fw_device_get_by_devt(dev_t devt) @@ -689,7 +689,7 @@ static void fw_device_init(struct work_struct *work) fw_notify("giving up on config rom for node id %x\n", device->node_id); if (device->node == device->card->root_node) - schedule_delayed_work(&device->card->work, 0); + fw_schedule_bm_work(device->card, 0); fw_device_release(&device->device); } return; @@ -758,7 +758,7 @@ static void fw_device_init(struct work_struct *work) * pretty harmless. */ if (device->node == device->card->root_node) - schedule_delayed_work(&device->card->work, 0); + fw_schedule_bm_work(device->card, 0); return; @@ -892,7 +892,7 @@ static void fw_device_refresh(struct work_struct *work) fw_device_shutdown(work); out: if (node_id == card->root_node->node_id) - schedule_delayed_work(&card->work, 0); + fw_schedule_bm_work(card, 0); } void fw_node_event(struct fw_card *card, struct fw_node *node, int event) diff --git a/drivers/firewire/fw-device.h b/drivers/firewire/fw-device.h index 42305bbac72f..df51732608d9 100644 --- a/drivers/firewire/fw-device.h +++ b/drivers/firewire/fw-device.h @@ -21,6 +21,7 @@ #include <linux/fs.h> #include <linux/cdev.h> +#include <linux/idr.h> #include <linux/rwsem.h> #include <asm/atomic.h> @@ -99,6 +100,7 @@ void fw_device_cdev_update(struct fw_device *device); void fw_device_cdev_remove(struct fw_device *device); extern struct rw_semaphore fw_device_rwsem; +extern struct idr fw_device_idr; extern int fw_cdev_major; /* diff --git a/drivers/firewire/fw-sbp2.c b/drivers/firewire/fw-sbp2.c index e54403ee59e7..e88d5067448c 100644 --- a/drivers/firewire/fw-sbp2.c +++ b/drivers/firewire/fw-sbp2.c @@ -670,17 +670,6 @@ static void sbp2_agent_reset_no_wait(struct sbp2_logical_unit *lu) &d, sizeof(d), complete_agent_reset_write_no_wait, t); } -static void sbp2_set_generation(struct sbp2_logical_unit *lu, int generation) -{ - struct fw_card *card = fw_device(lu->tgt->unit->device.parent)->card; - unsigned long flags; - - /* serialize with comparisons of lu->generation and card->generation */ - spin_lock_irqsave(&card->lock, flags); - lu->generation = generation; - spin_unlock_irqrestore(&card->lock, flags); -} - static inline void sbp2_allow_block(struct sbp2_logical_unit *lu) { /* @@ -884,7 +873,7 @@ static void sbp2_login(struct work_struct *work) goto out; generation = device->generation; - smp_rmb(); /* node_id must not be older than generation */ + smp_rmb(); /* node IDs must not be older than generation */ node_id = device->node_id; local_node_id = device->card->node_id; @@ -908,7 +897,8 @@ static void sbp2_login(struct work_struct *work) tgt->node_id = node_id; tgt->address_high = local_node_id << 16; - sbp2_set_generation(lu, generation); + smp_wmb(); /* node IDs must not be older than generation */ + lu->generation = generation; lu->command_block_agent_address = ((u64)(be32_to_cpu(response.command_block_agent.high) & 0xffff) @@ -1201,7 +1191,7 @@ static void sbp2_reconnect(struct work_struct *work) goto out; generation = device->generation; - smp_rmb(); /* node_id must not be older than generation */ + smp_rmb(); /* node IDs must not be older than generation */ node_id = device->node_id; local_node_id = device->card->node_id; @@ -1228,7 +1218,8 @@ static void sbp2_reconnect(struct work_struct *work) tgt->node_id = node_id; tgt->address_high = local_node_id << 16; - sbp2_set_generation(lu, generation); + smp_wmb(); /* node IDs must not be older than generation */ + lu->generation = generation; fw_notify("%s: reconnected to LUN %04x (%d retries)\n", tgt->bus_id, lu->lun, lu->retries); diff --git a/drivers/firewire/fw-topology.c b/drivers/firewire/fw-topology.c index 5e204713002d..c9be6e6948c4 100644 --- a/drivers/firewire/fw-topology.c +++ b/drivers/firewire/fw-topology.c @@ -355,6 +355,9 @@ report_lost_node(struct fw_card *card, { fw_node_event(card, node, FW_NODE_DESTROYED); fw_node_put(node); + + /* Topology has changed - reset bus manager retry counter */ + card->bm_retries = 0; } static void @@ -374,6 +377,9 @@ report_found_node(struct fw_card *card, } fw_node_event(card, node, FW_NODE_CREATED); + + /* Topology has changed - reset bus manager retry counter */ + card->bm_retries = 0; } void fw_destroy_nodes(struct fw_card *card) @@ -514,14 +520,6 @@ fw_core_handle_bus_reset(struct fw_card *card, spin_lock_irqsave(&card->lock, flags); - /* - * If the new topology has a different self_id_count the topology - * changed, either nodes were added or removed. In that case we - * reset the IRM reset counter. - */ - if (card->self_id_count != self_id_count) - card->bm_retries = 0; - card->node_id = node_id; /* * Update node_id before generation to prevent anybody from using @@ -530,7 +528,7 @@ fw_core_handle_bus_reset(struct fw_card *card, smp_wmb(); card->generation = generation; card->reset_jiffies = jiffies; - schedule_delayed_work(&card->work, 0); + fw_schedule_bm_work(card, 0); local_node = build_tree(card, self_ids, self_id_count); diff --git a/drivers/firewire/fw-transaction.c b/drivers/firewire/fw-transaction.c index 2884f876397b..699ac041f39a 100644 --- a/drivers/firewire/fw-transaction.c +++ b/drivers/firewire/fw-transaction.c @@ -19,6 +19,7 @@ */ #include <linux/completion.h> +#include <linux/idr.h> #include <linux/kernel.h> #include <linux/kref.h> #include <linux/module.h> @@ -971,6 +972,7 @@ static void __exit fw_core_cleanup(void) { unregister_chrdev(fw_cdev_major, "firewire"); bus_unregister(&fw_bus_type); + idr_destroy(&fw_device_idr); } module_init(fw_core_init); diff --git a/drivers/firewire/fw-transaction.h b/drivers/firewire/fw-transaction.h index 839466f0a795..c9ab12a15f6e 100644 --- a/drivers/firewire/fw-transaction.h +++ b/drivers/firewire/fw-transaction.h @@ -237,14 +237,6 @@ struct fw_card { int link_speed; int config_rom_generation; - /* - * We need to store up to 4 self ID for a maximum of 63 - * devices plus 3 words for the topology map header. - */ - int self_id_count; - u32 topology_map[252 + 3]; - u32 broadcast_channel; - spinlock_t lock; /* Take this lock when handling the lists in * this struct. */ struct fw_node *local_node; @@ -262,6 +254,9 @@ struct fw_card { struct delayed_work work; int bm_retries; int bm_generation; + + u32 broadcast_channel; + u32 topology_map[(CSR_TOPOLOGY_MAP_END - CSR_TOPOLOGY_MAP) / 4]; }; static inline struct fw_card *fw_card_get(struct fw_card *card) @@ -278,6 +273,8 @@ static inline void fw_card_put(struct fw_card *card) kref_put(&card->kref, fw_card_release); } +extern void fw_schedule_bm_work(struct fw_card *card, unsigned long delay); + /* * The iso packet format allows for an immediate header/payload part * stored in 'header' immediately after the packet info plus an diff --git a/drivers/hid/usbhid/hid-core.c b/drivers/hid/usbhid/hid-core.c index 03cb494af1c5..f0a0f72238ab 100644 --- a/drivers/hid/usbhid/hid-core.c +++ b/drivers/hid/usbhid/hid-core.c @@ -102,7 +102,7 @@ static void hid_reset(struct work_struct *work) struct usbhid_device *usbhid = container_of(work, struct usbhid_device, reset_work); struct hid_device *hid = usbhid->hid; - int rc_lock, rc = 0; + int rc = 0; if (test_bit(HID_CLEAR_HALT, &usbhid->iofl)) { dev_dbg(&usbhid->intf->dev, "clear halt\n"); @@ -113,11 +113,10 @@ static void hid_reset(struct work_struct *work) else if (test_bit(HID_RESET_PENDING, &usbhid->iofl)) { dev_dbg(&usbhid->intf->dev, "resetting device\n"); - rc = rc_lock = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf); - if (rc_lock >= 0) { + rc = usb_lock_device_for_reset(hid_to_usb_dev(hid), usbhid->intf); + if (rc == 0) { rc = usb_reset_device(hid_to_usb_dev(hid)); - if (rc_lock) - usb_unlock_device(hid_to_usb_dev(hid)); + usb_unlock_device(hid_to_usb_dev(hid)); } clear_bit(HID_RESET_PENDING, &usbhid->iofl); } diff --git a/drivers/hid/usbhid/hiddev.c b/drivers/hid/usbhid/hiddev.c index 6a98f9f572b0..d73eea382ab3 100644 --- a/drivers/hid/usbhid/hiddev.c +++ b/drivers/hid/usbhid/hiddev.c @@ -874,12 +874,14 @@ int hiddev_connect(struct hid_device *hid, unsigned int force) INIT_LIST_HEAD(&hiddev->list); spin_lock_init(&hiddev->list_lock); mutex_init(&hiddev->existancelock); + hid->hiddev = hiddev; hiddev->hid = hid; hiddev->exist = 1; retval = usb_register_dev(usbhid->intf, &hiddev_class); if (retval) { err_hid("Not able to get a minor for this device."); + hid->hiddev = NULL; kfree(hiddev); return -1; } else { diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index c709e821f04b..4b33bc82cc24 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -284,11 +284,12 @@ config SENSORS_F71805F will be called f71805f. config SENSORS_F71882FG - tristate "Fintek F71882FG and F71883FG" + tristate "Fintek F71862FG, F71882FG and F8000" depends on EXPERIMENTAL help If you say yes here you get support for hardware monitoring - features of the Fintek F71882FG and F71883FG Super-I/O chips. + features of the Fintek F71882FG/F71883FG, F71862FG/71863FG + and F8000 Super-I/O chips. This driver can also be built as a module. If so, the module will be called f71882fg. @@ -304,9 +305,13 @@ config SENSORS_F75375S will be called f75375s. config SENSORS_FSCHER - tristate "FSC Hermes" + tristate "FSC Hermes (DEPRECATED)" depends on X86 && I2C help + This driver is DEPRECATED please use the new merged fschmd + ("FSC Poseidon, Scylla, Hermes, Heimdall and Heracles") driver + instead. + If you say yes here you get support for Fujitsu Siemens Computers Hermes sensor chips. @@ -314,9 +319,13 @@ config SENSORS_FSCHER will be called fscher. config SENSORS_FSCPOS - tristate "FSC Poseidon" + tristate "FSC Poseidon (DEPRECATED)" depends on X86 && I2C help + This driver is DEPRECATED please use the new merged fschmd + ("FSC Poseidon, Scylla, Hermes, Heimdall and Heracles") driver + instead. + If you say yes here you get support for Fujitsu Siemens Computers Poseidon sensor chips. @@ -325,14 +334,15 @@ config SENSORS_FSCPOS config SENSORS_FSCHMD tristate "FSC Poseidon, Scylla, Hermes, Heimdall and Heracles" - depends on X86 && I2C && EXPERIMENTAL + depends on X86 && I2C help If you say yes here you get support for various Fujitsu Siemens - Computers sensor chips. + Computers sensor chips, including support for the integrated + watchdog. - This is a new merged driver for FSC sensor chips which is intended - as a replacment for the fscpos, fscscy and fscher drivers and adds - support for several other FCS sensor chips. + This is a merged driver for FSC sensor chips replacing the fscpos, + fscscy and fscher drivers and adding support for several other FSC + sensor chips. This driver can also be built as a module. If so, the module will be called fschmd. @@ -399,7 +409,8 @@ config SENSORS_IT87 select HWMON_VID help If you say yes here you get support for ITE IT8705F, IT8712F, - IT8716F, IT8718F and IT8726F sensor chips, and the SiS960 clone. + IT8716F, IT8718F, IT8720F and IT8726F sensor chips, and the + SiS960 clone. This driver can also be built as a module. If so, the module will be called it87. @@ -417,11 +428,12 @@ config SENSORS_LM63 will be called lm63. config SENSORS_LM70 - tristate "National Semiconductor LM70" + tristate "National Semiconductor LM70 / Texas Instruments TMP121" depends on SPI_MASTER && EXPERIMENTAL help If you say yes here you get support for the National Semiconductor - LM70 digital temperature sensor chip. + LM70 and Texas Instruments TMP121/TMP123 digital temperature + sensor chips. This driver can also be built as a module. If so, the module will be called lm70. @@ -548,6 +560,17 @@ config SENSORS_LM93 This driver can also be built as a module. If so, the module will be called lm93. +config SENSORS_LTC4245 + tristate "Linear Technology LTC4245" + depends on I2C && EXPERIMENTAL + default n + help + If you say yes here you get support for Linear Technology LTC4245 + Multiple Supply Hot Swap Controller I2C interface. + + This driver can also be built as a module. If so, the module will + be called ltc4245. + config SENSORS_MAX1111 tristate "Maxim MAX1111 Multichannel, Serial 8-bit ADC chip" depends on SPI_MASTER diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 58fc5be5355d..8fd124eff646 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -62,6 +62,7 @@ obj-$(CONFIG_SENSORS_LM87) += lm87.o obj-$(CONFIG_SENSORS_LM90) += lm90.o obj-$(CONFIG_SENSORS_LM92) += lm92.o obj-$(CONFIG_SENSORS_LM93) += lm93.o +obj-$(CONFIG_SENSORS_LTC4245) += ltc4245.o obj-$(CONFIG_SENSORS_MAX1111) += max1111.o obj-$(CONFIG_SENSORS_MAX1619) += max1619.o obj-$(CONFIG_SENSORS_MAX6650) += max6650.o diff --git a/drivers/hwmon/asb100.c b/drivers/hwmon/asb100.c index 8a45a2e6ba8a..8acf82977e7b 100644 --- a/drivers/hwmon/asb100.c +++ b/drivers/hwmon/asb100.c @@ -53,7 +53,10 @@ static const unsigned short normal_i2c[] = { 0x2d, I2C_CLIENT_END }; /* Insmod parameters */ I2C_CLIENT_INSMOD_1(asb100); -I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: " + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, "List of subclient addresses: " "{bus, clientaddr, subclientaddr1, subclientaddr2}"); /* Voltage IN registers 0-6 */ diff --git a/drivers/hwmon/dme1737.c b/drivers/hwmon/dme1737.c index 27a5d397f9a1..3df202a9ad72 100644 --- a/drivers/hwmon/dme1737.c +++ b/drivers/hwmon/dme1737.c @@ -34,6 +34,7 @@ #include <linux/hwmon-vid.h> #include <linux/err.h> #include <linux/mutex.h> +#include <linux/acpi.h> #include <asm/io.h> /* ISA device, if found */ @@ -2361,6 +2362,10 @@ static int __init dme1737_isa_device_add(unsigned short addr) }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + if (!(pdev = platform_device_alloc("dme1737", addr))) { printk(KERN_ERR "dme1737: Failed to allocate device.\n"); err = -ENOMEM; diff --git a/drivers/hwmon/f71805f.c b/drivers/hwmon/f71805f.c index 7a14a2dbb752..899876579253 100644 --- a/drivers/hwmon/f71805f.c +++ b/drivers/hwmon/f71805f.c @@ -39,6 +39,7 @@ #include <linux/mutex.h> #include <linux/sysfs.h> #include <linux/ioport.h> +#include <linux/acpi.h> #include <asm/io.h> static unsigned short force_id; @@ -1455,6 +1456,10 @@ static int __init f71805f_device_add(unsigned short address, } res.name = pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + err = platform_device_add_resources(pdev, &res, 1); if (err) { printk(KERN_ERR DRVNAME ": Device resource addition failed " diff --git a/drivers/hwmon/f71882fg.c b/drivers/hwmon/f71882fg.c index 67067e9a323e..609cafff86bc 100644 --- a/drivers/hwmon/f71882fg.c +++ b/drivers/hwmon/f71882fg.c @@ -1,6 +1,6 @@ /*************************************************************************** * Copyright (C) 2006 by Hans Edgington <hans@edgington.nl> * - * Copyright (C) 2007 by Hans de Goede <j.w.r.degoede@hhs.nl> * + * Copyright (C) 2007,2008 by Hans de Goede <hdegoede@redhat.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 * @@ -27,11 +27,12 @@ #include <linux/hwmon-sysfs.h> #include <linux/err.h> #include <linux/mutex.h> -#include <asm/io.h> +#include <linux/io.h> +#include <linux/acpi.h> #define DRVNAME "f71882fg" -#define SIO_F71882FG_LD_HWM 0x04 /* Hardware monitor logical device*/ +#define SIO_F71882FG_LD_HWM 0x04 /* Hardware monitor logical device */ #define SIO_UNLOCK_KEY 0x87 /* Key to enable Super-I/O */ #define SIO_LOCK_KEY 0xAA /* Key to diasble Super-I/O */ @@ -43,7 +44,9 @@ #define SIO_REG_ADDR 0x60 /* Logical device address (2 bytes) */ #define SIO_FINTEK_ID 0x1934 /* Manufacturers ID */ +#define SIO_F71862_ID 0x0601 /* Chipset ID */ #define SIO_F71882_ID 0x0541 /* Chipset ID */ +#define SIO_F8000_ID 0x0581 /* Chipset ID */ #define REGION_LENGTH 8 #define ADDR_REG_OFFSET 5 @@ -51,25 +54,36 @@ #define F71882FG_REG_PECI 0x0A -#define F71882FG_REG_IN_STATUS 0x12 -#define F71882FG_REG_IN_BEEP 0x13 +#define F71882FG_REG_IN_STATUS 0x12 /* f71882fg only */ +#define F71882FG_REG_IN_BEEP 0x13 /* f71882fg only */ #define F71882FG_REG_IN(nr) (0x20 + (nr)) -#define F71882FG_REG_IN1_HIGH 0x32 +#define F71882FG_REG_IN1_HIGH 0x32 /* f71882fg only */ #define F71882FG_REG_FAN(nr) (0xA0 + (16 * (nr))) +#define F71882FG_REG_FAN_TARGET(nr) (0xA2 + (16 * (nr))) +#define F71882FG_REG_FAN_FULL_SPEED(nr) (0xA4 + (16 * (nr))) #define F71882FG_REG_FAN_STATUS 0x92 #define F71882FG_REG_FAN_BEEP 0x93 -#define F71882FG_REG_TEMP(nr) (0x72 + 2 * (nr)) -#define F71882FG_REG_TEMP_OVT(nr) (0x82 + 2 * (nr)) -#define F71882FG_REG_TEMP_HIGH(nr) (0x83 + 2 * (nr)) +#define F71882FG_REG_TEMP(nr) (0x70 + 2 * (nr)) +#define F71882FG_REG_TEMP_OVT(nr) (0x80 + 2 * (nr)) +#define F71882FG_REG_TEMP_HIGH(nr) (0x81 + 2 * (nr)) #define F71882FG_REG_TEMP_STATUS 0x62 #define F71882FG_REG_TEMP_BEEP 0x63 -#define F71882FG_REG_TEMP_HYST1 0x6C -#define F71882FG_REG_TEMP_HYST23 0x6D +#define F71882FG_REG_TEMP_HYST(nr) (0x6C + (nr)) #define F71882FG_REG_TEMP_TYPE 0x6B #define F71882FG_REG_TEMP_DIODE_OPEN 0x6F +#define F71882FG_REG_PWM(nr) (0xA3 + (16 * (nr))) +#define F71882FG_REG_PWM_TYPE 0x94 +#define F71882FG_REG_PWM_ENABLE 0x96 + +#define F71882FG_REG_FAN_HYST(nr) (0x98 + (nr)) + +#define F71882FG_REG_POINT_PWM(pwm, point) (0xAA + (point) + (16 * (pwm))) +#define F71882FG_REG_POINT_TEMP(pwm, point) (0xA6 + (point) + (16 * (pwm))) +#define F71882FG_REG_POINT_MAPPING(nr) (0xAF + 16 * (nr)) + #define F71882FG_REG_START 0x01 #define FAN_MIN_DETECT 366 /* Lowest detectable fanspeed */ @@ -78,7 +92,15 @@ static unsigned short force_id; module_param(force_id, ushort, 0); MODULE_PARM_DESC(force_id, "Override the detected device ID"); -static struct platform_device *f71882fg_pdev = NULL; +enum chips { f71862fg, f71882fg, f8000 }; + +static const char *f71882fg_names[] = { + "f71862fg", + "f71882fg", + "f8000", +}; + +static struct platform_device *f71882fg_pdev; /* Super-I/O Function prototypes */ static inline int superio_inb(int base, int reg); @@ -87,8 +109,13 @@ static inline void superio_enter(int base); static inline void superio_select(int base, int ld); static inline void superio_exit(int base); +struct f71882fg_sio_data { + enum chips type; +}; + struct f71882fg_data { unsigned short addr; + enum chips type; struct device *hwmon_dev; struct mutex update_lock; @@ -102,19 +129,30 @@ struct f71882fg_data { u8 in_status; u8 in_beep; u16 fan[4]; + u16 fan_target[4]; + u16 fan_full_speed[4]; u8 fan_status; u8 fan_beep; - u8 temp[3]; - u8 temp_ovt[3]; - u8 temp_high[3]; - u8 temp_hyst[3]; - u8 temp_type[3]; + /* Note: all models have only 3 temperature channels, but on some + they are addressed as 0-2 and on others as 1-3, so for coding + convenience we reserve space for 4 channels */ + u8 temp[4]; + u8 temp_ovt[4]; + u8 temp_high[4]; + u8 temp_hyst[2]; /* 2 hysts stored per reg */ + u8 temp_type[4]; u8 temp_status; u8 temp_beep; u8 temp_diode_open; + u8 pwm[4]; + u8 pwm_enable; + u8 pwm_auto_point_hyst[2]; + u8 pwm_auto_point_mapping[4]; + u8 pwm_auto_point_pwm[4][5]; + u8 pwm_auto_point_temp[4][4]; }; -/* Sysfs in*/ +/* Sysfs in */ static ssize_t show_in(struct device *dev, struct device_attribute *devattr, char *buf); static ssize_t show_in_max(struct device *dev, struct device_attribute @@ -130,6 +168,10 @@ static ssize_t show_in_alarm(struct device *dev, struct device_attribute /* Sysfs Fan */ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, char *buf); +static ssize_t show_fan_full_speed(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_fan_full_speed(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); static ssize_t show_fan_beep(struct device *dev, struct device_attribute *devattr, char *buf); static ssize_t store_fan_beep(struct device *dev, struct device_attribute @@ -163,16 +205,41 @@ static ssize_t show_temp_alarm(struct device *dev, struct device_attribute *devattr, char *buf); static ssize_t show_temp_fault(struct device *dev, struct device_attribute *devattr, char *buf); +/* PWM and Auto point control */ +static ssize_t show_pwm(struct device *dev, struct device_attribute *devattr, + char *buf); +static ssize_t store_pwm(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count); +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); +static ssize_t show_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, char *buf); +static ssize_t store_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, const char *buf, size_t count); /* Sysfs misc */ static ssize_t show_name(struct device *dev, struct device_attribute *devattr, char *buf); static int __devinit f71882fg_probe(struct platform_device * pdev); -static int __devexit f71882fg_remove(struct platform_device *pdev); -static int __init f71882fg_init(void); -static int __init f71882fg_find(int sioaddr, unsigned short *address); -static int __init f71882fg_device_add(unsigned short address); -static void __exit f71882fg_exit(void); +static int f71882fg_remove(struct platform_device *pdev); static struct platform_driver f71882fg_driver = { .driver = { @@ -183,86 +250,531 @@ static struct platform_driver f71882fg_driver = { .remove = __devexit_p(f71882fg_remove), }; -static struct device_attribute f71882fg_dev_attr[] = -{ - __ATTR( name, S_IRUGO, show_name, NULL ), +static DEVICE_ATTR(name, S_IRUGO, show_name, NULL); + +/* Temp and in attr common to both the f71862fg and f71882fg */ +static struct sensor_device_attribute_2 f718x2fg_in_temp_attr[] = { + SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), + SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), + SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), + SENSOR_ATTR_2(in3_input, S_IRUGO, show_in, NULL, 0, 3), + SENSOR_ATTR_2(in4_input, S_IRUGO, show_in, NULL, 0, 4), + SENSOR_ATTR_2(in5_input, S_IRUGO, show_in, NULL, 0, 5), + SENSOR_ATTR_2(in6_input, S_IRUGO, show_in, NULL, 0, 6), + SENSOR_ATTR_2(in7_input, S_IRUGO, show_in, NULL, 0, 7), + SENSOR_ATTR_2(in8_input, S_IRUGO, show_in, NULL, 0, 8), + SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 1), + SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 1), + SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 1), + /* Should really be temp1_max_alarm, but older versions did not handle + the max and crit alarms separately and lm_sensors v2 depends on the + presence of temp#_alarm files. The same goes for temp2/3 _alarm. */ + SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 1), + SENSOR_ATTR_2(temp1_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 1), + SENSOR_ATTR_2(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 1), + SENSOR_ATTR_2(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 1), + SENSOR_ATTR_2(temp1_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), + SENSOR_ATTR_2(temp1_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 5), + SENSOR_ATTR_2(temp1_type, S_IRUGO, show_temp_type, NULL, 0, 1), + SENSOR_ATTR_2(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0, 1), + SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 2), + SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 2), + SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 2), + /* Should be temp2_max_alarm, see temp1_alarm note */ + SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 2), + SENSOR_ATTR_2(temp2_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 2), + SENSOR_ATTR_2(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 2), + SENSOR_ATTR_2(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 2), + SENSOR_ATTR_2(temp2_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6), + SENSOR_ATTR_2(temp2_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 6), + SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 2), + SENSOR_ATTR_2(temp2_fault, S_IRUGO, show_temp_fault, NULL, 0, 2), + SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 3), + SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 3), + SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, + store_temp_max_hyst, 0, 3), + /* Should be temp3_max_alarm, see temp1_alarm note */ + SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 3), + SENSOR_ATTR_2(temp3_max_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 3), + SENSOR_ATTR_2(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 3), + SENSOR_ATTR_2(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, + 0, 3), + SENSOR_ATTR_2(temp3_crit_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 7), + SENSOR_ATTR_2(temp3_crit_beep, S_IRUGO|S_IWUSR, show_temp_beep, + store_temp_beep, 0, 7), + SENSOR_ATTR_2(temp3_type, S_IRUGO, show_temp_type, NULL, 0, 3), + SENSOR_ATTR_2(temp3_fault, S_IRUGO, show_temp_fault, NULL, 0, 3), +}; + +/* Temp and in attr found only on the f71882fg */ +static struct sensor_device_attribute_2 f71882fg_in_temp_attr[] = { + SENSOR_ATTR_2(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, + 0, 1), + SENSOR_ATTR_2(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, + 0, 1), + SENSOR_ATTR_2(in1_alarm, S_IRUGO, show_in_alarm, NULL, 0, 1), +}; + +/* Temp and in attr for the f8000 + Note on the f8000 temp_ovt (crit) is used as max, and temp_high (max) + is used as hysteresis value to clear alarms + */ +static struct sensor_device_attribute_2 f8000_in_temp_attr[] = { + SENSOR_ATTR_2(in0_input, S_IRUGO, show_in, NULL, 0, 0), + SENSOR_ATTR_2(in1_input, S_IRUGO, show_in, NULL, 0, 1), + SENSOR_ATTR_2(in2_input, S_IRUGO, show_in, NULL, 0, 2), + SENSOR_ATTR_2(temp1_input, S_IRUGO, show_temp, NULL, 0, 0), + SENSOR_ATTR_2(temp1_max, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 0), + SENSOR_ATTR_2(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 0), + SENSOR_ATTR_2(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 4), + SENSOR_ATTR_2(temp2_input, S_IRUGO, show_temp, NULL, 0, 1), + SENSOR_ATTR_2(temp2_max, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 1), + SENSOR_ATTR_2(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 1), + SENSOR_ATTR_2(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 5), + SENSOR_ATTR_2(temp2_type, S_IRUGO, show_temp_type, NULL, 0, 1), + SENSOR_ATTR_2(temp3_input, S_IRUGO, show_temp, NULL, 0, 2), + SENSOR_ATTR_2(temp3_max, S_IRUGO|S_IWUSR, show_temp_crit, + store_temp_crit, 0, 2), + SENSOR_ATTR_2(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max, + store_temp_max, 0, 2), + SENSOR_ATTR_2(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 0, 6), +}; + +/* Fan / PWM attr common to all models */ +static struct sensor_device_attribute_2 fxxxx_fan_attr[] = { + SENSOR_ATTR_2(fan1_input, S_IRUGO, show_fan, NULL, 0, 0), + SENSOR_ATTR_2(fan1_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 0), + SENSOR_ATTR_2(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 0), + SENSOR_ATTR_2(fan2_input, S_IRUGO, show_fan, NULL, 0, 1), + SENSOR_ATTR_2(fan2_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 1), + SENSOR_ATTR_2(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 1), + SENSOR_ATTR_2(fan3_input, S_IRUGO, show_fan, NULL, 0, 2), + SENSOR_ATTR_2(fan3_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 2), + SENSOR_ATTR_2(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 2), + + SENSOR_ATTR_2(pwm1, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 0), + SENSOR_ATTR_2(pwm1_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 0), + SENSOR_ATTR_2(pwm1_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 0), + SENSOR_ATTR_2(pwm1_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 0), + + SENSOR_ATTR_2(pwm2, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 1), + SENSOR_ATTR_2(pwm2_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 1), + SENSOR_ATTR_2(pwm2_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 1), + SENSOR_ATTR_2(pwm2_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 1), + + SENSOR_ATTR_2(pwm3_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 2), + SENSOR_ATTR_2(pwm3_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 2), }; -static struct sensor_device_attribute f71882fg_in_temp_attr[] = -{ - SENSOR_ATTR(in0_input, S_IRUGO, show_in, NULL, 0), - SENSOR_ATTR(in1_input, S_IRUGO, show_in, NULL, 1), - SENSOR_ATTR(in1_max, S_IRUGO|S_IWUSR, show_in_max, store_in_max, 1), - SENSOR_ATTR(in1_beep, S_IRUGO|S_IWUSR, show_in_beep, store_in_beep, 1), - SENSOR_ATTR(in1_alarm, S_IRUGO, show_in_alarm, NULL, 1), - SENSOR_ATTR(in2_input, S_IRUGO, show_in, NULL, 2), - SENSOR_ATTR(in3_input, S_IRUGO, show_in, NULL, 3), - SENSOR_ATTR(in4_input, S_IRUGO, show_in, NULL, 4), - SENSOR_ATTR(in5_input, S_IRUGO, show_in, NULL, 5), - SENSOR_ATTR(in6_input, S_IRUGO, show_in, NULL, 6), - SENSOR_ATTR(in7_input, S_IRUGO, show_in, NULL, 7), - SENSOR_ATTR(in8_input, S_IRUGO, show_in, NULL, 8), - SENSOR_ATTR(temp1_input, S_IRUGO, show_temp, NULL, 0), - SENSOR_ATTR(temp1_max, S_IRUGO|S_IWUSR, show_temp_max, - store_temp_max, 0), - SENSOR_ATTR(temp1_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, - store_temp_max_hyst, 0), - SENSOR_ATTR(temp1_crit, S_IRUGO|S_IWUSR, show_temp_crit, - store_temp_crit, 0), - SENSOR_ATTR(temp1_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 0), - SENSOR_ATTR(temp1_type, S_IRUGO, show_temp_type, NULL, 0), - SENSOR_ATTR(temp1_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 0), - SENSOR_ATTR(temp1_alarm, S_IRUGO, show_temp_alarm, NULL, 0), - SENSOR_ATTR(temp1_fault, S_IRUGO, show_temp_fault, NULL, 0), - SENSOR_ATTR(temp2_input, S_IRUGO, show_temp, NULL, 1), - SENSOR_ATTR(temp2_max, S_IRUGO|S_IWUSR, show_temp_max, - store_temp_max, 1), - SENSOR_ATTR(temp2_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, - store_temp_max_hyst, 1), - SENSOR_ATTR(temp2_crit, S_IRUGO|S_IWUSR, show_temp_crit, - store_temp_crit, 1), - SENSOR_ATTR(temp2_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 1), - SENSOR_ATTR(temp2_type, S_IRUGO, show_temp_type, NULL, 1), - SENSOR_ATTR(temp2_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 1), - SENSOR_ATTR(temp2_alarm, S_IRUGO, show_temp_alarm, NULL, 1), - SENSOR_ATTR(temp2_fault, S_IRUGO, show_temp_fault, NULL, 1), - SENSOR_ATTR(temp3_input, S_IRUGO, show_temp, NULL, 2), - SENSOR_ATTR(temp3_max, S_IRUGO|S_IWUSR, show_temp_max, - store_temp_max, 2), - SENSOR_ATTR(temp3_max_hyst, S_IRUGO|S_IWUSR, show_temp_max_hyst, - store_temp_max_hyst, 2), - SENSOR_ATTR(temp3_crit, S_IRUGO|S_IWUSR, show_temp_crit, - store_temp_crit, 2), - SENSOR_ATTR(temp3_crit_hyst, S_IRUGO, show_temp_crit_hyst, NULL, 2), - SENSOR_ATTR(temp3_type, S_IRUGO, show_temp_type, NULL, 2), - SENSOR_ATTR(temp3_beep, S_IRUGO|S_IWUSR, show_temp_beep, - store_temp_beep, 2), - SENSOR_ATTR(temp3_alarm, S_IRUGO, show_temp_alarm, NULL, 2), - SENSOR_ATTR(temp3_fault, S_IRUGO, show_temp_fault, NULL, 2) +/* Fan / PWM attr for the f71862fg, less pwms and less zones per pwm than the + f71882fg */ +static struct sensor_device_attribute_2 f71862fg_fan_attr[] = { + SENSOR_ATTR_2(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 0), + SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 1), + SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 2), + + SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), + + SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), + + SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 2), + SENSOR_ATTR_2(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 2), + SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), }; -static struct sensor_device_attribute f71882fg_fan_attr[] = -{ - SENSOR_ATTR(fan1_input, S_IRUGO, show_fan, NULL, 0), - SENSOR_ATTR(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep, - store_fan_beep, 0), - SENSOR_ATTR(fan1_alarm, S_IRUGO, show_fan_alarm, NULL, 0), - SENSOR_ATTR(fan2_input, S_IRUGO, show_fan, NULL, 1), - SENSOR_ATTR(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep, - store_fan_beep, 1), - SENSOR_ATTR(fan2_alarm, S_IRUGO, show_fan_alarm, NULL, 1), - SENSOR_ATTR(fan3_input, S_IRUGO, show_fan, NULL, 2), - SENSOR_ATTR(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep, - store_fan_beep, 2), - SENSOR_ATTR(fan3_alarm, S_IRUGO, show_fan_alarm, NULL, 2), - SENSOR_ATTR(fan4_input, S_IRUGO, show_fan, NULL, 3), - SENSOR_ATTR(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep, - store_fan_beep, 3), - SENSOR_ATTR(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 3) +/* Fan / PWM attr for the f71882fg */ +static struct sensor_device_attribute_2 f71882fg_fan_attr[] = { + SENSOR_ATTR_2(fan1_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 0), + SENSOR_ATTR_2(fan2_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 1), + SENSOR_ATTR_2(fan3_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 2), + SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 0, 3), + SENSOR_ATTR_2(fan4_full_speed, S_IRUGO|S_IWUSR, + show_fan_full_speed, + store_fan_full_speed, 0, 3), + SENSOR_ATTR_2(fan4_beep, S_IRUGO|S_IWUSR, show_fan_beep, + store_fan_beep, 0, 3), + SENSOR_ATTR_2(fan4_alarm, S_IRUGO, show_fan_alarm, NULL, 0, 3), + + SENSOR_ATTR_2(pwm1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 0), + SENSOR_ATTR_2(pwm1_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 0), + SENSOR_ATTR_2(pwm1_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(pwm1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(pwm1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 0), + SENSOR_ATTR_2(pwm1_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 0), + SENSOR_ATTR_2(pwm1_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), + + SENSOR_ATTR_2(pwm2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 1), + SENSOR_ATTR_2(pwm2_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 1), + SENSOR_ATTR_2(pwm2_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(pwm2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(pwm2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 1), + SENSOR_ATTR_2(pwm2_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 1), + SENSOR_ATTR_2(pwm2_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), + + SENSOR_ATTR_2(pwm3, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 2), + SENSOR_ATTR_2(pwm3_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 2), + SENSOR_ATTR_2(pwm3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 2), + SENSOR_ATTR_2(pwm3_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 2), + SENSOR_ATTR_2(pwm3_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(pwm3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(pwm3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 2), + SENSOR_ATTR_2(pwm3_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 2), + SENSOR_ATTR_2(pwm3_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), + + SENSOR_ATTR_2(pwm4, S_IRUGO|S_IWUSR, show_pwm, store_pwm, 0, 3), + SENSOR_ATTR_2(pwm4_enable, S_IRUGO|S_IWUSR, show_pwm_enable, + store_pwm_enable, 0, 3), + SENSOR_ATTR_2(pwm4_interpolate, S_IRUGO|S_IWUSR, + show_pwm_interpolate, store_pwm_interpolate, 0, 3), + SENSOR_ATTR_2(pwm4_auto_channels_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_channel, + store_pwm_auto_point_channel, 0, 3), + SENSOR_ATTR_2(pwm4_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 3), + SENSOR_ATTR_2(pwm4_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 3), + SENSOR_ATTR_2(pwm4_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 3), + SENSOR_ATTR_2(pwm4_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 3), + SENSOR_ATTR_2(pwm4_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 3), + SENSOR_ATTR_2(pwm4_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 3), + SENSOR_ATTR_2(pwm4_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 3), + SENSOR_ATTR_2(pwm4_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 3), + SENSOR_ATTR_2(pwm4_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 3), + SENSOR_ATTR_2(pwm4_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 3), + SENSOR_ATTR_2(pwm4_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 3), + SENSOR_ATTR_2(pwm4_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 3), + SENSOR_ATTR_2(pwm4_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 3), }; +/* Fan / PWM attr for the f8000, zones mapped to temp instead of to pwm! + Also the register block at offset A0 maps to TEMP1 (so our temp2, as the + F8000 starts counting temps at 0), B0 maps the TEMP2 and C0 maps to TEMP0 */ +static struct sensor_device_attribute_2 f8000_fan_attr[] = { + SENSOR_ATTR_2(fan4_input, S_IRUGO, show_fan, NULL, 0, 3), + + SENSOR_ATTR_2(pwm3, S_IRUGO, show_pwm, NULL, 0, 2), + + SENSOR_ATTR_2(temp1_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 2), + SENSOR_ATTR_2(temp1_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 2), + SENSOR_ATTR_2(temp1_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 2), + SENSOR_ATTR_2(temp1_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 2), + SENSOR_ATTR_2(temp1_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 2), + SENSOR_ATTR_2(temp1_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 2), + SENSOR_ATTR_2(temp1_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 2), + SENSOR_ATTR_2(temp1_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 2), + SENSOR_ATTR_2(temp1_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 2), + SENSOR_ATTR_2(temp1_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 2), + SENSOR_ATTR_2(temp1_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 2), + SENSOR_ATTR_2(temp1_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 2), + SENSOR_ATTR_2(temp1_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 2), + + SENSOR_ATTR_2(temp2_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 0), + SENSOR_ATTR_2(temp2_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 0), + SENSOR_ATTR_2(temp2_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 0), + SENSOR_ATTR_2(temp2_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 0), + SENSOR_ATTR_2(temp2_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 0), + SENSOR_ATTR_2(temp2_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 0), + SENSOR_ATTR_2(temp2_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 0), + SENSOR_ATTR_2(temp2_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 0), + SENSOR_ATTR_2(temp2_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 0), + SENSOR_ATTR_2(temp2_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 0), + SENSOR_ATTR_2(temp2_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 0), + SENSOR_ATTR_2(temp2_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 0), + SENSOR_ATTR_2(temp2_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 0), + + SENSOR_ATTR_2(temp3_auto_point1_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 0, 1), + SENSOR_ATTR_2(temp3_auto_point2_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 1, 1), + SENSOR_ATTR_2(temp3_auto_point3_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 2, 1), + SENSOR_ATTR_2(temp3_auto_point4_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 3, 1), + SENSOR_ATTR_2(temp3_auto_point5_pwm, S_IRUGO|S_IWUSR, + show_pwm_auto_point_pwm, store_pwm_auto_point_pwm, + 4, 1), + SENSOR_ATTR_2(temp3_auto_point1_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 0, 1), + SENSOR_ATTR_2(temp3_auto_point2_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 1, 1), + SENSOR_ATTR_2(temp3_auto_point3_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 2, 1), + SENSOR_ATTR_2(temp3_auto_point4_temp, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp, store_pwm_auto_point_temp, + 3, 1), + SENSOR_ATTR_2(temp3_auto_point1_temp_hyst, S_IRUGO|S_IWUSR, + show_pwm_auto_point_temp_hyst, + store_pwm_auto_point_temp_hyst, + 0, 1), + SENSOR_ATTR_2(temp3_auto_point2_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 1, 1), + SENSOR_ATTR_2(temp3_auto_point3_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 2, 1), + SENSOR_ATTR_2(temp3_auto_point4_temp_hyst, S_IRUGO, + show_pwm_auto_point_temp_hyst, NULL, 3, 1), +}; /* Super I/O functions */ static inline int superio_inb(int base, int reg) @@ -299,11 +811,16 @@ static inline void superio_exit(int base) outb(SIO_LOCK_KEY, base); } -static inline u16 fan_from_reg(u16 reg) +static inline int fan_from_reg(u16 reg) { return reg ? (1500000 / reg) : 0; } +static inline u16 fan_to_reg(int fan) +{ + return fan ? (1500000 / fan) : 0; +} + static u8 f71882fg_read8(struct f71882fg_data *data, u8 reg) { u8 val; @@ -332,52 +849,111 @@ static void f71882fg_write8(struct f71882fg_data *data, u8 reg, u8 val) outb(val, data->addr + DATA_REG_OFFSET); } -static struct f71882fg_data *f71882fg_update_device(struct device * dev) +static void f71882fg_write16(struct f71882fg_data *data, u8 reg, u16 val) +{ + outb(reg++, data->addr + ADDR_REG_OFFSET); + outb(val >> 8, data->addr + DATA_REG_OFFSET); + outb(reg, data->addr + ADDR_REG_OFFSET); + outb(val & 255, data->addr + DATA_REG_OFFSET); +} + +static struct f71882fg_data *f71882fg_update_device(struct device *dev) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr, reg, reg2; + int nr, reg = 0, reg2; + int nr_fans = (data->type == f71882fg) ? 4 : 3; + int nr_ins = (data->type == f8000) ? 3 : 9; + int temp_start = (data->type == f8000) ? 0 : 1; mutex_lock(&data->update_lock); /* Update once every 60 seconds */ if ( time_after(jiffies, data->last_limits + 60 * HZ ) || !data->valid) { - data->in1_max = f71882fg_read8(data, F71882FG_REG_IN1_HIGH); - data->in_beep = f71882fg_read8(data, F71882FG_REG_IN_BEEP); + if (data->type == f71882fg) { + data->in1_max = + f71882fg_read8(data, F71882FG_REG_IN1_HIGH); + data->in_beep = + f71882fg_read8(data, F71882FG_REG_IN_BEEP); + } /* Get High & boundary temps*/ - for (nr = 0; nr < 3; nr++) { + for (nr = temp_start; nr < 3 + temp_start; nr++) { data->temp_ovt[nr] = f71882fg_read8(data, F71882FG_REG_TEMP_OVT(nr)); data->temp_high[nr] = f71882fg_read8(data, F71882FG_REG_TEMP_HIGH(nr)); } - /* Have to hardcode hyst*/ - data->temp_hyst[0] = f71882fg_read8(data, - F71882FG_REG_TEMP_HYST1) >> 4; - /* Hyst temps 2 & 3 stored in same register */ - reg = f71882fg_read8(data, F71882FG_REG_TEMP_HYST23); - data->temp_hyst[1] = reg & 0x0F; - data->temp_hyst[2] = reg >> 4; - - /* Have to hardcode type, because temp1 is special */ - reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); + if (data->type != f8000) { + data->fan_beep = f71882fg_read8(data, + F71882FG_REG_FAN_BEEP); + data->temp_beep = f71882fg_read8(data, + F71882FG_REG_TEMP_BEEP); + data->temp_hyst[0] = f71882fg_read8(data, + F71882FG_REG_TEMP_HYST(0)); + data->temp_hyst[1] = f71882fg_read8(data, + F71882FG_REG_TEMP_HYST(1)); + /* Have to hardcode type, because temp1 is special */ + reg = f71882fg_read8(data, F71882FG_REG_TEMP_TYPE); + data->temp_type[2] = (reg & 0x04) ? 2 : 4; + data->temp_type[3] = (reg & 0x08) ? 2 : 4; + } reg2 = f71882fg_read8(data, F71882FG_REG_PECI); if ((reg2 & 0x03) == 0x01) - data->temp_type[0] = 6 /* PECI */; + data->temp_type[1] = 6 /* PECI */; else if ((reg2 & 0x03) == 0x02) - data->temp_type[0] = 5 /* AMDSI */; + data->temp_type[1] = 5 /* AMDSI */; + else if (data->type != f8000) + data->temp_type[1] = (reg & 0x02) ? 2 : 4; else - data->temp_type[0] = (reg & 0x02) ? 2 : 4; - - data->temp_type[1] = (reg & 0x04) ? 2 : 4; - data->temp_type[2] = (reg & 0x08) ? 2 : 4; - - data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP); - - data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP); - + data->temp_type[1] = 2; /* F8000 only supports BJT */ + + data->pwm_enable = f71882fg_read8(data, + F71882FG_REG_PWM_ENABLE); + data->pwm_auto_point_hyst[0] = + f71882fg_read8(data, F71882FG_REG_FAN_HYST(0)); + data->pwm_auto_point_hyst[1] = + f71882fg_read8(data, F71882FG_REG_FAN_HYST(1)); + + for (nr = 0; nr < nr_fans; nr++) { + data->pwm_auto_point_mapping[nr] = + f71882fg_read8(data, + F71882FG_REG_POINT_MAPPING(nr)); + + if (data->type != f71862fg) { + int point; + for (point = 0; point < 5; point++) { + data->pwm_auto_point_pwm[nr][point] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM + (nr, point)); + } + for (point = 0; point < 4; point++) { + data->pwm_auto_point_temp[nr][point] = + f71882fg_read8(data, + F71882FG_REG_POINT_TEMP + (nr, point)); + } + } else { + data->pwm_auto_point_pwm[nr][1] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM + (nr, 1)); + data->pwm_auto_point_pwm[nr][4] = + f71882fg_read8(data, + F71882FG_REG_POINT_PWM + (nr, 4)); + data->pwm_auto_point_temp[nr][0] = + f71882fg_read8(data, + F71882FG_REG_POINT_TEMP + (nr, 0)); + data->pwm_auto_point_temp[nr][3] = + f71882fg_read8(data, + F71882FG_REG_POINT_TEMP + (nr, 3)); + } + } data->last_limits = jiffies; } @@ -387,19 +963,32 @@ static struct f71882fg_data *f71882fg_update_device(struct device * dev) F71882FG_REG_TEMP_STATUS); data->temp_diode_open = f71882fg_read8(data, F71882FG_REG_TEMP_DIODE_OPEN); - for (nr = 0; nr < 3; nr++) + for (nr = temp_start; nr < 3 + temp_start; nr++) data->temp[nr] = f71882fg_read8(data, F71882FG_REG_TEMP(nr)); data->fan_status = f71882fg_read8(data, F71882FG_REG_FAN_STATUS); - for (nr = 0; nr < 4; nr++) + for (nr = 0; nr < nr_fans; nr++) { data->fan[nr] = f71882fg_read16(data, F71882FG_REG_FAN(nr)); + data->fan_target[nr] = + f71882fg_read16(data, F71882FG_REG_FAN_TARGET(nr)); + data->fan_full_speed[nr] = + f71882fg_read16(data, + F71882FG_REG_FAN_FULL_SPEED(nr)); + data->pwm[nr] = + f71882fg_read8(data, F71882FG_REG_PWM(nr)); + } - data->in_status = f71882fg_read8(data, + /* The f8000 can monitor 1 more fan, but has no pwm for it */ + if (data->type == f8000) + data->fan[3] = f71882fg_read16(data, + F71882FG_REG_FAN(3)); + if (data->type == f71882fg) + data->in_status = f71882fg_read8(data, F71882FG_REG_IN_STATUS); - for (nr = 0; nr < 9; nr++) + for (nr = 0; nr < nr_ins; nr++) data->in[nr] = f71882fg_read8(data, F71882FG_REG_IN(nr)); @@ -417,7 +1006,7 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; int speed = fan_from_reg(data->fan[nr]); if (speed == FAN_MIN_DETECT) @@ -426,11 +1015,39 @@ static ssize_t show_fan(struct device *dev, struct device_attribute *devattr, return sprintf(buf, "%d\n", speed); } +static ssize_t show_fan_full_speed(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int speed = fan_from_reg(data->fan_full_speed[nr]); + return sprintf(buf, "%d\n", speed); +} + +static ssize_t store_fan_full_speed(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + long val = simple_strtol(buf, NULL, 10); + + val = SENSORS_LIMIT(val, 23, 1500000); + val = fan_to_reg(val); + + mutex_lock(&data->update_lock); + f71882fg_write16(data, F71882FG_REG_FAN_FULL_SPEED(nr), val); + data->fan_full_speed[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + static ssize_t show_fan_beep(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; if (data->fan_beep & (1 << nr)) return sprintf(buf, "1\n"); @@ -442,10 +1059,11 @@ static ssize_t store_fan_beep(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr = to_sensor_dev_attr(devattr)->index; - int val = simple_strtoul(buf, NULL, 10); + int nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); + data->fan_beep = f71882fg_read8(data, F71882FG_REG_FAN_BEEP); if (val) data->fan_beep |= 1 << nr; else @@ -461,7 +1079,7 @@ static ssize_t show_fan_alarm(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; if (data->fan_status & (1 << nr)) return sprintf(buf, "1\n"); @@ -473,7 +1091,7 @@ static ssize_t show_in(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; return sprintf(buf, "%d\n", data->in[nr] * 8); } @@ -490,10 +1108,8 @@ static ssize_t store_in_max(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71882fg_data *data = dev_get_drvdata(dev); - int val = simple_strtoul(buf, NULL, 10) / 8; - - if (val > 255) - val = 255; + long val = simple_strtol(buf, NULL, 10) / 8; + val = SENSORS_LIMIT(val, 0, 255); mutex_lock(&data->update_lock); f71882fg_write8(data, F71882FG_REG_IN1_HIGH, val); @@ -507,7 +1123,7 @@ static ssize_t show_in_beep(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; if (data->in_beep & (1 << nr)) return sprintf(buf, "1\n"); @@ -519,10 +1135,11 @@ static ssize_t store_in_beep(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr = to_sensor_dev_attr(devattr)->index; - int val = simple_strtoul(buf, NULL, 10); + int nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); + data->in_beep = f71882fg_read8(data, F71882FG_REG_IN_BEEP); if (val) data->in_beep |= 1 << nr; else @@ -538,7 +1155,7 @@ static ssize_t show_in_alarm(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; if (data->in_status & (1 << nr)) return sprintf(buf, "1\n"); @@ -550,7 +1167,7 @@ static ssize_t show_temp(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; return sprintf(buf, "%d\n", data->temp[nr] * 1000); } @@ -559,7 +1176,7 @@ static ssize_t show_temp_max(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; return sprintf(buf, "%d\n", data->temp_high[nr] * 1000); } @@ -568,11 +1185,9 @@ static ssize_t store_temp_max(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr = to_sensor_dev_attr(devattr)->index; - int val = simple_strtoul(buf, NULL, 10) / 1000; - - if (val > 255) - val = 255; + int nr = to_sensor_dev_attr_2(devattr)->index; + long val = simple_strtol(buf, NULL, 10) / 1000; + val = SENSORS_LIMIT(val, 0, 255); mutex_lock(&data->update_lock); f71882fg_write8(data, F71882FG_REG_TEMP_HIGH(nr), val); @@ -586,48 +1201,46 @@ static ssize_t show_temp_max_hyst(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; + int temp_max_hyst; + + mutex_lock(&data->update_lock); + if (nr & 1) + temp_max_hyst = data->temp_hyst[nr / 2] >> 4; + else + temp_max_hyst = data->temp_hyst[nr / 2] & 0x0f; + temp_max_hyst = (data->temp_high[nr] - temp_max_hyst) * 1000; + mutex_unlock(&data->update_lock); - return sprintf(buf, "%d\n", - (data->temp_high[nr] - data->temp_hyst[nr]) * 1000); + return sprintf(buf, "%d\n", temp_max_hyst); } static ssize_t store_temp_max_hyst(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr = to_sensor_dev_attr(devattr)->index; - int val = simple_strtoul(buf, NULL, 10) / 1000; + int nr = to_sensor_dev_attr_2(devattr)->index; + long val = simple_strtol(buf, NULL, 10) / 1000; ssize_t ret = count; + u8 reg; mutex_lock(&data->update_lock); /* convert abs to relative and check */ + data->temp_high[nr] = f71882fg_read8(data, F71882FG_REG_TEMP_HIGH(nr)); + val = SENSORS_LIMIT(val, data->temp_high[nr] - 15, + data->temp_high[nr]); val = data->temp_high[nr] - val; - if (val < 0 || val > 15) { - ret = -EINVAL; - goto store_temp_max_hyst_exit; - } - - data->temp_hyst[nr] = val; /* convert value to register contents */ - switch (nr) { - case 0: - val = val << 4; - break; - case 1: - val = val | (data->temp_hyst[2] << 4); - break; - case 2: - val = data->temp_hyst[1] | (val << 4); - break; - } - - f71882fg_write8(data, nr ? F71882FG_REG_TEMP_HYST23 : - F71882FG_REG_TEMP_HYST1, val); + reg = f71882fg_read8(data, F71882FG_REG_TEMP_HYST(nr / 2)); + if (nr & 1) + reg = (reg & 0x0f) | (val << 4); + else + reg = (reg & 0xf0) | val; + f71882fg_write8(data, F71882FG_REG_TEMP_HYST(nr / 2), reg); + data->temp_hyst[nr / 2] = reg; -store_temp_max_hyst_exit: mutex_unlock(&data->update_lock); return ret; } @@ -636,7 +1249,7 @@ static ssize_t show_temp_crit(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; return sprintf(buf, "%d\n", data->temp_ovt[nr] * 1000); } @@ -645,11 +1258,9 @@ static ssize_t store_temp_crit(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr = to_sensor_dev_attr(devattr)->index; - int val = simple_strtoul(buf, NULL, 10) / 1000; - - if (val > 255) - val = 255; + int nr = to_sensor_dev_attr_2(devattr)->index; + long val = simple_strtol(buf, NULL, 10) / 1000; + val = SENSORS_LIMIT(val, 0, 255); mutex_lock(&data->update_lock); f71882fg_write8(data, F71882FG_REG_TEMP_OVT(nr), val); @@ -663,17 +1274,25 @@ static ssize_t show_temp_crit_hyst(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; + int temp_crit_hyst; + + mutex_lock(&data->update_lock); + if (nr & 1) + temp_crit_hyst = data->temp_hyst[nr / 2] >> 4; + else + temp_crit_hyst = data->temp_hyst[nr / 2] & 0x0f; + temp_crit_hyst = (data->temp_ovt[nr] - temp_crit_hyst) * 1000; + mutex_unlock(&data->update_lock); - return sprintf(buf, "%d\n", - (data->temp_ovt[nr] - data->temp_hyst[nr]) * 1000); + return sprintf(buf, "%d\n", temp_crit_hyst); } static ssize_t show_temp_type(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; return sprintf(buf, "%d\n", data->temp_type[nr]); } @@ -682,9 +1301,9 @@ static ssize_t show_temp_beep(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; - if (data->temp_beep & (1 << (nr + 1))) + if (data->temp_beep & (1 << nr)) return sprintf(buf, "1\n"); else return sprintf(buf, "0\n"); @@ -694,14 +1313,15 @@ static ssize_t store_temp_beep(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct f71882fg_data *data = dev_get_drvdata(dev); - int nr = to_sensor_dev_attr(devattr)->index; - int val = simple_strtoul(buf, NULL, 10); + int nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val = simple_strtoul(buf, NULL, 10); mutex_lock(&data->update_lock); + data->temp_beep = f71882fg_read8(data, F71882FG_REG_TEMP_BEEP); if (val) - data->temp_beep |= 1 << (nr + 1); + data->temp_beep |= 1 << nr; else - data->temp_beep &= ~(1 << (nr + 1)); + data->temp_beep &= ~(1 << nr); f71882fg_write8(data, F71882FG_REG_TEMP_BEEP, data->temp_beep); mutex_unlock(&data->update_lock); @@ -713,9 +1333,9 @@ static ssize_t show_temp_alarm(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; - if (data->temp_status & (1 << (nr + 1))) + if (data->temp_status & (1 << nr)) return sprintf(buf, "1\n"); else return sprintf(buf, "0\n"); @@ -725,113 +1345,528 @@ static ssize_t show_temp_fault(struct device *dev, struct device_attribute *devattr, char *buf) { struct f71882fg_data *data = f71882fg_update_device(dev); - int nr = to_sensor_dev_attr(devattr)->index; + int nr = to_sensor_dev_attr_2(devattr)->index; - if (data->temp_diode_open & (1 << (nr + 1))) + if (data->temp_diode_open & (1 << nr)) return sprintf(buf, "1\n"); else return sprintf(buf, "0\n"); } +static ssize_t show_pwm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct f71882fg_data *data = f71882fg_update_device(dev); + int val, nr = to_sensor_dev_attr_2(devattr)->index; + mutex_lock(&data->update_lock); + if (data->pwm_enable & (1 << (2 * nr))) + /* PWM mode */ + val = data->pwm[nr]; + else { + /* RPM mode */ + val = 255 * fan_from_reg(data->fan_target[nr]) + / fan_from_reg(data->fan_full_speed[nr]); + } + mutex_unlock(&data->update_lock); + return sprintf(buf, "%d\n", val); +} + +static ssize_t store_pwm(struct device *dev, + struct device_attribute *devattr, const char *buf, + size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + long val = simple_strtol(buf, NULL, 10); + val = SENSORS_LIMIT(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + if ((data->type == f8000 && ((data->pwm_enable >> 2 * nr) & 3) != 2) || + (data->type != f8000 && !((data->pwm_enable >> 2 * nr) & 2))) { + count = -EROFS; + goto leave; + } + if (data->pwm_enable & (1 << (2 * nr))) { + /* PWM mode */ + f71882fg_write8(data, F71882FG_REG_PWM(nr), val); + data->pwm[nr] = val; + } else { + /* RPM mode */ + int target, full_speed; + full_speed = f71882fg_read16(data, + F71882FG_REG_FAN_FULL_SPEED(nr)); + target = fan_to_reg(val * fan_from_reg(full_speed) / 255); + f71882fg_write16(data, F71882FG_REG_FAN_TARGET(nr), target); + data->fan_target[nr] = target; + data->fan_full_speed[nr] = full_speed; + } +leave: + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int result = 0; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + switch ((data->pwm_enable >> 2 * nr) & 3) { + case 0: + case 1: + result = 2; /* Normal auto mode */ + break; + case 2: + result = 1; /* Manual mode */ + break; + case 3: + if (data->type == f8000) + result = 3; /* Thermostat mode */ + else + result = 1; /* Manual mode */ + break; + } + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_enable(struct device *dev, struct device_attribute + *devattr, const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + long val = simple_strtol(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + /* Special case for F8000 auto PWM mode / Thermostat mode */ + if (data->type == f8000 && ((data->pwm_enable >> 2 * nr) & 1)) { + switch (val) { + case 2: + data->pwm_enable &= ~(2 << (2 * nr)); + break; /* Normal auto mode */ + case 3: + data->pwm_enable |= 2 << (2 * nr); + break; /* Thermostat mode */ + default: + count = -EINVAL; + goto leave; + } + } else { + switch (val) { + case 1: + data->pwm_enable |= 2 << (2 * nr); + break; /* Manual */ + case 2: + data->pwm_enable &= ~(2 << (2 * nr)); + break; /* Normal auto mode */ + default: + count = -EINVAL; + goto leave; + } + } + f71882fg_write8(data, F71882FG_REG_PWM_ENABLE, data->pwm_enable); +leave: + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + + mutex_lock(&data->update_lock); + if (data->pwm_enable & (1 << (2 * pwm))) { + /* PWM mode */ + result = data->pwm_auto_point_pwm[pwm][point]; + } else { + /* RPM mode */ + result = 32 * 255 / (32 + data->pwm_auto_point_pwm[pwm][point]); + } + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_auto_point_pwm(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + long val = simple_strtol(buf, NULL, 10); + val = SENSORS_LIMIT(val, 0, 255); + + mutex_lock(&data->update_lock); + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + if (data->pwm_enable & (1 << (2 * pwm))) { + /* PWM mode */ + } else { + /* RPM mode */ + if (val < 29) /* Prevent negative numbers */ + val = 255; + else + val = (255 - val) * 32 / val; + } + f71882fg_write8(data, F71882FG_REG_POINT_PWM(pwm, point), val); + data->pwm_auto_point_pwm[pwm][point] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result = 0; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + + mutex_lock(&data->update_lock); + if (nr & 1) + result = data->pwm_auto_point_hyst[nr / 2] >> 4; + else + result = data->pwm_auto_point_hyst[nr / 2] & 0x0f; + result = 1000 * (data->pwm_auto_point_temp[nr][point] - result); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_auto_point_temp_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + long val = simple_strtol(buf, NULL, 10) / 1000; + u8 reg; + + mutex_lock(&data->update_lock); + data->pwm_auto_point_temp[nr][point] = + f71882fg_read8(data, F71882FG_REG_POINT_TEMP(nr, point)); + val = SENSORS_LIMIT(val, data->pwm_auto_point_temp[nr][point] - 15, + data->pwm_auto_point_temp[nr][point]); + val = data->pwm_auto_point_temp[nr][point] - val; + + reg = f71882fg_read8(data, F71882FG_REG_FAN_HYST(nr / 2)); + if (nr & 1) + reg = (reg & 0x0f) | (val << 4); + else + reg = (reg & 0xf0) | val; + + f71882fg_write8(data, F71882FG_REG_FAN_HYST(nr / 2), reg); + data->pwm_auto_point_hyst[nr / 2] = reg; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + + result = (data->pwm_auto_point_mapping[nr] >> 4) & 1; + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_interpolate(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + unsigned long val = simple_strtoul(buf, NULL, 10); + + mutex_lock(&data->update_lock); + data->pwm_auto_point_mapping[nr] = + f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr)); + if (val) + val = data->pwm_auto_point_mapping[nr] | (1 << 4); + else + val = data->pwm_auto_point_mapping[nr] & (~(1 << 4)); + f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val); + data->pwm_auto_point_mapping[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int temp_start = (data->type == f8000) ? 0 : 1; + + result = 1 << ((data->pwm_auto_point_mapping[nr] & 3) - temp_start); + + return sprintf(buf, "%d\n", result); +} + +static ssize_t store_pwm_auto_point_channel(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int nr = to_sensor_dev_attr_2(devattr)->index; + int temp_start = (data->type == f8000) ? 0 : 1; + long val = simple_strtol(buf, NULL, 10); + + switch (val) { + case 1: + val = 0; + break; + case 2: + val = 1; + break; + case 4: + val = 2; + break; + default: + return -EINVAL; + } + val += temp_start; + mutex_lock(&data->update_lock); + data->pwm_auto_point_mapping[nr] = + f71882fg_read8(data, F71882FG_REG_POINT_MAPPING(nr)); + val = (data->pwm_auto_point_mapping[nr] & 0xfc) | val; + f71882fg_write8(data, F71882FG_REG_POINT_MAPPING(nr), val); + data->pwm_auto_point_mapping[nr] = val; + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + char *buf) +{ + int result; + struct f71882fg_data *data = f71882fg_update_device(dev); + int pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + + result = data->pwm_auto_point_temp[pwm][point]; + return sprintf(buf, "%d\n", 1000 * result); +} + +static ssize_t store_pwm_auto_point_temp(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + struct f71882fg_data *data = dev_get_drvdata(dev); + int pwm = to_sensor_dev_attr_2(devattr)->index; + int point = to_sensor_dev_attr_2(devattr)->nr; + long val = simple_strtol(buf, NULL, 10) / 1000; + val = SENSORS_LIMIT(val, 0, 255); + + mutex_lock(&data->update_lock); + f71882fg_write8(data, F71882FG_REG_POINT_TEMP(pwm, point), val); + data->pwm_auto_point_temp[pwm][point] = val; + mutex_unlock(&data->update_lock); + + return count; +} + static ssize_t show_name(struct device *dev, struct device_attribute *devattr, char *buf) { - return sprintf(buf, DRVNAME "\n"); + struct f71882fg_data *data = dev_get_drvdata(dev); + return sprintf(buf, "%s\n", f71882fg_names[data->type]); } +static int __devinit f71882fg_create_sysfs_files(struct platform_device *pdev, + struct sensor_device_attribute_2 *attr, int count) +{ + int err, i; + + for (i = 0; i < count; i++) { + err = device_create_file(&pdev->dev, &attr[i].dev_attr); + if (err) + return err; + } + return 0; +} -static int __devinit f71882fg_probe(struct platform_device * pdev) +static int __devinit f71882fg_probe(struct platform_device *pdev) { struct f71882fg_data *data; - int err, i; + struct f71882fg_sio_data *sio_data = pdev->dev.platform_data; + int err, i, nr_fans = (sio_data->type == f71882fg) ? 4 : 3; u8 start_reg; - if (!(data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL))) + data = kzalloc(sizeof(struct f71882fg_data), GFP_KERNEL); + if (!data) return -ENOMEM; data->addr = platform_get_resource(pdev, IORESOURCE_IO, 0)->start; + data->type = sio_data->type; mutex_init(&data->update_lock); platform_set_drvdata(pdev, data); - /* Register sysfs interface files */ - for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++) { - err = device_create_file(&pdev->dev, &f71882fg_dev_attr[i]); - if (err) - goto exit_unregister_sysfs; + start_reg = f71882fg_read8(data, F71882FG_REG_START); + if (start_reg & 0x04) { + dev_warn(&pdev->dev, "Hardware monitor is powered down\n"); + err = -ENODEV; + goto exit_free; + } + if (!(start_reg & 0x03)) { + dev_warn(&pdev->dev, "Hardware monitoring not activated\n"); + err = -ENODEV; + goto exit_free; } - start_reg = f71882fg_read8(data, F71882FG_REG_START); + data->pwm_enable = f71882fg_read8(data, F71882FG_REG_PWM_ENABLE); + /* If it is a 71862 and the fan / pwm part is enabled sanity check + the pwm settings */ + if (data->type == f71862fg && (start_reg & 0x02)) { + if ((data->pwm_enable & 0x15) != 0x15) { + dev_err(&pdev->dev, + "Invalid (reserved) pwm settings: 0x%02x\n", + (unsigned int)data->pwm_enable); + err = -ENODEV; + goto exit_free; + } + } + + /* Register sysfs interface files */ + err = device_create_file(&pdev->dev, &dev_attr_name); + if (err) + goto exit_unregister_sysfs; + if (start_reg & 0x01) { - for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++) { - err = device_create_file(&pdev->dev, - &f71882fg_in_temp_attr[i].dev_attr); + switch (data->type) { + case f71882fg: + err = f71882fg_create_sysfs_files(pdev, + f71882fg_in_temp_attr, + ARRAY_SIZE(f71882fg_in_temp_attr)); if (err) goto exit_unregister_sysfs; + /* fall through! */ + case f71862fg: + err = f71882fg_create_sysfs_files(pdev, + f718x2fg_in_temp_attr, + ARRAY_SIZE(f718x2fg_in_temp_attr)); + break; + case f8000: + err = f71882fg_create_sysfs_files(pdev, + f8000_in_temp_attr, + ARRAY_SIZE(f8000_in_temp_attr)); + break; } + if (err) + goto exit_unregister_sysfs; } if (start_reg & 0x02) { - for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++) { - err = device_create_file(&pdev->dev, - &f71882fg_fan_attr[i].dev_attr); - if (err) - goto exit_unregister_sysfs; + err = f71882fg_create_sysfs_files(pdev, fxxxx_fan_attr, + ARRAY_SIZE(fxxxx_fan_attr)); + if (err) + goto exit_unregister_sysfs; + + switch (data->type) { + case f71862fg: + err = f71882fg_create_sysfs_files(pdev, + f71862fg_fan_attr, + ARRAY_SIZE(f71862fg_fan_attr)); + break; + case f71882fg: + err = f71882fg_create_sysfs_files(pdev, + f71882fg_fan_attr, + ARRAY_SIZE(f71882fg_fan_attr)); + break; + case f8000: + err = f71882fg_create_sysfs_files(pdev, + f8000_fan_attr, + ARRAY_SIZE(f8000_fan_attr)); + break; } + if (err) + goto exit_unregister_sysfs; + + for (i = 0; i < nr_fans; i++) + dev_info(&pdev->dev, "Fan: %d is in %s mode\n", i + 1, + (data->pwm_enable & (1 << 2 * i)) ? + "duty-cycle" : "RPM"); } data->hwmon_dev = hwmon_device_register(&pdev->dev); if (IS_ERR(data->hwmon_dev)) { err = PTR_ERR(data->hwmon_dev); + data->hwmon_dev = NULL; goto exit_unregister_sysfs; } return 0; exit_unregister_sysfs: - for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++) - device_remove_file(&pdev->dev, &f71882fg_dev_attr[i]); - - for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++) - device_remove_file(&pdev->dev, - &f71882fg_in_temp_attr[i].dev_attr); - - for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++) - device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr); - + f71882fg_remove(pdev); /* Will unregister the sysfs files for us */ + return err; /* f71882fg_remove() also frees our data */ +exit_free: kfree(data); - return err; } -static int __devexit f71882fg_remove(struct platform_device *pdev) +static int f71882fg_remove(struct platform_device *pdev) { int i; struct f71882fg_data *data = platform_get_drvdata(pdev); platform_set_drvdata(pdev, NULL); - hwmon_device_unregister(data->hwmon_dev); + if (data->hwmon_dev) + hwmon_device_unregister(data->hwmon_dev); + + /* Note we are not looping over all attr arrays we have as the ones + below are supersets of the ones skipped. */ + device_remove_file(&pdev->dev, &dev_attr_name); - for (i = 0; i < ARRAY_SIZE(f71882fg_dev_attr); i++) - device_remove_file(&pdev->dev, &f71882fg_dev_attr[i]); + for (i = 0; i < ARRAY_SIZE(f718x2fg_in_temp_attr); i++) + device_remove_file(&pdev->dev, + &f718x2fg_in_temp_attr[i].dev_attr); for (i = 0; i < ARRAY_SIZE(f71882fg_in_temp_attr); i++) device_remove_file(&pdev->dev, &f71882fg_in_temp_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(fxxxx_fan_attr); i++) + device_remove_file(&pdev->dev, &fxxxx_fan_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(f71882fg_fan_attr); i++) device_remove_file(&pdev->dev, &f71882fg_fan_attr[i].dev_attr); + for (i = 0; i < ARRAY_SIZE(f8000_fan_attr); i++) + device_remove_file(&pdev->dev, &f8000_fan_attr[i].dev_attr); + kfree(data); return 0; } -static int __init f71882fg_find(int sioaddr, unsigned short *address) +static int __init f71882fg_find(int sioaddr, unsigned short *address, + struct f71882fg_sio_data *sio_data) { int err = -ENODEV; u16 devid; - u8 start_reg; - struct f71882fg_data data; superio_enter(sioaddr); @@ -842,7 +1877,17 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address) } devid = force_id ? force_id : superio_inw(sioaddr, SIO_REG_DEVID); - if (devid != SIO_F71882_ID) { + switch (devid) { + case SIO_F71862_ID: + sio_data->type = f71862fg; + break; + case SIO_F71882_ID: + sio_data->type = f71882fg; + break; + case SIO_F8000_ID: + sio_data->type = f8000; + break; + default: printk(KERN_INFO DRVNAME ": Unsupported Fintek device\n"); goto exit; } @@ -861,24 +1906,17 @@ static int __init f71882fg_find(int sioaddr, unsigned short *address) } *address &= ~(REGION_LENGTH - 1); /* Ignore 3 LSB */ - data.addr = *address; - start_reg = f71882fg_read8(&data, F71882FG_REG_START); - if (!(start_reg & 0x03)) { - printk(KERN_WARNING DRVNAME - ": Hardware monitoring not activated\n"); - goto exit; - } - err = 0; - printk(KERN_INFO DRVNAME ": Found F71882FG chip at %#x, revision %d\n", - (unsigned int)*address, + printk(KERN_INFO DRVNAME ": Found %s chip at %#x, revision %d\n", + f71882fg_names[sio_data->type], (unsigned int)*address, (int)superio_inb(sioaddr, SIO_REG_DEVREV)); exit: superio_exit(sioaddr); return err; } -static int __init f71882fg_device_add(unsigned short address) +static int __init f71882fg_device_add(unsigned short address, + const struct f71882fg_sio_data *sio_data) { struct resource res = { .start = address, @@ -892,12 +1930,23 @@ static int __init f71882fg_device_add(unsigned short address) return -ENOMEM; res.name = f71882fg_pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + return err; + err = platform_device_add_resources(f71882fg_pdev, &res, 1); if (err) { printk(KERN_ERR DRVNAME ": Device resource addition failed\n"); goto exit_device_put; } + err = platform_device_add_data(f71882fg_pdev, sio_data, + sizeof(struct f71882fg_sio_data)); + if (err) { + printk(KERN_ERR DRVNAME ": Platform data allocation failed\n"); + goto exit_device_put; + } + err = platform_device_add(f71882fg_pdev); if (err) { printk(KERN_ERR DRVNAME ": Device addition failed\n"); @@ -916,14 +1965,20 @@ static int __init f71882fg_init(void) { int err = -ENODEV; unsigned short address; + struct f71882fg_sio_data sio_data; + + memset(&sio_data, 0, sizeof(sio_data)); - if (f71882fg_find(0x2e, &address) && f71882fg_find(0x4e, &address)) + if (f71882fg_find(0x2e, &address, &sio_data) && + f71882fg_find(0x4e, &address, &sio_data)) goto exit; - if ((err = platform_driver_register(&f71882fg_driver))) + err = platform_driver_register(&f71882fg_driver); + if (err) goto exit; - if ((err = f71882fg_device_add(address))) + err = f71882fg_device_add(address, &sio_data); + if (err) goto exit_driver; return 0; @@ -941,7 +1996,7 @@ static void __exit f71882fg_exit(void) } MODULE_DESCRIPTION("F71882FG Hardware Monitoring Driver"); -MODULE_AUTHOR("Hans Edgington (hans@edgington.nl)"); +MODULE_AUTHOR("Hans Edgington, Hans de Goede (hdegoede@redhat.com)"); MODULE_LICENSE("GPL"); module_init(f71882fg_init); diff --git a/drivers/hwmon/fschmd.c b/drivers/hwmon/fschmd.c index 967170368933..d07f4ef75092 100644 --- a/drivers/hwmon/fschmd.c +++ b/drivers/hwmon/fschmd.c @@ -1,6 +1,6 @@ /* fschmd.c * - * Copyright (C) 2007 Hans de Goede <j.w.r.degoede@hhs.nl> + * Copyright (C) 2007,2008 Hans de Goede <hdegoede@redhat.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 @@ -42,11 +42,20 @@ #include <linux/mutex.h> #include <linux/sysfs.h> #include <linux/dmi.h> +#include <linux/fs.h> +#include <linux/watchdog.h> +#include <linux/miscdevice.h> +#include <linux/uaccess.h> +#include <linux/kref.h> /* Addresses to scan */ static const unsigned short normal_i2c[] = { 0x73, I2C_CLIENT_END }; /* Insmod parameters */ +static int nowayout = WATCHDOG_NOWAYOUT; +module_param(nowayout, int, 0); +MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started (default=" + __MODULE_STRING(WATCHDOG_NOWAYOUT) ")"); I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); /* @@ -63,19 +72,26 @@ I2C_CLIENT_INSMOD_5(fscpos, fscher, fscscy, fschrc, fschmd); #define FSCHMD_REG_EVENT_STATE 0x04 #define FSCHMD_REG_CONTROL 0x05 -#define FSCHMD_CONTROL_ALERT_LED_MASK 0x01 +#define FSCHMD_CONTROL_ALERT_LED 0x01 -/* watchdog (support to be implemented) */ +/* watchdog */ #define FSCHMD_REG_WDOG_PRESET 0x28 #define FSCHMD_REG_WDOG_STATE 0x23 #define FSCHMD_REG_WDOG_CONTROL 0x21 +#define FSCHMD_WDOG_CONTROL_TRIGGER 0x10 +#define FSCHMD_WDOG_CONTROL_STARTED 0x10 /* the same as trigger */ +#define FSCHMD_WDOG_CONTROL_STOP 0x20 +#define FSCHMD_WDOG_CONTROL_RESOLUTION 0x40 + +#define FSCHMD_WDOG_STATE_CARDRESET 0x02 + /* voltages, weird order is to keep the same order as the old drivers */ static const u8 FSCHMD_REG_VOLT[3] = { 0x45, 0x42, 0x48 }; /* minimum pwm at which the fan is driven (pwm can by increased depending on the temp. Notice that for the scy some fans share there minimum speed. - Also notice that with the scy the sensor order is different then with the + Also notice that with the scy the sensor order is different than with the other chips, this order was in the 2.4 driver and kept for consistency. */ static const u8 FSCHMD_REG_FAN_MIN[5][6] = { { 0x55, 0x65 }, /* pos */ @@ -115,8 +131,8 @@ static const u8 FSCHMD_REG_FAN_RIPPLE[5][6] = { static const int FSCHMD_NO_FAN_SENSORS[5] = { 3, 3, 6, 4, 5 }; /* Fan status register bitmasks */ -#define FSCHMD_FAN_ALARM_MASK 0x04 /* called fault by FSC! */ -#define FSCHMD_FAN_NOT_PRESENT_MASK 0x08 /* not documented */ +#define FSCHMD_FAN_ALARM 0x04 /* called fault by FSC! */ +#define FSCHMD_FAN_NOT_PRESENT 0x08 /* not documented */ /* actual temperature registers */ @@ -158,14 +174,11 @@ static const u8 FSCHER_REG_TEMP_AUTOP2[] = { 0x75, 0x85, 0x95 }; */ static const int FSCHMD_NO_TEMP_SENSORS[5] = { 3, 3, 4, 3, 5 }; /* temp status register bitmasks */ -#define FSCHMD_TEMP_WORKING_MASK 0x01 -#define FSCHMD_TEMP_ALERT_MASK 0x02 +#define FSCHMD_TEMP_WORKING 0x01 +#define FSCHMD_TEMP_ALERT 0x02 /* there only really is an alarm if the sensor is working and alert == 1 */ #define FSCHMD_TEMP_ALARM_MASK \ - (FSCHMD_TEMP_WORKING_MASK | FSCHMD_TEMP_ALERT_MASK) - -/* our driver name */ -#define FSCHMD_NAME "fschmd" + (FSCHMD_TEMP_WORKING | FSCHMD_TEMP_ALERT) /* * Functions declarations @@ -195,7 +208,7 @@ MODULE_DEVICE_TABLE(i2c, fschmd_id); static struct i2c_driver fschmd_driver = { .class = I2C_CLASS_HWMON, .driver = { - .name = FSCHMD_NAME, + .name = "fschmd", }, .probe = fschmd_probe, .remove = fschmd_remove, @@ -209,14 +222,26 @@ static struct i2c_driver fschmd_driver = { */ struct fschmd_data { + struct i2c_client *client; struct device *hwmon_dev; struct mutex update_lock; + struct mutex watchdog_lock; + struct list_head list; /* member of the watchdog_data_list */ + struct kref kref; + struct miscdevice watchdog_miscdev; int kind; + unsigned long watchdog_is_open; + char watchdog_expect_close; + char watchdog_name[10]; /* must be unique to avoid sysfs conflict */ char valid; /* zero until following fields are valid */ unsigned long last_updated; /* in jiffies */ /* register values */ + u8 revision; /* chip revision */ u8 global_control; /* global control register */ + u8 watchdog_control; /* watchdog control register */ + u8 watchdog_state; /* watchdog status register */ + u8 watchdog_preset; /* watchdog counter preset on trigger val */ u8 volt[3]; /* 12, 5, battery voltage */ u8 temp_act[5]; /* temperature */ u8 temp_status[5]; /* status of sensor */ @@ -228,11 +253,28 @@ struct fschmd_data { }; /* Global variables to hold information read from special DMI tables, which are - available on FSC machines with an fscher or later chip. */ + available on FSC machines with an fscher or later chip. There is no need to + protect these with a lock as they are only modified from our attach function + which always gets called with the i2c-core lock held and never accessed + before the attach function is done with them. */ static int dmi_mult[3] = { 490, 200, 100 }; static int dmi_offset[3] = { 0, 0, 0 }; static int dmi_vref = -1; +/* Somewhat ugly :( global data pointer list with all fschmd devices, so that + we can find our device data as when using misc_register there is no other + method to get to ones device data from the open fop. */ +static LIST_HEAD(watchdog_data_list); +/* Note this lock not only protect list access, but also data.kref access */ +static DEFINE_MUTEX(watchdog_data_mutex); + +/* Release our data struct when we're detached from the i2c client *and* all + references to our watchdog device are released */ +static void fschmd_release_resources(struct kref *ref) +{ + struct fschmd_data *data = container_of(ref, struct fschmd_data, kref); + kfree(data); +} /* * Sysfs attr show / store functions @@ -300,7 +342,7 @@ static ssize_t show_temp_fault(struct device *dev, struct fschmd_data *data = fschmd_update_device(dev); /* bit 0 set means sensor working ok, so no fault! */ - if (data->temp_status[index] & FSCHMD_TEMP_WORKING_MASK) + if (data->temp_status[index] & FSCHMD_TEMP_WORKING) return sprintf(buf, "0\n"); else return sprintf(buf, "1\n"); @@ -385,7 +427,7 @@ static ssize_t show_fan_alarm(struct device *dev, int index = to_sensor_dev_attr(devattr)->index; struct fschmd_data *data = fschmd_update_device(dev); - if (data->fan_status[index] & FSCHMD_FAN_ALARM_MASK) + if (data->fan_status[index] & FSCHMD_FAN_ALARM) return sprintf(buf, "1\n"); else return sprintf(buf, "0\n"); @@ -397,7 +439,7 @@ static ssize_t show_fan_fault(struct device *dev, int index = to_sensor_dev_attr(devattr)->index; struct fschmd_data *data = fschmd_update_device(dev); - if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT_MASK) + if (data->fan_status[index] & FSCHMD_FAN_NOT_PRESENT) return sprintf(buf, "1\n"); else return sprintf(buf, "0\n"); @@ -449,7 +491,7 @@ static ssize_t show_alert_led(struct device *dev, { struct fschmd_data *data = fschmd_update_device(dev); - if (data->global_control & FSCHMD_CONTROL_ALERT_LED_MASK) + if (data->global_control & FSCHMD_CONTROL_ALERT_LED) return sprintf(buf, "1\n"); else return sprintf(buf, "0\n"); @@ -467,9 +509,9 @@ static ssize_t store_alert_led(struct device *dev, reg = i2c_smbus_read_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL); if (v) - reg |= FSCHMD_CONTROL_ALERT_LED_MASK; + reg |= FSCHMD_CONTROL_ALERT_LED; else - reg &= ~FSCHMD_CONTROL_ALERT_LED_MASK; + reg &= ~FSCHMD_CONTROL_ALERT_LED; i2c_smbus_write_byte_data(to_i2c_client(dev), FSCHMD_REG_CONTROL, reg); @@ -551,7 +593,265 @@ static struct sensor_device_attribute fschmd_fan_attr[] = { /* - * Real code + * Watchdog routines + */ + +static int watchdog_set_timeout(struct fschmd_data *data, int timeout) +{ + int ret, resolution; + int kind = data->kind + 1; /* 0-x array index -> 1-x module param */ + + /* 2 second or 60 second resolution? */ + if (timeout <= 510 || kind == fscpos || kind == fscscy) + resolution = 2; + else + resolution = 60; + + if (timeout < resolution || timeout > (resolution * 255)) + return -EINVAL; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + if (resolution == 2) + data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_RESOLUTION; + else + data->watchdog_control |= FSCHMD_WDOG_CONTROL_RESOLUTION; + + data->watchdog_preset = DIV_ROUND_UP(timeout, resolution); + + /* Write new timeout value */ + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_PRESET, + data->watchdog_preset); + /* Write new control register, do not trigger! */ + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, + data->watchdog_control & ~FSCHMD_WDOG_CONTROL_TRIGGER); + + ret = data->watchdog_preset * resolution; + +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_get_timeout(struct fschmd_data *data) +{ + int timeout; + + mutex_lock(&data->watchdog_lock); + if (data->watchdog_control & FSCHMD_WDOG_CONTROL_RESOLUTION) + timeout = data->watchdog_preset * 60; + else + timeout = data->watchdog_preset * 2; + mutex_unlock(&data->watchdog_lock); + + return timeout; +} + +static int watchdog_trigger(struct fschmd_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_control |= FSCHMD_WDOG_CONTROL_TRIGGER; + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, + data->watchdog_control); +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_stop(struct fschmd_data *data) +{ + int ret = 0; + + mutex_lock(&data->watchdog_lock); + if (!data->client) { + ret = -ENODEV; + goto leave; + } + + data->watchdog_control &= ~FSCHMD_WDOG_CONTROL_STARTED; + /* Don't store the stop flag in our watchdog control register copy, as + its a write only bit (read always returns 0) */ + i2c_smbus_write_byte_data(data->client, FSCHMD_REG_WDOG_CONTROL, + data->watchdog_control | FSCHMD_WDOG_CONTROL_STOP); +leave: + mutex_unlock(&data->watchdog_lock); + return ret; +} + +static int watchdog_open(struct inode *inode, struct file *filp) +{ + struct fschmd_data *pos, *data = NULL; + + /* We get called from drivers/char/misc.c with misc_mtx hold, and we + call misc_register() from fschmd_probe() with watchdog_data_mutex + hold, as misc_register() takes the misc_mtx lock, this is a possible + deadlock, so we use mutex_trylock here. */ + if (!mutex_trylock(&watchdog_data_mutex)) + return -ERESTARTSYS; + list_for_each_entry(pos, &watchdog_data_list, list) { + if (pos->watchdog_miscdev.minor == iminor(inode)) { + data = pos; + break; + } + } + /* Note we can never not have found data, so we don't check for this */ + kref_get(&data->kref); + mutex_unlock(&watchdog_data_mutex); + + if (test_and_set_bit(0, &data->watchdog_is_open)) + return -EBUSY; + + /* Start the watchdog */ + watchdog_trigger(data); + filp->private_data = data; + + return nonseekable_open(inode, filp); +} + +static int watchdog_release(struct inode *inode, struct file *filp) +{ + struct fschmd_data *data = filp->private_data; + + if (data->watchdog_expect_close) { + watchdog_stop(data); + data->watchdog_expect_close = 0; + } else { + watchdog_trigger(data); + dev_crit(&data->client->dev, + "unexpected close, not stopping watchdog!\n"); + } + + clear_bit(0, &data->watchdog_is_open); + + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, fschmd_release_resources); + mutex_unlock(&watchdog_data_mutex); + + return 0; +} + +static ssize_t watchdog_write(struct file *filp, const char __user *buf, + size_t count, loff_t *offset) +{ + size_t ret; + struct fschmd_data *data = filp->private_data; + + if (count) { + if (!nowayout) { + size_t i; + + /* Clear it in case it was set with a previous write */ + data->watchdog_expect_close = 0; + + for (i = 0; i != count; i++) { + char c; + if (get_user(c, buf + i)) + return -EFAULT; + if (c == 'V') + data->watchdog_expect_close = 1; + } + } + ret = watchdog_trigger(data); + if (ret < 0) + return ret; + } + return count; +} + +static int watchdog_ioctl(struct inode *inode, struct file *filp, + unsigned int cmd, unsigned long arg) +{ + static struct watchdog_info ident = { + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT | + WDIOF_CARDRESET, + .identity = "FSC watchdog" + }; + int i, ret = 0; + struct fschmd_data *data = filp->private_data; + + switch (cmd) { + case WDIOC_GETSUPPORT: + ident.firmware_version = data->revision; + if (!nowayout) + ident.options |= WDIOF_MAGICCLOSE; + if (copy_to_user((void __user *)arg, &ident, sizeof(ident))) + ret = -EFAULT; + break; + + case WDIOC_GETSTATUS: + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_GETBOOTSTATUS: + if (data->watchdog_state & FSCHMD_WDOG_STATE_CARDRESET) + ret = put_user(WDIOF_CARDRESET, (int __user *)arg); + else + ret = put_user(0, (int __user *)arg); + break; + + case WDIOC_KEEPALIVE: + ret = watchdog_trigger(data); + break; + + case WDIOC_GETTIMEOUT: + i = watchdog_get_timeout(data); + ret = put_user(i, (int __user *)arg); + break; + + case WDIOC_SETTIMEOUT: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + ret = watchdog_set_timeout(data, i); + if (ret > 0) + ret = put_user(ret, (int __user *)arg); + break; + + case WDIOC_SETOPTIONS: + if (get_user(i, (int __user *)arg)) { + ret = -EFAULT; + break; + } + + if (i & WDIOS_DISABLECARD) + ret = watchdog_stop(data); + else if (i & WDIOS_ENABLECARD) + ret = watchdog_trigger(data); + else + ret = -EINVAL; + + break; + default: + ret = -ENOTTY; + } + + return ret; +} + +static struct file_operations watchdog_fops = { + .owner = THIS_MODULE, + .llseek = no_llseek, + .open = watchdog_open, + .release = watchdog_release, + .write = watchdog_write, + .ioctl = watchdog_ioctl, +}; + + +/* + * Detect, register, unregister and update device functions */ /* DMI decode routine to read voltage scaling factors from special DMI tables, @@ -661,9 +961,9 @@ static int fschmd_probe(struct i2c_client *client, const struct i2c_device_id *id) { struct fschmd_data *data; - u8 revision; const char * const names[5] = { "Poseidon", "Hermes", "Scylla", "Heracles", "Heimdall" }; + const int watchdog_minors[] = { WATCHDOG_MINOR, 212, 213, 214, 215 }; int i, err; enum chips kind = id->driver_data; @@ -673,6 +973,13 @@ static int fschmd_probe(struct i2c_client *client, i2c_set_clientdata(client, data); mutex_init(&data->update_lock); + mutex_init(&data->watchdog_lock); + INIT_LIST_HEAD(&data->list); + kref_init(&data->kref); + /* Store client pointer in our data struct for watchdog usage + (where the client is found through a data ptr instead of the + otherway around) */ + data->client = client; if (kind == fscpos) { /* The Poseidon has hardwired temp limits, fill these @@ -683,16 +990,27 @@ static int fschmd_probe(struct i2c_client *client, } /* Read the special DMI table for fscher and newer chips */ - if (kind == fscher || kind >= fschrc) { + if ((kind == fscher || kind >= fschrc) && dmi_vref == -1) { dmi_walk(fschmd_dmi_decode); if (dmi_vref == -1) { - printk(KERN_WARNING FSCHMD_NAME - ": Couldn't get voltage scaling factors from " + dev_warn(&client->dev, + "Couldn't get voltage scaling factors from " "BIOS DMI table, using builtin defaults\n"); dmi_vref = 33; } } + /* Read in some never changing registers */ + data->revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); + data->global_control = i2c_smbus_read_byte_data(client, + FSCHMD_REG_CONTROL); + data->watchdog_control = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_CONTROL); + data->watchdog_state = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_STATE); + data->watchdog_preset = i2c_smbus_read_byte_data(client, + FSCHMD_REG_WDOG_PRESET); + /* i2c kind goes from 1-5, we want from 0-4 to address arrays */ data->kind = kind - 1; @@ -735,9 +1053,43 @@ static int fschmd_probe(struct i2c_client *client, goto exit_detach; } - revision = i2c_smbus_read_byte_data(client, FSCHMD_REG_REVISION); - printk(KERN_INFO FSCHMD_NAME ": Detected FSC %s chip, revision: %d\n", - names[data->kind], (int) revision); + /* We take the data_mutex lock early so that watchdog_open() cannot + run when misc_register() has completed, but we've not yet added + our data to the watchdog_data_list (and set the default timeout) */ + mutex_lock(&watchdog_data_mutex); + for (i = 0; i < ARRAY_SIZE(watchdog_minors); i++) { + /* Register our watchdog part */ + snprintf(data->watchdog_name, sizeof(data->watchdog_name), + "watchdog%c", (i == 0) ? '\0' : ('0' + i)); + data->watchdog_miscdev.name = data->watchdog_name; + data->watchdog_miscdev.fops = &watchdog_fops; + data->watchdog_miscdev.minor = watchdog_minors[i]; + err = misc_register(&data->watchdog_miscdev); + if (err == -EBUSY) + continue; + if (err) { + data->watchdog_miscdev.minor = 0; + dev_err(&client->dev, + "Registering watchdog chardev: %d\n", err); + break; + } + + list_add(&data->list, &watchdog_data_list); + watchdog_set_timeout(data, 60); + dev_info(&client->dev, + "Registered watchdog chardev major 10, minor: %d\n", + watchdog_minors[i]); + break; + } + if (i == ARRAY_SIZE(watchdog_minors)) { + data->watchdog_miscdev.minor = 0; + dev_warn(&client->dev, "Couldn't register watchdog chardev " + "(due to no free minor)\n"); + } + mutex_unlock(&watchdog_data_mutex); + + dev_info(&client->dev, "Detected FSC %s chip, revision: %d\n", + names[data->kind], (int) data->revision); return 0; @@ -751,6 +1103,24 @@ static int fschmd_remove(struct i2c_client *client) struct fschmd_data *data = i2c_get_clientdata(client); int i; + /* Unregister the watchdog (if registered) */ + if (data->watchdog_miscdev.minor) { + misc_deregister(&data->watchdog_miscdev); + if (data->watchdog_is_open) { + dev_warn(&client->dev, + "i2c client detached with watchdog open! " + "Stopping watchdog.\n"); + watchdog_stop(data); + } + mutex_lock(&watchdog_data_mutex); + list_del(&data->list); + mutex_unlock(&watchdog_data_mutex); + /* Tell the watchdog code the client is gone */ + mutex_lock(&data->watchdog_lock); + data->client = NULL; + mutex_unlock(&data->watchdog_lock); + } + /* Check if registered in case we're called from fschmd_detect to cleanup after an error */ if (data->hwmon_dev) @@ -765,7 +1135,10 @@ static int fschmd_remove(struct i2c_client *client) device_remove_file(&client->dev, &fschmd_fan_attr[i].dev_attr); - kfree(data); + mutex_lock(&watchdog_data_mutex); + kref_put(&data->kref, fschmd_release_resources); + mutex_unlock(&watchdog_data_mutex); + return 0; } @@ -798,7 +1171,7 @@ static struct fschmd_data *fschmd_update_device(struct device *dev) data->temp_act[i] < data->temp_max[i]) i2c_smbus_write_byte_data(client, FSCHMD_REG_TEMP_STATE[data->kind][i], - FSCHMD_TEMP_ALERT_MASK); + FSCHMD_TEMP_ALERT); } for (i = 0; i < FSCHMD_NO_FAN_SENSORS[data->kind]; i++) { @@ -816,28 +1189,17 @@ static struct fschmd_data *fschmd_update_device(struct device *dev) FSCHMD_REG_FAN_MIN[data->kind][i]); /* reset fan status if speed is back to > 0 */ - if ((data->fan_status[i] & FSCHMD_FAN_ALARM_MASK) && + if ((data->fan_status[i] & FSCHMD_FAN_ALARM) && data->fan_act[i]) i2c_smbus_write_byte_data(client, FSCHMD_REG_FAN_STATE[data->kind][i], - FSCHMD_FAN_ALARM_MASK); + FSCHMD_FAN_ALARM); } for (i = 0; i < 3; i++) data->volt[i] = i2c_smbus_read_byte_data(client, FSCHMD_REG_VOLT[i]); - data->global_control = i2c_smbus_read_byte_data(client, - FSCHMD_REG_CONTROL); - - /* To be implemented in the future - data->watchdog[0] = i2c_smbus_read_byte_data(client, - FSCHMD_REG_WDOG_PRESET); - data->watchdog[1] = i2c_smbus_read_byte_data(client, - FSCHMD_REG_WDOG_STATE); - data->watchdog[2] = i2c_smbus_read_byte_data(client, - FSCHMD_REG_WDOG_CONTROL); */ - data->last_updated = jiffies; data->valid = 1; } @@ -857,7 +1219,7 @@ static void __exit fschmd_exit(void) i2c_del_driver(&fschmd_driver); } -MODULE_AUTHOR("Hans de Goede <j.w.r.degoede@hhs.nl>"); +MODULE_AUTHOR("Hans de Goede <hdegoede@redhat.com>"); MODULE_DESCRIPTION("FSC Poseidon, Hermes, Scylla, Heracles and " "Heimdall driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/i5k_amb.c b/drivers/hwmon/i5k_amb.c index 2ede9388096b..27d7f72a5f11 100644 --- a/drivers/hwmon/i5k_amb.c +++ b/drivers/hwmon/i5k_amb.c @@ -490,6 +490,13 @@ static unsigned long chipset_ids[] = { 0 }; +static struct pci_device_id i5k_amb_ids[] __devinitdata = { + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5000_ERR) }, + { PCI_DEVICE(PCI_VENDOR_ID_INTEL, PCI_DEVICE_ID_INTEL_5400_ERR) }, + { 0, } +}; +MODULE_DEVICE_TABLE(pci, i5k_amb_ids); + static int __devinit i5k_amb_probe(struct platform_device *pdev) { struct i5k_amb_data *data; diff --git a/drivers/hwmon/it87.c b/drivers/hwmon/it87.c index b74c95735f95..95a99c590da2 100644 --- a/drivers/hwmon/it87.c +++ b/drivers/hwmon/it87.c @@ -14,6 +14,7 @@ IT8712F Super I/O chip w/LPC interface IT8716F Super I/O chip w/LPC interface IT8718F Super I/O chip w/LPC interface + IT8720F Super I/O chip w/LPC interface IT8726F Super I/O chip w/LPC interface Sis950 A clone of the IT8705F @@ -48,11 +49,12 @@ #include <linux/sysfs.h> #include <linux/string.h> #include <linux/dmi.h> +#include <linux/acpi.h> #include <asm/io.h> #define DRVNAME "it87" -enum chips { it87, it8712, it8716, it8718 }; +enum chips { it87, it8712, it8716, it8718, it8720 }; static unsigned short force_id; module_param(force_id, ushort, 0); @@ -64,7 +66,10 @@ static struct platform_device *pdev; #define DEV 0x07 /* Register: Logical device select */ #define VAL 0x2f /* The value to read/write */ #define PME 0x04 /* The device with the fan registers in it */ -#define GPIO 0x07 /* The device with the IT8718F VID value in it */ + +/* The device with the IT8718F/IT8720F VID value in it */ +#define GPIO 0x07 + #define DEVID 0x20 /* Register: Device ID */ #define DEVREV 0x22 /* Register: Device Revision */ @@ -113,6 +118,7 @@ superio_exit(void) #define IT8705F_DEVID 0x8705 #define IT8716F_DEVID 0x8716 #define IT8718F_DEVID 0x8718 +#define IT8720F_DEVID 0x8720 #define IT8726F_DEVID 0x8726 #define IT87_ACT_REG 0x30 #define IT87_BASE_REG 0x60 @@ -150,8 +156,8 @@ static int fix_pwm_polarity; #define IT87_REG_ALARM2 0x02 #define IT87_REG_ALARM3 0x03 -/* The IT8718F has the VID value in a different register, in Super-I/O - configuration space. */ +/* The IT8718F and IT8720F have the VID value in a different register, in + Super-I/O configuration space. */ #define IT87_REG_VID 0x0a /* The IT8705F and IT8712F earlier than revision 0x08 use register 0x0b for fan divisors. Later IT8712F revisions must use 16-bit tachometer @@ -282,7 +288,8 @@ static inline int has_16bit_fans(const struct it87_data *data) return (data->type == it87 && data->revision >= 0x03) || (data->type == it8712 && data->revision >= 0x08) || data->type == it8716 - || data->type == it8718; + || data->type == it8718 + || data->type == it8720; } static int it87_probe(struct platform_device *pdev); @@ -992,6 +999,9 @@ static int __init it87_find(unsigned short *address, case IT8718F_DEVID: sio_data->type = it8718; break; + case IT8720F_DEVID: + sio_data->type = it8720; + break; case 0xffff: /* No device at all */ goto exit; default: @@ -1022,7 +1032,8 @@ static int __init it87_find(unsigned short *address, int reg; superio_select(GPIO); - if (chip_type == it8718) + if ((chip_type == it8718) || + (chip_type == it8720)) sio_data->vid_value = superio_inb(IT87_SIO_VID_REG); reg = superio_inb(IT87_SIO_PINX2_REG); @@ -1068,6 +1079,7 @@ static int __devinit it87_probe(struct platform_device *pdev) "it8712", "it8716", "it8718", + "it8720", }; res = platform_get_resource(pdev, IORESOURCE_IO, 0); @@ -1226,7 +1238,7 @@ static int __devinit it87_probe(struct platform_device *pdev) } if (data->type == it8712 || data->type == it8716 - || data->type == it8718) { + || data->type == it8718 || data->type == it8720) { data->vrm = vid_which_vrm(); /* VID reading from Super-I/O config space if available */ data->vid = sio_data->vid_value; @@ -1374,7 +1386,7 @@ static void __devinit it87_init_device(struct platform_device *pdev) it87_write_value(data, IT87_REG_TEMP_HIGH(i), 127); } - /* Check if temperature channnels are reset manually or by some reason */ + /* Check if temperature channels are reset manually or by some reason */ tmp = it87_read_value(data, IT87_REG_TEMP_ENABLE); if ((tmp & 0x3f) == 0) { /* Temp1,Temp3=thermistor; Temp2=thermal diode */ @@ -1513,7 +1525,8 @@ static struct it87_data *it87_update_device(struct device *dev) data->sensor = it87_read_value(data, IT87_REG_TEMP_ENABLE); /* The 8705 does not have VID capability. - The 8718 does not use IT87_REG_VID for the same purpose. */ + The 8718 and the 8720 don't use IT87_REG_VID for the + same purpose. */ if (data->type == it8712 || data->type == it8716) { data->vid = it87_read_value(data, IT87_REG_VID); /* The older IT8712F revisions had only 5 VID pins, @@ -1540,6 +1553,10 @@ static int __init it87_device_add(unsigned short address, }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc(DRVNAME, address); if (!pdev) { err = -ENOMEM; @@ -1608,7 +1625,7 @@ static void __exit sm_it87_exit(void) MODULE_AUTHOR("Chris Gauthron, " "Jean Delvare <khali@linux-fr.org>"); -MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8726F, SiS950 driver"); +MODULE_DESCRIPTION("IT8705F/8712F/8716F/8718F/8720F/8726F, SiS950 driver"); module_param(update_vbat, bool, 0); MODULE_PARM_DESC(update_vbat, "Update vbat if set else return powerup value"); module_param(fix_pwm_polarity, bool, 0); diff --git a/drivers/hwmon/lm70.c b/drivers/hwmon/lm70.c index d435f003292d..ae6204f33214 100644 --- a/drivers/hwmon/lm70.c +++ b/drivers/hwmon/lm70.c @@ -37,9 +37,13 @@ #define DRVNAME "lm70" +#define LM70_CHIP_LM70 0 /* original NS LM70 */ +#define LM70_CHIP_TMP121 1 /* TI TMP121/TMP123 */ + struct lm70 { struct device *hwmon_dev; struct mutex lock; + unsigned int chip; }; /* sysfs hook function */ @@ -47,7 +51,7 @@ static ssize_t lm70_sense_temp(struct device *dev, struct device_attribute *attr, char *buf) { struct spi_device *spi = to_spi_device(dev); - int status, val; + int status, val = 0; u8 rxbuf[2]; s16 raw=0; struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev); @@ -65,12 +69,12 @@ static ssize_t lm70_sense_temp(struct device *dev, "spi_write_then_read failed with status %d\n", status); goto out; } - dev_dbg(dev, "rxbuf[1] : 0x%x rxbuf[0] : 0x%x\n", rxbuf[1], rxbuf[0]); - - raw = (rxbuf[1] << 8) + rxbuf[0]; - dev_dbg(dev, "raw=0x%x\n", raw); + raw = (rxbuf[0] << 8) + rxbuf[1]; + dev_dbg(dev, "rxbuf[0] : 0x%02x rxbuf[1] : 0x%02x raw=0x%04x\n", + rxbuf[0], rxbuf[1], raw); /* + * LM70: * The "raw" temperature read into rxbuf[] is a 16-bit signed 2's * complement value. Only the MSB 11 bits (1 sign + 10 temperature * bits) are meaningful; the LSB 5 bits are to be discarded. @@ -80,8 +84,21 @@ static ssize_t lm70_sense_temp(struct device *dev, * by 0.25. Also multiply by 1000 to represent in millidegrees * Celsius. * So it's equivalent to multiplying by 0.25 * 1000 = 250. + * + * TMP121/TMP123: + * 13 bits of 2's complement data, discard LSB 3 bits, + * resolution 0.0625 degrees celsius. */ - val = ((int)raw/32) * 250; + switch (p_lm70->chip) { + case LM70_CHIP_LM70: + val = ((int)raw / 32) * 250; + break; + + case LM70_CHIP_TMP121: + val = ((int)raw / 8) * 625 / 10; + break; + } + status = sprintf(buf, "%d\n", val); /* millidegrees Celsius */ out: mutex_unlock(&p_lm70->lock); @@ -93,27 +110,39 @@ static DEVICE_ATTR(temp1_input, S_IRUGO, lm70_sense_temp, NULL); static ssize_t lm70_show_name(struct device *dev, struct device_attribute *devattr, char *buf) { - return sprintf(buf, "lm70\n"); + struct lm70 *p_lm70 = dev_get_drvdata(dev); + int ret; + + switch (p_lm70->chip) { + case LM70_CHIP_LM70: + ret = sprintf(buf, "lm70\n"); + break; + case LM70_CHIP_TMP121: + ret = sprintf(buf, "tmp121\n"); + break; + default: + ret = -EINVAL; + } + return ret; } static DEVICE_ATTR(name, S_IRUGO, lm70_show_name, NULL); /*----------------------------------------------------------------------*/ -static int __devinit lm70_probe(struct spi_device *spi) +static int __devinit common_probe(struct spi_device *spi, int chip) { struct lm70 *p_lm70; int status; - /* signaling is SPI_MODE_0 on a 3-wire link (shared SI/SO) */ - if ((spi->mode & (SPI_CPOL|SPI_CPHA)) || !(spi->mode & SPI_3WIRE)) - return -EINVAL; + /* NOTE: we assume 8-bit words, and convert to 16 bits manually */ p_lm70 = kzalloc(sizeof *p_lm70, GFP_KERNEL); if (!p_lm70) return -ENOMEM; mutex_init(&p_lm70->lock); + p_lm70->chip = chip; /* sysfs hook */ p_lm70->hwmon_dev = hwmon_device_register(&spi->dev); @@ -141,6 +170,24 @@ out_dev_reg_failed: return status; } +static int __devinit lm70_probe(struct spi_device *spi) +{ + /* signaling is SPI_MODE_0 on a 3-wire link (shared SI/SO) */ + if ((spi->mode & (SPI_CPOL | SPI_CPHA)) || !(spi->mode & SPI_3WIRE)) + return -EINVAL; + + return common_probe(spi, LM70_CHIP_LM70); +} + +static int __devinit tmp121_probe(struct spi_device *spi) +{ + /* signaling is SPI_MODE_0 with only MISO connected */ + if (spi->mode & (SPI_CPOL | SPI_CPHA)) + return -EINVAL; + + return common_probe(spi, LM70_CHIP_TMP121); +} + static int __devexit lm70_remove(struct spi_device *spi) { struct lm70 *p_lm70 = dev_get_drvdata(&spi->dev); @@ -154,6 +201,15 @@ static int __devexit lm70_remove(struct spi_device *spi) return 0; } +static struct spi_driver tmp121_driver = { + .driver = { + .name = "tmp121", + .owner = THIS_MODULE, + }, + .probe = tmp121_probe, + .remove = __devexit_p(lm70_remove), +}; + static struct spi_driver lm70_driver = { .driver = { .name = "lm70", @@ -165,17 +221,26 @@ static struct spi_driver lm70_driver = { static int __init init_lm70(void) { - return spi_register_driver(&lm70_driver); + int ret = spi_register_driver(&lm70_driver); + if (ret) + return ret; + + ret = spi_register_driver(&tmp121_driver); + if (ret) + spi_unregister_driver(&lm70_driver); + + return ret; } static void __exit cleanup_lm70(void) { spi_unregister_driver(&lm70_driver); + spi_unregister_driver(&tmp121_driver); } module_init(init_lm70); module_exit(cleanup_lm70); MODULE_AUTHOR("Kaiwan N Billimoria"); -MODULE_DESCRIPTION("National Semiconductor LM70 Linux driver"); +MODULE_DESCRIPTION("NS LM70 / TI TMP121/TMP123 Linux driver"); MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/ltc4245.c b/drivers/hwmon/ltc4245.c new file mode 100644 index 000000000000..034b2c515848 --- /dev/null +++ b/drivers/hwmon/ltc4245.c @@ -0,0 +1,567 @@ +/* + * Driver for Linear Technology LTC4245 I2C Multiple Supply Hot Swap Controller + * + * Copyright (C) 2008 Ira W. Snyder <iws@ovro.caltech.edu> + * + * 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 of the License. + * + * This driver is based on the ds1621 and ina209 drivers. + * + * Datasheet: + * http://www.linear.com/pc/downloadDocument.do?navId=H0,C1,C1003,C1006,C1140,P19392,D13517 + */ + +#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> + +/* Valid addresses are 0x20 - 0x3f + * + * For now, we do not probe, since some of these addresses + * are known to be unfriendly to probing */ +static const unsigned short normal_i2c[] = { I2C_CLIENT_END }; + +/* Insmod parameters */ +I2C_CLIENT_INSMOD_1(ltc4245); + +/* Here are names of the chip's registers (a.k.a. commands) */ +enum ltc4245_cmd { + LTC4245_STATUS = 0x00, /* readonly */ + LTC4245_ALERT = 0x01, + LTC4245_CONTROL = 0x02, + LTC4245_ON = 0x03, + LTC4245_FAULT1 = 0x04, + LTC4245_FAULT2 = 0x05, + LTC4245_GPIO = 0x06, + LTC4245_ADCADR = 0x07, + + LTC4245_12VIN = 0x10, + LTC4245_12VSENSE = 0x11, + LTC4245_12VOUT = 0x12, + LTC4245_5VIN = 0x13, + LTC4245_5VSENSE = 0x14, + LTC4245_5VOUT = 0x15, + LTC4245_3VIN = 0x16, + LTC4245_3VSENSE = 0x17, + LTC4245_3VOUT = 0x18, + LTC4245_VEEIN = 0x19, + LTC4245_VEESENSE = 0x1a, + LTC4245_VEEOUT = 0x1b, + LTC4245_GPIOADC1 = 0x1c, + LTC4245_GPIOADC2 = 0x1d, + LTC4245_GPIOADC3 = 0x1e, +}; + +struct ltc4245_data { + struct device *hwmon_dev; + + struct mutex update_lock; + bool valid; + unsigned long last_updated; /* in jiffies */ + + /* Control registers */ + u8 cregs[0x08]; + + /* Voltage registers */ + u8 vregs[0x0f]; +}; + +static struct ltc4245_data *ltc4245_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct ltc4245_data *data = i2c_get_clientdata(client); + s32 val; + int i; + + mutex_lock(&data->update_lock); + + if (time_after(jiffies, data->last_updated + HZ) || !data->valid) { + + dev_dbg(&client->dev, "Starting ltc4245 update\n"); + + /* Read control registers -- 0x00 to 0x07 */ + for (i = 0; i < ARRAY_SIZE(data->cregs); i++) { + val = i2c_smbus_read_byte_data(client, i); + if (unlikely(val < 0)) + data->cregs[i] = 0; + else + data->cregs[i] = val; + } + + /* Read voltage registers -- 0x10 to 0x1f */ + for (i = 0; i < ARRAY_SIZE(data->vregs); i++) { + val = i2c_smbus_read_byte_data(client, i+0x10); + if (unlikely(val < 0)) + data->vregs[i] = 0; + else + data->vregs[i] = val; + } + + data->last_updated = jiffies; + data->valid = 1; + } + + mutex_unlock(&data->update_lock); + + return data; +} + +/* Return the voltage from the given register in millivolts */ +static int ltc4245_get_voltage(struct device *dev, u8 reg) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 regval = data->vregs[reg - 0x10]; + u32 voltage = 0; + + switch (reg) { + case LTC4245_12VIN: + case LTC4245_12VOUT: + voltage = regval * 55; + break; + case LTC4245_5VIN: + case LTC4245_5VOUT: + voltage = regval * 22; + break; + case LTC4245_3VIN: + case LTC4245_3VOUT: + voltage = regval * 15; + break; + case LTC4245_VEEIN: + case LTC4245_VEEOUT: + voltage = regval * -55; + break; + case LTC4245_GPIOADC1: + case LTC4245_GPIOADC2: + case LTC4245_GPIOADC3: + voltage = regval * 10; + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + break; + } + + return voltage; +} + +/* Return the current in the given sense register in milliAmperes */ +static unsigned int ltc4245_get_current(struct device *dev, u8 reg) +{ + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 regval = data->vregs[reg - 0x10]; + unsigned int voltage; + unsigned int curr; + + /* The strange looking conversions that follow are fixed-point + * math, since we cannot do floating point in the kernel. + * + * Step 1: convert sense register to microVolts + * Step 2: convert voltage to milliAmperes + * + * If you play around with the V=IR equation, you come up with + * the following: X uV / Y mOhm == Z mA + * + * With the resistors that are fractions of a milliOhm, we multiply + * the voltage and resistance by 10, to shift the decimal point. + * Now we can use the normal division operator again. + */ + + switch (reg) { + case LTC4245_12VSENSE: + voltage = regval * 250; /* voltage in uV */ + curr = voltage / 50; /* sense resistor 50 mOhm */ + break; + case LTC4245_5VSENSE: + voltage = regval * 125; /* voltage in uV */ + curr = (voltage * 10) / 35; /* sense resistor 3.5 mOhm */ + break; + case LTC4245_3VSENSE: + voltage = regval * 125; /* voltage in uV */ + curr = (voltage * 10) / 25; /* sense resistor 2.5 mOhm */ + break; + case LTC4245_VEESENSE: + voltage = regval * 250; /* voltage in uV */ + curr = voltage / 100; /* sense resistor 100 mOhm */ + break; + default: + /* If we get here, the developer messed up */ + WARN_ON_ONCE(1); + curr = 0; + break; + } + + return curr; +} + +static ssize_t ltc4245_show_voltage(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const int voltage = ltc4245_get_voltage(dev, attr->index); + + return snprintf(buf, PAGE_SIZE, "%d\n", voltage); +} + +static ssize_t ltc4245_show_current(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const unsigned int curr = ltc4245_get_current(dev, attr->index); + + return snprintf(buf, PAGE_SIZE, "%u\n", curr); +} + +static ssize_t ltc4245_show_power(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute *attr = to_sensor_dev_attr(da); + const unsigned int curr = ltc4245_get_current(dev, attr->index); + const int output_voltage = ltc4245_get_voltage(dev, attr->index+1); + + /* current in mA * voltage in mV == power in uW */ + const unsigned int power = abs(output_voltage * curr); + + return snprintf(buf, PAGE_SIZE, "%u\n", power); +} + +static ssize_t ltc4245_show_alarm(struct device *dev, + struct device_attribute *da, + char *buf) +{ + struct sensor_device_attribute_2 *attr = to_sensor_dev_attr_2(da); + struct ltc4245_data *data = ltc4245_update_device(dev); + const u8 reg = data->cregs[attr->index]; + const u32 mask = attr->nr; + + return snprintf(buf, PAGE_SIZE, "%u\n", (reg & mask) ? 1 : 0); +} + +/* These macros are used below in constructing device attribute objects + * for use with sysfs_create_group() to make a sysfs device file + * for each register. + */ + +#define LTC4245_VOLTAGE(name, ltc4245_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_voltage, NULL, ltc4245_cmd_idx) + +#define LTC4245_CURRENT(name, ltc4245_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_current, NULL, ltc4245_cmd_idx) + +#define LTC4245_POWER(name, ltc4245_cmd_idx) \ + static SENSOR_DEVICE_ATTR(name, S_IRUGO, \ + ltc4245_show_power, NULL, ltc4245_cmd_idx) + +#define LTC4245_ALARM(name, mask, reg) \ + static SENSOR_DEVICE_ATTR_2(name, S_IRUGO, \ + ltc4245_show_alarm, NULL, (mask), reg) + +/* Construct a sensor_device_attribute structure for each register */ + +/* Input voltages */ +LTC4245_VOLTAGE(in1_input, LTC4245_12VIN); +LTC4245_VOLTAGE(in2_input, LTC4245_5VIN); +LTC4245_VOLTAGE(in3_input, LTC4245_3VIN); +LTC4245_VOLTAGE(in4_input, LTC4245_VEEIN); + +/* Input undervoltage alarms */ +LTC4245_ALARM(in1_min_alarm, (1 << 0), LTC4245_FAULT1); +LTC4245_ALARM(in2_min_alarm, (1 << 1), LTC4245_FAULT1); +LTC4245_ALARM(in3_min_alarm, (1 << 2), LTC4245_FAULT1); +LTC4245_ALARM(in4_min_alarm, (1 << 3), LTC4245_FAULT1); + +/* Currents (via sense resistor) */ +LTC4245_CURRENT(curr1_input, LTC4245_12VSENSE); +LTC4245_CURRENT(curr2_input, LTC4245_5VSENSE); +LTC4245_CURRENT(curr3_input, LTC4245_3VSENSE); +LTC4245_CURRENT(curr4_input, LTC4245_VEESENSE); + +/* Overcurrent alarms */ +LTC4245_ALARM(curr1_max_alarm, (1 << 4), LTC4245_FAULT1); +LTC4245_ALARM(curr2_max_alarm, (1 << 5), LTC4245_FAULT1); +LTC4245_ALARM(curr3_max_alarm, (1 << 6), LTC4245_FAULT1); +LTC4245_ALARM(curr4_max_alarm, (1 << 7), LTC4245_FAULT1); + +/* Output voltages */ +LTC4245_VOLTAGE(in5_input, LTC4245_12VOUT); +LTC4245_VOLTAGE(in6_input, LTC4245_5VOUT); +LTC4245_VOLTAGE(in7_input, LTC4245_3VOUT); +LTC4245_VOLTAGE(in8_input, LTC4245_VEEOUT); + +/* Power Bad alarms */ +LTC4245_ALARM(in5_min_alarm, (1 << 0), LTC4245_FAULT2); +LTC4245_ALARM(in6_min_alarm, (1 << 1), LTC4245_FAULT2); +LTC4245_ALARM(in7_min_alarm, (1 << 2), LTC4245_FAULT2); +LTC4245_ALARM(in8_min_alarm, (1 << 3), LTC4245_FAULT2); + +/* GPIO voltages */ +LTC4245_VOLTAGE(in9_input, LTC4245_GPIOADC1); +LTC4245_VOLTAGE(in10_input, LTC4245_GPIOADC2); +LTC4245_VOLTAGE(in11_input, LTC4245_GPIOADC3); + +/* Power Consumption (virtual) */ +LTC4245_POWER(power1_input, LTC4245_12VSENSE); +LTC4245_POWER(power2_input, LTC4245_5VSENSE); +LTC4245_POWER(power3_input, LTC4245_3VSENSE); +LTC4245_POWER(power4_input, LTC4245_VEESENSE); + +/* Finally, construct an array of pointers to members of the above objects, + * as required for sysfs_create_group() + */ +static struct attribute *ltc4245_attributes[] = { + &sensor_dev_attr_in1_input.dev_attr.attr, + &sensor_dev_attr_in2_input.dev_attr.attr, + &sensor_dev_attr_in3_input.dev_attr.attr, + &sensor_dev_attr_in4_input.dev_attr.attr, + + &sensor_dev_attr_in1_min_alarm.dev_attr.attr, + &sensor_dev_attr_in2_min_alarm.dev_attr.attr, + &sensor_dev_attr_in3_min_alarm.dev_attr.attr, + &sensor_dev_attr_in4_min_alarm.dev_attr.attr, + + &sensor_dev_attr_curr1_input.dev_attr.attr, + &sensor_dev_attr_curr2_input.dev_attr.attr, + &sensor_dev_attr_curr3_input.dev_attr.attr, + &sensor_dev_attr_curr4_input.dev_attr.attr, + + &sensor_dev_attr_curr1_max_alarm.dev_attr.attr, + &sensor_dev_attr_curr2_max_alarm.dev_attr.attr, + &sensor_dev_attr_curr3_max_alarm.dev_attr.attr, + &sensor_dev_attr_curr4_max_alarm.dev_attr.attr, + + &sensor_dev_attr_in5_input.dev_attr.attr, + &sensor_dev_attr_in6_input.dev_attr.attr, + &sensor_dev_attr_in7_input.dev_attr.attr, + &sensor_dev_attr_in8_input.dev_attr.attr, + + &sensor_dev_attr_in5_min_alarm.dev_attr.attr, + &sensor_dev_attr_in6_min_alarm.dev_attr.attr, + &sensor_dev_attr_in7_min_alarm.dev_attr.attr, + &sensor_dev_attr_in8_min_alarm.dev_attr.attr, + + &sensor_dev_attr_in9_input.dev_attr.attr, + &sensor_dev_attr_in10_input.dev_attr.attr, + &sensor_dev_attr_in11_input.dev_attr.attr, + + &sensor_dev_attr_power1_input.dev_attr.attr, + &sensor_dev_attr_power2_input.dev_attr.attr, + &sensor_dev_attr_power3_input.dev_attr.attr, + &sensor_dev_attr_power4_input.dev_attr.attr, + + NULL, +}; + +static const struct attribute_group ltc4245_group = { + .attrs = ltc4245_attributes, +}; + +static int ltc4245_probe(struct i2c_client *client, + const struct i2c_device_id *id) +{ + struct ltc4245_data *data; + int ret; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) { + ret = -ENOMEM; + goto out_kzalloc; + } + + i2c_set_clientdata(client, data); + mutex_init(&data->update_lock); + + /* Initialize the LTC4245 chip */ + /* TODO */ + + /* Register sysfs hooks */ + ret = sysfs_create_group(&client->dev.kobj, <c4245_group); + if (ret) + goto out_sysfs_create_group; + + data->hwmon_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->hwmon_dev)) { + ret = PTR_ERR(data->hwmon_dev); + goto out_hwmon_device_register; + } + + return 0; + +out_hwmon_device_register: + sysfs_remove_group(&client->dev.kobj, <c4245_group); +out_sysfs_create_group: + kfree(data); +out_kzalloc: + return ret; +} + +static int ltc4245_remove(struct i2c_client *client) +{ + struct ltc4245_data *data = i2c_get_clientdata(client); + + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&client->dev.kobj, <c4245_group); + + kfree(data); + + return 0; +} + +/* Check that some bits in a control register appear at all possible + * locations without changing value + * + * @client: the i2c client to use + * @reg: the register to read + * @bits: the bits to check (0xff checks all bits, + * 0x03 checks only the last two bits) + * + * return -ERRNO if the register read failed + * return -ENODEV if the register value doesn't stay constant at all + * possible addresses + * + * return 0 for success + */ +static int ltc4245_check_control_reg(struct i2c_client *client, u8 reg, u8 bits) +{ + int i; + s32 v, voff1, voff2; + + /* Read register and check for error */ + v = i2c_smbus_read_byte_data(client, reg); + if (v < 0) + return v; + + v &= bits; + + for (i = 0x00; i < 0xff; i += 0x20) { + + voff1 = i2c_smbus_read_byte_data(client, reg + i); + if (voff1 < 0) + return voff1; + + voff2 = i2c_smbus_read_byte_data(client, reg + i + 0x08); + if (voff2 < 0) + return voff2; + + voff1 &= bits; + voff2 &= bits; + + if (v != voff1 || v != voff2) + return -ENODEV; + } + + return 0; +} + +static int ltc4245_detect(struct i2c_client *client, + int kind, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + if (kind < 0) { /* probed detection - check the chip type */ + s32 v; /* 8 bits from the chip, or -ERRNO */ + + /* Chip registers 0x00-0x07 are control registers + * Chip registers 0x10-0x1f are data registers + * + * Address bits b7-b5 are ignored. This makes the chip "repeat" + * in steps of 0x20. Any control registers should appear with + * the same values across all duplicated addresses. + * + * Register 0x02 bit b2 is reserved, expect 0 + * Register 0x07 bits b7 to b4 are reserved, expect 0 + * + * Registers 0x01, 0x02 are control registers and should not + * change on their own. + * + * Register 0x06 bits b6 and b7 are control bits, and should + * not change on their own. + * + * Register 0x07 bits b3 to b0 are control bits, and should + * not change on their own. + */ + + /* read register 0x02 reserved bit, expect 0 */ + v = i2c_smbus_read_byte_data(client, LTC4245_CONTROL); + if (v < 0 || (v & 0x04) != 0) + return -ENODEV; + + /* read register 0x07 reserved bits, expect 0 */ + v = i2c_smbus_read_byte_data(client, LTC4245_ADCADR); + if (v < 0 || (v & 0xf0) != 0) + return -ENODEV; + + /* check that the alert register appears at all locations */ + if (ltc4245_check_control_reg(client, LTC4245_ALERT, 0xff)) + return -ENODEV; + + /* check that the control register appears at all locations */ + if (ltc4245_check_control_reg(client, LTC4245_CONTROL, 0xff)) + return -ENODEV; + + /* check that register 0x06 bits b6 and b7 stay constant */ + if (ltc4245_check_control_reg(client, LTC4245_GPIO, 0xc0)) + return -ENODEV; + + /* check that register 0x07 bits b3-b0 stay constant */ + if (ltc4245_check_control_reg(client, LTC4245_ADCADR, 0x0f)) + return -ENODEV; + } + + strlcpy(info->type, "ltc4245", I2C_NAME_SIZE); + dev_info(&adapter->dev, "ltc4245 %s at address 0x%02x\n", + kind < 0 ? "probed" : "forced", + client->addr); + + return 0; +} + +static const struct i2c_device_id ltc4245_id[] = { + { "ltc4245", ltc4245 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, ltc4245_id); + +/* This is the driver that will be inserted */ +static struct i2c_driver ltc4245_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "ltc4245", + }, + .probe = ltc4245_probe, + .remove = ltc4245_remove, + .id_table = ltc4245_id, + .detect = ltc4245_detect, + .address_data = &addr_data, +}; + +static int __init ltc4245_init(void) +{ + return i2c_add_driver(<c4245_driver); +} + +static void __exit ltc4245_exit(void) +{ + i2c_del_driver(<c4245_driver); +} + +MODULE_AUTHOR("Ira W. Snyder <iws@ovro.caltech.edu>"); +MODULE_DESCRIPTION("LTC4245 driver"); +MODULE_LICENSE("GPL"); + +module_init(ltc4245_init); +module_exit(ltc4245_exit); diff --git a/drivers/hwmon/pc87360.c b/drivers/hwmon/pc87360.c index 5fbfa34c110e..fb052fea3744 100644 --- a/drivers/hwmon/pc87360.c +++ b/drivers/hwmon/pc87360.c @@ -43,6 +43,7 @@ #include <linux/hwmon-vid.h> #include <linux/err.h> #include <linux/mutex.h> +#include <linux/acpi.h> #include <asm/io.h> static u8 devid; @@ -1627,6 +1628,11 @@ static int __init pc87360_device_add(unsigned short address) continue; res.start = extra_isa[i]; res.end = extra_isa[i] + PC87360_EXTENT - 1; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit_device_put; + err = platform_device_add_resources(pdev, &res, 1); if (err) { printk(KERN_ERR "pc87360: Device resource[%d] " diff --git a/drivers/hwmon/pc87427.c b/drivers/hwmon/pc87427.c index 7265f22ae5cd..3a8a0f7a7736 100644 --- a/drivers/hwmon/pc87427.c +++ b/drivers/hwmon/pc87427.c @@ -32,6 +32,7 @@ #include <linux/mutex.h> #include <linux/sysfs.h> #include <linux/ioport.h> +#include <linux/acpi.h> #include <asm/io.h> static unsigned short force_id; @@ -524,6 +525,10 @@ static int __init pc87427_device_add(unsigned short address) }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc(DRVNAME, address); if (!pdev) { err = -ENOMEM; diff --git a/drivers/hwmon/sis5595.c b/drivers/hwmon/sis5595.c index a276806f3d53..aa2e8318f167 100644 --- a/drivers/hwmon/sis5595.c +++ b/drivers/hwmon/sis5595.c @@ -62,6 +62,7 @@ #include <linux/jiffies.h> #include <linux/mutex.h> #include <linux/sysfs.h> +#include <linux/acpi.h> #include <asm/io.h> @@ -727,6 +728,10 @@ static int __devinit sis5595_device_add(unsigned short address) }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc("sis5595", address); if (!pdev) { err = -ENOMEM; diff --git a/drivers/hwmon/smsc47b397.c b/drivers/hwmon/smsc47b397.c index eb03544c731c..6f6d52b4fb64 100644 --- a/drivers/hwmon/smsc47b397.c +++ b/drivers/hwmon/smsc47b397.c @@ -36,6 +36,7 @@ #include <linux/err.h> #include <linux/init.h> #include <linux/mutex.h> +#include <linux/acpi.h> #include <asm/io.h> static unsigned short force_id; @@ -303,6 +304,10 @@ static int __init smsc47b397_device_add(unsigned short address) }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc(DRVNAME, address); if (!pdev) { err = -ENOMEM; diff --git a/drivers/hwmon/smsc47m1.c b/drivers/hwmon/smsc47m1.c index d1b498548736..a92dbb97ee99 100644 --- a/drivers/hwmon/smsc47m1.c +++ b/drivers/hwmon/smsc47m1.c @@ -37,6 +37,7 @@ #include <linux/init.h> #include <linux/mutex.h> #include <linux/sysfs.h> +#include <linux/acpi.h> #include <asm/io.h> static unsigned short force_id; @@ -705,6 +706,10 @@ static int __init smsc47m1_device_add(unsigned short address, }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc(DRVNAME, address); if (!pdev) { err = -ENOMEM; diff --git a/drivers/hwmon/via686a.c b/drivers/hwmon/via686a.c index f1ee5e731968..a022aedcaacb 100644 --- a/drivers/hwmon/via686a.c +++ b/drivers/hwmon/via686a.c @@ -41,6 +41,7 @@ #include <linux/init.h> #include <linux/mutex.h> #include <linux/sysfs.h> +#include <linux/acpi.h> #include <asm/io.h> @@ -783,6 +784,10 @@ static int __devinit via686a_device_add(unsigned short address) }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc("via686a", address); if (!pdev) { err = -ENOMEM; diff --git a/drivers/hwmon/vt1211.c b/drivers/hwmon/vt1211.c index 12b43590fa53..b0ce37852281 100644 --- a/drivers/hwmon/vt1211.c +++ b/drivers/hwmon/vt1211.c @@ -32,6 +32,7 @@ #include <linux/err.h> #include <linux/mutex.h> #include <linux/ioport.h> +#include <linux/acpi.h> #include <asm/io.h> static int uch_config = -1; @@ -1259,6 +1260,10 @@ static int __init vt1211_device_add(unsigned short address) } res.name = pdev->name; + err = acpi_check_resource_conflict(&res); + if (err) + goto EXIT; + err = platform_device_add_resources(pdev, &res, 1); if (err) { printk(KERN_ERR DRVNAME ": Device resource addition failed " diff --git a/drivers/hwmon/vt8231.c b/drivers/hwmon/vt8231.c index 5bc57275cae8..9982b45fbb14 100644 --- a/drivers/hwmon/vt8231.c +++ b/drivers/hwmon/vt8231.c @@ -35,6 +35,7 @@ #include <linux/hwmon-vid.h> #include <linux/err.h> #include <linux/mutex.h> +#include <linux/acpi.h> #include <asm/io.h> static int force_addr; @@ -894,6 +895,10 @@ static int __devinit vt8231_device_add(unsigned short address) }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc("vt8231", address); if (!pdev) { err = -ENOMEM; diff --git a/drivers/hwmon/w83627ehf.c b/drivers/hwmon/w83627ehf.c index 075164dd65a7..cb808d015361 100644 --- a/drivers/hwmon/w83627ehf.c +++ b/drivers/hwmon/w83627ehf.c @@ -48,6 +48,7 @@ #include <linux/hwmon-vid.h> #include <linux/err.h> #include <linux/mutex.h> +#include <linux/acpi.h> #include <asm/io.h> #include "lm75.h" @@ -502,7 +503,7 @@ static struct w83627ehf_data *w83627ehf_update_device(struct device *dev) } for (i = 0; i < 4; i++) { - /* pwmcfg, tolarance mapped for i=0, i=1 to same reg */ + /* pwmcfg, tolerance mapped for i=0, i=1 to same reg */ if (i != 1) { pwmcfg = w83627ehf_read_value(data, W83627EHF_REG_PWM_ENABLE[i]); @@ -1544,6 +1545,11 @@ static int __init sensors_w83627ehf_init(void) res.start = address + IOREGION_OFFSET; res.end = address + IOREGION_OFFSET + IOREGION_LENGTH - 1; res.flags = IORESOURCE_IO; + + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + err = platform_device_add_resources(pdev, &res, 1); if (err) { printk(KERN_ERR DRVNAME ": Device resource addition failed " diff --git a/drivers/hwmon/w83627hf.c b/drivers/hwmon/w83627hf.c index b30e5796cb26..389150ba30d3 100644 --- a/drivers/hwmon/w83627hf.c +++ b/drivers/hwmon/w83627hf.c @@ -50,6 +50,7 @@ #include <linux/err.h> #include <linux/mutex.h> #include <linux/ioport.h> +#include <linux/acpi.h> #include <asm/io.h> #include "lm75.h" @@ -1793,6 +1794,10 @@ static int __init w83627hf_device_add(unsigned short address, }; int err; + err = acpi_check_resource_conflict(&res); + if (err) + goto exit; + pdev = platform_device_alloc(DRVNAME, address); if (!pdev) { err = -ENOMEM; diff --git a/drivers/hwmon/w83781d.c b/drivers/hwmon/w83781d.c index fc12bd412e3a..dbfb30c588d8 100644 --- a/drivers/hwmon/w83781d.c +++ b/drivers/hwmon/w83781d.c @@ -58,7 +58,10 @@ static const unsigned short normal_i2c[] = { 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, I2C_CLIENT_END }; /* Insmod parameters */ I2C_CLIENT_INSMOD_4(w83781d, w83782d, w83783s, as99127f); -I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: " + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, "List of subclient addresses: " "{bus, clientaddr, subclientaddr1, subclientaddr2}"); static int reset; diff --git a/drivers/hwmon/w83791d.c b/drivers/hwmon/w83791d.c index 5768def8a4f2..97851c5ba3a3 100644 --- a/drivers/hwmon/w83791d.c +++ b/drivers/hwmon/w83791d.c @@ -53,7 +53,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, /* Insmod parameters */ I2C_CLIENT_INSMOD_1(w83791d); -I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: " + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, "List of subclient addresses: " "{bus, clientaddr, subclientaddr1, subclientaddr2}"); static int reset; diff --git a/drivers/hwmon/w83792d.c b/drivers/hwmon/w83792d.c index cf94c5b0c879..2be16194ddf3 100644 --- a/drivers/hwmon/w83792d.c +++ b/drivers/hwmon/w83792d.c @@ -51,7 +51,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, /* Insmod parameters */ I2C_CLIENT_INSMOD_1(w83792d); -I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: " + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, "List of subclient addresses: " "{bus, clientaddr, subclientaddr1, subclientaddr2}"); static int init; diff --git a/drivers/hwmon/w83793.c b/drivers/hwmon/w83793.c index 0a739f1c69be..47dd398f7258 100644 --- a/drivers/hwmon/w83793.c +++ b/drivers/hwmon/w83793.c @@ -42,7 +42,10 @@ static const unsigned short normal_i2c[] = { 0x2c, 0x2d, 0x2e, 0x2f, /* Insmod parameters */ I2C_CLIENT_INSMOD_1(w83793); -I2C_CLIENT_MODULE_PARM(force_subclients, "List of subclient addresses: " + +static unsigned short force_subclients[4]; +module_param_array(force_subclients, short, NULL, 0); +MODULE_PARM_DESC(force_subclients, "List of subclient addresses: " "{bus, clientaddr, subclientaddr1, subclientaddr2}"); static int reset; diff --git a/drivers/i2c/busses/i2c-ali1563.c b/drivers/i2c/busses/i2c-ali1563.c index fc3e5b026423..dd9e796fad69 100644 --- a/drivers/i2c/busses/i2c-ali1563.c +++ b/drivers/i2c/busses/i2c-ali1563.c @@ -399,8 +399,8 @@ static int __devinit ali1563_probe(struct pci_dev * dev, if ((error = ali1563_setup(dev))) goto exit; ali1563_adapter.dev.parent = &dev->dev; - sprintf(ali1563_adapter.name,"SMBus ALi 1563 Adapter @ %04x", - ali1563_smba); + snprintf(ali1563_adapter.name, sizeof(ali1563_adapter.name), + "SMBus ALi 1563 Adapter @ %04x", ali1563_smba); if ((error = i2c_add_adapter(&ali1563_adapter))) goto exit_shutdown; return 0; diff --git a/drivers/i2c/busses/i2c-amd756-s4882.c b/drivers/i2c/busses/i2c-amd756-s4882.c index 8ba2bcf727d3..378fcb5d5783 100644 --- a/drivers/i2c/busses/i2c-amd756-s4882.c +++ b/drivers/i2c/busses/i2c-amd756-s4882.c @@ -197,8 +197,8 @@ static int __init amd756_s4882_init(void) for (i = 1; i < 5; i++) { s4882_algo[i] = *(amd756_smbus.algo); s4882_adapter[i] = amd756_smbus; - sprintf(s4882_adapter[i].name, - "SMBus 8111 adapter (CPU%d)", i-1); + snprintf(s4882_adapter[i].name, sizeof(s4882_adapter[i].name), + "SMBus 8111 adapter (CPU%d)", i-1); s4882_adapter[i].algo = s4882_algo+i; s4882_adapter[i].dev.parent = amd756_smbus.dev.parent; } diff --git a/drivers/i2c/busses/i2c-amd756.c b/drivers/i2c/busses/i2c-amd756.c index 424dad6f18d8..36bee5b9c952 100644 --- a/drivers/i2c/busses/i2c-amd756.c +++ b/drivers/i2c/busses/i2c-amd756.c @@ -380,8 +380,9 @@ static int __devinit amd756_probe(struct pci_dev *pdev, /* set up the sysfs linkage to our parent device */ amd756_smbus.dev.parent = &pdev->dev; - sprintf(amd756_smbus.name, "SMBus %s adapter at %04x", - chipname[id->driver_data], amd756_ioport); + snprintf(amd756_smbus.name, sizeof(amd756_smbus.name), + "SMBus %s adapter at %04x", chipname[id->driver_data], + amd756_ioport); error = i2c_add_adapter(&amd756_smbus); if (error) { diff --git a/drivers/i2c/busses/i2c-at91.c b/drivers/i2c/busses/i2c-at91.c index 9efb02137254..67d9dc5b351b 100644 --- a/drivers/i2c/busses/i2c-at91.c +++ b/drivers/i2c/busses/i2c-at91.c @@ -222,7 +222,7 @@ static int __devinit at91_i2c_probe(struct platform_device *pdev) rc = -ENOMEM; goto fail2; } - sprintf(adapter->name, "AT91"); + snprintf(adapter->name, sizeof(adapter->name), "AT91"); adapter->algo = &at91_algorithm; adapter->class = I2C_CLASS_HWMON; adapter->dev.parent = &pdev->dev; diff --git a/drivers/i2c/busses/i2c-bfin-twi.c b/drivers/i2c/busses/i2c-bfin-twi.c index 3c855ff2992f..3fd2c417c1e0 100644 --- a/drivers/i2c/busses/i2c-bfin-twi.c +++ b/drivers/i2c/busses/i2c-bfin-twi.c @@ -656,7 +656,7 @@ static int i2c_bfin_twi_probe(struct platform_device *pdev) strlcpy(p_adap->name, pdev->name, sizeof(p_adap->name)); p_adap->algo = &bfin_twi_algorithm; p_adap->algo_data = iface; - p_adap->class = I2C_CLASS_ALL; + p_adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; p_adap->dev.parent = &pdev->dev; rc = peripheral_request_list(pin_req[pdev->id], "i2c-bfin-twi"); diff --git a/drivers/i2c/busses/i2c-i801.c b/drivers/i2c/busses/i2c-i801.c index 5123eb69a971..526625eaa84b 100644 --- a/drivers/i2c/busses/i2c-i801.c +++ b/drivers/i2c/busses/i2c-i801.c @@ -64,7 +64,7 @@ #include <linux/init.h> #include <linux/i2c.h> #include <linux/acpi.h> -#include <asm/io.h> +#include <linux/io.h> /* I801 SMBus address offsets */ #define SMBHSTSTS (0 + i801_smba) @@ -583,6 +583,40 @@ static struct pci_device_id i801_ids[] = { MODULE_DEVICE_TABLE (pci, i801_ids); +#if defined CONFIG_INPUT_APANEL || defined CONFIG_INPUT_APANEL_MODULE +static unsigned char apanel_addr; + +/* Scan the system ROM for the signature "FJKEYINF" */ +static __init const void __iomem *bios_signature(const void __iomem *bios) +{ + ssize_t offset; + const unsigned char signature[] = "FJKEYINF"; + + for (offset = 0; offset < 0x10000; offset += 0x10) { + if (check_signature(bios + offset, signature, + sizeof(signature)-1)) + return bios + offset; + } + return NULL; +} + +static void __init input_apanel_init(void) +{ + void __iomem *bios; + const void __iomem *p; + + bios = ioremap(0xF0000, 0x10000); /* Can't fail */ + p = bios_signature(bios); + if (p) { + /* just use the first address */ + apanel_addr = readb(p + 8 + 3) >> 1; + } + iounmap(bios); +} +#else +static void __init input_apanel_init(void) {} +#endif + static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id *id) { unsigned char temp; @@ -667,6 +701,19 @@ static int __devinit i801_probe(struct pci_dev *dev, const struct pci_device_id dev_err(&dev->dev, "Failed to add SMBus adapter\n"); goto exit_release; } + + /* Register optional slaves */ +#if defined CONFIG_INPUT_APANEL || defined CONFIG_INPUT_APANEL_MODULE + if (apanel_addr) { + struct i2c_board_info info; + + memset(&info, 0, sizeof(struct i2c_board_info)); + info.addr = apanel_addr; + strlcpy(info.type, "fujitsu_apanel", I2C_NAME_SIZE); + i2c_new_device(&i801_adapter, &info); + } +#endif + return 0; exit_release: @@ -717,6 +764,7 @@ static struct pci_driver i801_driver = { static int __init i2c_i801_init(void) { + input_apanel_init(); return pci_register_driver(&i801_driver); } diff --git a/drivers/i2c/busses/i2c-pxa.c b/drivers/i2c/busses/i2c-pxa.c index 587f5b2380d4..6af68146c342 100644 --- a/drivers/i2c/busses/i2c-pxa.c +++ b/drivers/i2c/busses/i2c-pxa.c @@ -1076,10 +1076,10 @@ static int i2c_pxa_probe(struct platform_device *dev) #ifdef CONFIG_I2C_PXA_SLAVE printk(KERN_INFO "I2C: %s: PXA I2C adapter, slave address %d\n", - i2c->adap.dev.bus_id, i2c->slave_addr); + dev_name(&i2c->adap.dev), i2c->slave_addr); #else printk(KERN_INFO "I2C: %s: PXA I2C adapter\n", - i2c->adap.dev.bus_id); + dev_name(&i2c->adap.dev)); #endif return 0; diff --git a/drivers/i2c/busses/i2c-s3c2410.c b/drivers/i2c/busses/i2c-s3c2410.c index f69f91ffb469..5b7f95641ba4 100644 --- a/drivers/i2c/busses/i2c-s3c2410.c +++ b/drivers/i2c/busses/i2c-s3c2410.c @@ -906,7 +906,7 @@ static int s3c24xx_i2c_probe(struct platform_device *pdev) platform_set_drvdata(pdev, i2c); - dev_info(&pdev->dev, "%s: S3C I2C adapter\n", i2c->adap.dev.bus_id); + dev_info(&pdev->dev, "%s: S3C I2C adapter\n", dev_name(&i2c->adap.dev)); return 0; err_cpufreq: diff --git a/drivers/i2c/busses/i2c-sh7760.c b/drivers/i2c/busses/i2c-sh7760.c index 5e0e254976de..baa28b73ae42 100644 --- a/drivers/i2c/busses/i2c-sh7760.c +++ b/drivers/i2c/busses/i2c-sh7760.c @@ -475,7 +475,7 @@ static int __devinit sh7760_i2c_probe(struct platform_device *pdev) id->adap.nr = pdev->id; id->adap.algo = &sh7760_i2c_algo; - id->adap.class = I2C_CLASS_ALL; + id->adap.class = I2C_CLASS_HWMON | I2C_CLASS_SPD; id->adap.retries = 3; id->adap.algo_data = id; id->adap.dev.parent = &pdev->dev; diff --git a/drivers/i2c/busses/i2c-sh_mobile.c b/drivers/i2c/busses/i2c-sh_mobile.c index 6c3d60b939bf..1c01083b01b5 100644 --- a/drivers/i2c/busses/i2c-sh_mobile.c +++ b/drivers/i2c/busses/i2c-sh_mobile.c @@ -500,7 +500,7 @@ static int sh_mobile_i2c_hook_irqs(struct platform_device *dev, int hook) while ((res = platform_get_resource(dev, IORESOURCE_IRQ, k))) { for (n = res->start; hook && n <= res->end; n++) { if (request_irq(n, sh_mobile_i2c_isr, IRQF_DISABLED, - dev->dev.bus_id, dev)) + dev_name(&dev->dev), dev)) goto rollback; } k++; diff --git a/drivers/i2c/busses/i2c-sis5595.c b/drivers/i2c/busses/i2c-sis5595.c index dfc2d5eb6a68..8ce2daff985c 100644 --- a/drivers/i2c/busses/i2c-sis5595.c +++ b/drivers/i2c/busses/i2c-sis5595.c @@ -389,8 +389,8 @@ static int __devinit sis5595_probe(struct pci_dev *dev, const struct pci_device_ /* set up the sysfs linkage to our parent device */ sis5595_adapter.dev.parent = &dev->dev; - sprintf(sis5595_adapter.name, "SMBus SIS5595 adapter at %04x", - sis5595_base + SMB_INDEX); + snprintf(sis5595_adapter.name, sizeof(sis5595_adapter.name), + "SMBus SIS5595 adapter at %04x", sis5595_base + SMB_INDEX); err = i2c_add_adapter(&sis5595_adapter); if (err) { release_region(sis5595_base + SMB_INDEX, 2); diff --git a/drivers/i2c/busses/i2c-sis630.c b/drivers/i2c/busses/i2c-sis630.c index e7c4b790da54..9c9c016ff2b5 100644 --- a/drivers/i2c/busses/i2c-sis630.c +++ b/drivers/i2c/busses/i2c-sis630.c @@ -487,8 +487,8 @@ static int __devinit sis630_probe(struct pci_dev *dev, const struct pci_device_i /* set up the sysfs linkage to our parent device */ sis630_adapter.dev.parent = &dev->dev; - sprintf(sis630_adapter.name, "SMBus SIS630 adapter at %04x", - acpi_base + SMB_STS); + snprintf(sis630_adapter.name, sizeof(sis630_adapter.name), + "SMBus SIS630 adapter at %04x", acpi_base + SMB_STS); return i2c_add_adapter(&sis630_adapter); } diff --git a/drivers/i2c/chips/Kconfig b/drivers/i2c/chips/Kconfig index 864ac561fdbb..59c3d23f5bdc 100644 --- a/drivers/i2c/chips/Kconfig +++ b/drivers/i2c/chips/Kconfig @@ -114,18 +114,6 @@ config SENSORS_PCF8591 These devices are hard to detect and rarely found on mainstream hardware. If unsure, say N. -config ISP1301_OMAP - tristate "Philips ISP1301 with OMAP OTG" - depends on ARCH_OMAP_OTG - help - If you say yes here you get support for the Philips ISP1301 - USB-On-The-Go transceiver working with the OMAP OTG controller. - The ISP1301 is used in products including H2 and H3 development - boards for Texas Instruments OMAP processors. - - This driver can also be built as a module. If so, the module - will be called isp1301_omap. - config SENSORS_MAX6875 tristate "Maxim MAX6875 Power supply supervisor" depends on EXPERIMENTAL diff --git a/drivers/i2c/chips/Makefile b/drivers/i2c/chips/Makefile index 8b95f41a5001..83accaaf8164 100644 --- a/drivers/i2c/chips/Makefile +++ b/drivers/i2c/chips/Makefile @@ -18,7 +18,6 @@ obj-$(CONFIG_SENSORS_PCA9539) += pca9539.o obj-$(CONFIG_SENSORS_PCF8574) += pcf8574.o obj-$(CONFIG_PCF8575) += pcf8575.o obj-$(CONFIG_SENSORS_PCF8591) += pcf8591.o -obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o obj-$(CONFIG_SENSORS_TSL2550) += tsl2550.o obj-$(CONFIG_MCU_MPC8349EMITX) += mcu_mpc8349emitx.o diff --git a/drivers/i2c/i2c-core.c b/drivers/i2c/i2c-core.c index c6a63f46bc15..b1c9abe24c7b 100644 --- a/drivers/i2c/i2c-core.c +++ b/drivers/i2c/i2c-core.c @@ -459,7 +459,7 @@ static int i2c_register_adapter(struct i2c_adapter *adap) pr_debug("I2C adapter driver [%s] forgot to specify " "physical device\n", adap->name); } - sprintf(adap->dev.bus_id, "i2c-%d", adap->nr); + dev_set_name(&adap->dev, "i2c-%d", adap->nr); adap->dev.release = &i2c_adapter_dev_release; adap->dev.class = &i2c_adapter_class; res = device_register(&adap->dev); @@ -845,8 +845,8 @@ int i2c_attach_client(struct i2c_client *client) } else client->dev.release = i2c_client_dev_release; - snprintf(&client->dev.bus_id[0], sizeof(client->dev.bus_id), - "%d-%04x", i2c_adapter_id(adapter), client->addr); + dev_set_name(&client->dev, "%d-%04x", i2c_adapter_id(adapter), + client->addr); res = device_register(&client->dev); if (res) goto out_err; @@ -856,7 +856,7 @@ int i2c_attach_client(struct i2c_client *client) mutex_unlock(&adapter->clist_lock); dev_dbg(&adapter->dev, "client [%s] registered with bus id %s\n", - client->name, client->dev.bus_id); + client->name, dev_name(&client->dev)); if (adapter->client_register) { if (adapter->client_register(client)) { diff --git a/drivers/ieee1394/csr.c b/drivers/ieee1394/csr.c index c90be4070e40..31400c8ae051 100644 --- a/drivers/ieee1394/csr.c +++ b/drivers/ieee1394/csr.c @@ -68,22 +68,22 @@ static struct hpsb_highlevel csr_highlevel = { .host_reset = host_reset, }; -static struct hpsb_address_ops map_ops = { +const static struct hpsb_address_ops map_ops = { .read = read_maps, }; -static struct hpsb_address_ops fcp_ops = { +const static struct hpsb_address_ops fcp_ops = { .write = write_fcp, }; -static struct hpsb_address_ops reg_ops = { +const static struct hpsb_address_ops reg_ops = { .read = read_regs, .write = write_regs, .lock = lock_regs, .lock64 = lock64_regs, }; -static struct hpsb_address_ops config_rom_ops = { +const static struct hpsb_address_ops config_rom_ops = { .read = read_config_rom, }; @@ -217,7 +217,7 @@ static void add_host(struct hpsb_host *host) host->csr.generation = 2; - bus_info[1] = __constant_cpu_to_be32(0x31333934); + bus_info[1] = IEEE1394_BUSID_MAGIC; bus_info[2] = cpu_to_be32((hpsb_disable_irm ? 0 : 1 << CSR_IRMC_SHIFT) | (1 << CSR_CMC_SHIFT) | (1 << CSR_ISC_SHIFT) | @@ -250,7 +250,7 @@ static void remove_host(struct hpsb_host *host) { quadlet_t bus_info[CSR_BUS_INFO_SIZE]; - bus_info[1] = __constant_cpu_to_be32(0x31333934); + bus_info[1] = IEEE1394_BUSID_MAGIC; bus_info[2] = cpu_to_be32((0 << CSR_IRMC_SHIFT) | (0 << CSR_CMC_SHIFT) | (0 << CSR_ISC_SHIFT) | diff --git a/drivers/ieee1394/csr.h b/drivers/ieee1394/csr.h index f11546550d84..90fb3f2192c3 100644 --- a/drivers/ieee1394/csr.h +++ b/drivers/ieee1394/csr.h @@ -50,11 +50,11 @@ #define CSR_MAX_ROM_SHIFT 8 #define CSR_GENERATION_SHIFT 4 -#define CSR_SET_BUS_INFO_GENERATION(csr, gen) \ - ((csr)->bus_info_data[2] = \ - cpu_to_be32((be32_to_cpu((csr)->bus_info_data[2]) & \ - ~(0xf << CSR_GENERATION_SHIFT)) | \ - (gen) << CSR_GENERATION_SHIFT)) +static inline void csr_set_bus_info_generation(struct csr1212_csr *csr, u8 gen) +{ + csr->bus_info_data[2] &= ~cpu_to_be32(0xf << CSR_GENERATION_SHIFT); + csr->bus_info_data[2] |= cpu_to_be32((u32)gen << CSR_GENERATION_SHIFT); +} struct csr_control { spinlock_t lock; diff --git a/drivers/ieee1394/csr1212.c b/drivers/ieee1394/csr1212.c index 5e38a68b8af2..a6dfeb0b3372 100644 --- a/drivers/ieee1394/csr1212.c +++ b/drivers/ieee1394/csr1212.c @@ -1077,15 +1077,10 @@ static int csr1212_parse_bus_info_block(struct csr1212_csr *csr) int i; int ret; - /* IEEE 1212 says that the entire bus info block should be readable in - * a single transaction regardless of the max_rom value. - * Unfortunately, many IEEE 1394 devices do not abide by that, so the - * bus info block will be read 1 quadlet at a time. The rest of the - * ConfigROM will be read according to the max_rom field. */ for (i = 0; i < csr->bus_info_len; i += sizeof(u32)) { ret = csr->ops->bus_read(csr, CSR1212_CONFIG_ROM_SPACE_BASE + i, - sizeof(u32), &csr->cache_head->data[bytes_to_quads(i)], - csr->private); + &csr->cache_head->data[bytes_to_quads(i)], + csr->private); if (ret != CSR1212_SUCCESS) return ret; @@ -1104,8 +1099,8 @@ static int csr1212_parse_bus_info_block(struct csr1212_csr *csr) * a time. */ for (i = csr->bus_info_len; i <= csr->crc_len; i += sizeof(u32)) { ret = csr->ops->bus_read(csr, CSR1212_CONFIG_ROM_SPACE_BASE + i, - sizeof(u32), &csr->cache_head->data[bytes_to_quads(i)], - csr->private); + &csr->cache_head->data[bytes_to_quads(i)], + csr->private); if (ret != CSR1212_SUCCESS) return ret; } @@ -1289,7 +1284,7 @@ csr1212_read_keyval(struct csr1212_csr *csr, struct csr1212_keyval *kv) if (csr->ops->bus_read(csr, CSR1212_REGISTER_SPACE_BASE + kv->offset, - sizeof(u32), &q, csr->private)) + &q, csr->private)) return -EIO; kv->value.leaf.len = be32_to_cpu(q) >> 16; @@ -1372,17 +1367,8 @@ csr1212_read_keyval(struct csr1212_csr *csr, struct csr1212_keyval *kv) addr = (CSR1212_CSR_ARCH_REG_SPACE_BASE + cache->offset + cr->offset_end) & ~(csr->max_rom - 1); - if (csr->ops->bus_read(csr, addr, csr->max_rom, cache_ptr, - csr->private)) { - if (csr->max_rom == 4) - /* We've got problems! */ - return -EIO; - - /* Apperently the max_rom value was a lie, set it to - * do quadlet reads and try again. */ - csr->max_rom = 4; - continue; - } + if (csr->ops->bus_read(csr, addr, cache_ptr, csr->private)) + return -EIO; cr->offset_end += csr->max_rom - (cr->offset_end & (csr->max_rom - 1)); @@ -1433,7 +1419,6 @@ csr1212_get_keyval(struct csr1212_csr *csr, struct csr1212_keyval *kv) int csr1212_parse_csr(struct csr1212_csr *csr) { - static const int mr_map[] = { 4, 64, 1024, 0 }; struct csr1212_dentry *dentry; int ret; @@ -1443,15 +1428,13 @@ int csr1212_parse_csr(struct csr1212_csr *csr) if (ret != CSR1212_SUCCESS) return ret; - if (!csr->ops->get_max_rom) { - csr->max_rom = mr_map[0]; /* default value */ - } else { - int i = csr->ops->get_max_rom(csr->bus_info_data, - csr->private); - if (i & ~0x3) - return -EINVAL; - csr->max_rom = mr_map[i]; - } + /* + * There has been a buggy firmware with bus_info_block.max_rom > 0 + * spotted which actually only supported quadlet read requests to the + * config ROM. Therefore read everything quadlet by quadlet regardless + * of what the bus info block says. + */ + csr->max_rom = 4; csr->cache_head->layout_head = csr->root_kv; csr->cache_head->layout_tail = csr->root_kv; diff --git a/drivers/ieee1394/csr1212.h b/drivers/ieee1394/csr1212.h index 043039fc63ec..a892d922dbc9 100644 --- a/drivers/ieee1394/csr1212.h +++ b/drivers/ieee1394/csr1212.h @@ -181,7 +181,7 @@ struct csr1212_csr_rom_cache { struct csr1212_csr { size_t bus_info_len; /* bus info block length in bytes */ size_t crc_len; /* crc length in bytes */ - u32 *bus_info_data; /* bus info data incl bus name and EUI */ + __be32 *bus_info_data; /* bus info data incl bus name and EUI */ void *private; /* private, bus specific data */ struct csr1212_bus_ops *ops; @@ -200,7 +200,7 @@ struct csr1212_bus_ops { * entries located in the Units Space. Must return 0 on success * anything else indicates an error. */ int (*bus_read) (struct csr1212_csr *csr, u64 addr, - u16 length, void *buffer, void *private); + void *buffer, void *private); /* This function is used by csr1212 to allocate a region in units space * in the event that Config ROM entries don't all fit in the predefined @@ -211,11 +211,6 @@ struct csr1212_bus_ops { /* This function is used by csr1212 to release a region in units space * that is no longer needed. */ void (*release_addr) (u64 addr, void *private); - - /* This function is used by csr1212 to determine the max read request - * supported by a remote node when reading the ConfigROM space. Must - * return 0, 1, or 2 per IEEE 1212. */ - int (*get_max_rom) (u32 *bus_info, void *private); }; diff --git a/drivers/ieee1394/dv1394-private.h b/drivers/ieee1394/dv1394-private.h index 7d1d2845b420..18b92cbf4a9f 100644 --- a/drivers/ieee1394/dv1394-private.h +++ b/drivers/ieee1394/dv1394-private.h @@ -77,11 +77,11 @@ static inline void fill_cip_header(struct CIP_header *cip, See the Texas Instruments OHCI 1394 chipset documentation. */ -struct output_more_immediate { u32 q[8]; }; -struct output_more { u32 q[4]; }; -struct output_last { u32 q[4]; }; -struct input_more { u32 q[4]; }; -struct input_last { u32 q[4]; }; +struct output_more_immediate { __le32 q[8]; }; +struct output_more { __le32 q[4]; }; +struct output_last { __le32 q[4]; }; +struct input_more { __le32 q[4]; }; +struct input_last { __le32 q[4]; }; /* outputs */ @@ -92,9 +92,9 @@ static inline void fill_output_more_immediate(struct output_more_immediate *omi, unsigned int payload_size) { omi->q[0] = cpu_to_le32(0x02000000 | 8); /* OUTPUT_MORE_IMMEDIATE; 8 is the size of the IT header */ - omi->q[1] = 0; - omi->q[2] = 0; - omi->q[3] = 0; + omi->q[1] = cpu_to_le32(0); + omi->q[2] = cpu_to_le32(0); + omi->q[3] = cpu_to_le32(0); /* IT packet header */ omi->q[4] = cpu_to_le32( (0x0 << 16) /* IEEE1394_SPEED_100 */ @@ -106,8 +106,8 @@ static inline void fill_output_more_immediate(struct output_more_immediate *omi, /* reserved field; mimic behavior of my Sony DSR-40 */ omi->q[5] = cpu_to_le32((payload_size << 16) | (0x7F << 8) | 0xA0); - omi->q[6] = 0; - omi->q[7] = 0; + omi->q[6] = cpu_to_le32(0); + omi->q[7] = cpu_to_le32(0); } static inline void fill_output_more(struct output_more *om, @@ -116,8 +116,8 @@ static inline void fill_output_more(struct output_more *om, { om->q[0] = cpu_to_le32(data_size); om->q[1] = cpu_to_le32(data_phys_addr); - om->q[2] = 0; - om->q[3] = 0; + om->q[2] = cpu_to_le32(0); + om->q[3] = cpu_to_le32(0); } static inline void fill_output_last(struct output_last *ol, @@ -140,8 +140,8 @@ static inline void fill_output_last(struct output_last *ol, ol->q[0] = cpu_to_le32(temp); ol->q[1] = cpu_to_le32(data_phys_addr); - ol->q[2] = 0; - ol->q[3] = 0; + ol->q[2] = cpu_to_le32(0); + ol->q[3] = cpu_to_le32(0); } /* inputs */ @@ -161,8 +161,8 @@ static inline void fill_input_more(struct input_more *im, im->q[0] = cpu_to_le32(temp); im->q[1] = cpu_to_le32(data_phys_addr); - im->q[2] = 0; /* branchAddress and Z not use in packet-per-buffer mode */ - im->q[3] = 0; /* xferStatus & resCount, resCount must be initialize to data_size */ + im->q[2] = cpu_to_le32(0); /* branchAddress and Z not use in packet-per-buffer mode */ + im->q[3] = cpu_to_le32(0); /* xferStatus & resCount, resCount must be initialize to data_size */ } static inline void fill_input_last(struct input_last *il, @@ -331,7 +331,7 @@ struct frame { /* points to status/timestamp field of first DMA packet */ /* (we'll check it later to monitor timestamp accuracy) */ - u32 *frame_begin_timestamp; + __le32 *frame_begin_timestamp; /* the timestamp we assigned to the first packet in the frame */ u32 assigned_timestamp; @@ -348,15 +348,15 @@ struct frame { that can cause interrupts. We'll check these from the interrupt handler. */ - u32 *mid_frame_timestamp; - u32 *frame_end_timestamp; + __le32 *mid_frame_timestamp; + __le32 *frame_end_timestamp; /* branch address field of final packet. This is effectively the "tail" in the chain of DMA descriptor blocks. We will fill it with the address of the first DMA descriptor block in the subsequent frame, once it is ready. */ - u32 *frame_end_branch; + __le32 *frame_end_branch; /* the number of descriptors in the first descriptor block of the frame. Needed to start DMA */ @@ -365,10 +365,10 @@ struct frame { struct packet { - u16 timestamp; + __le16 timestamp; u16 invalid; u16 iso_header; - u16 data_length; + __le16 data_length; u32 cip_h1; u32 cip_h2; unsigned char data[480]; diff --git a/drivers/ieee1394/dv1394.c b/drivers/ieee1394/dv1394.c index c19f23267157..a329e6bd5d2d 100644 --- a/drivers/ieee1394/dv1394.c +++ b/drivers/ieee1394/dv1394.c @@ -265,7 +265,7 @@ static void frame_prepare(struct video_card *video, unsigned int this_frame) /* these flags denote packets that need special attention */ int empty_packet, first_packet, last_packet, mid_packet; - u32 *branch_address, *last_branch_address = NULL; + __le32 *branch_address, *last_branch_address = NULL; unsigned long data_p; int first_packet_empty = 0; u32 cycleTimer, ct_sec, ct_cyc, ct_off; @@ -848,7 +848,7 @@ static void receive_packets(struct video_card *video) dma_addr_t block_dma = 0; struct packet *data = NULL; dma_addr_t data_dma = 0; - u32 *last_branch_address = NULL; + __le32 *last_branch_address = NULL; unsigned long irq_flags; int want_interrupt = 0; struct frame *f = NULL; @@ -2110,17 +2110,17 @@ static void ir_tasklet_func(unsigned long data) f = video->frames[next_i / MAX_PACKETS]; next = &(f->descriptor_pool[next_i % MAX_PACKETS]); next_dma = ((unsigned long) block - (unsigned long) f->descriptor_pool) + f->descriptor_pool_dma; - next->u.in.il.q[0] |= 3 << 20; /* enable interrupt */ - next->u.in.il.q[2] = 0; /* disable branch */ + next->u.in.il.q[0] |= cpu_to_le32(3 << 20); /* enable interrupt */ + next->u.in.il.q[2] = cpu_to_le32(0); /* disable branch */ /* link previous to next */ prev_i = (next_i == 0) ? (MAX_PACKETS * video->n_frames - 1) : (next_i - 1); f = video->frames[prev_i / MAX_PACKETS]; prev = &(f->descriptor_pool[prev_i % MAX_PACKETS]); if (prev_i % (MAX_PACKETS/2)) { - prev->u.in.il.q[0] &= ~(3 << 20); /* no interrupt */ + prev->u.in.il.q[0] &= ~cpu_to_le32(3 << 20); /* no interrupt */ } else { - prev->u.in.il.q[0] |= 3 << 20; /* enable interrupt */ + prev->u.in.il.q[0] |= cpu_to_le32(3 << 20); /* enable interrupt */ } prev->u.in.il.q[2] = cpu_to_le32(next_dma | 1); /* set Z=1 */ wmb(); diff --git a/drivers/ieee1394/eth1394.c b/drivers/ieee1394/eth1394.c index 20128692b339..a074bfd5f825 100644 --- a/drivers/ieee1394/eth1394.c +++ b/drivers/ieee1394/eth1394.c @@ -92,7 +92,7 @@ struct partial_datagram { struct list_head list; u16 dgl; u16 dg_size; - u16 ether_type; + __be16 ether_type; struct sk_buff *skb; char *pbuf; struct list_head frag_info; @@ -181,7 +181,7 @@ static void ether1394_remove_host(struct hpsb_host *host); static void ether1394_host_reset(struct hpsb_host *host); /* Function for incoming 1394 packets */ -static struct hpsb_address_ops addr_ops = { +const static struct hpsb_address_ops addr_ops = { .write = ether1394_write, }; @@ -767,7 +767,7 @@ static int ether1394_header_parse(const struct sk_buff *skb, static int ether1394_header_cache(const struct neighbour *neigh, struct hh_cache *hh) { - unsigned short type = hh->hh_type; + __be16 type = hh->hh_type; struct net_device *dev = neigh->dev; struct eth1394hdr *eth = (struct eth1394hdr *)((u8 *)hh->hh_data + 16 - ETH1394_HLEN); @@ -795,7 +795,7 @@ static void ether1394_header_cache_update(struct hh_cache *hh, ******************************************/ /* Copied from net/ethernet/eth.c */ -static u16 ether1394_type_trans(struct sk_buff *skb, struct net_device *dev) +static __be16 ether1394_type_trans(struct sk_buff *skb, struct net_device *dev) { struct eth1394hdr *eth; unsigned char *rawp; @@ -829,17 +829,17 @@ static u16 ether1394_type_trans(struct sk_buff *skb, struct net_device *dev) /* Parse an encapsulated IP1394 header into an ethernet frame packet. * We also perform ARP translation here, if need be. */ -static u16 ether1394_parse_encap(struct sk_buff *skb, struct net_device *dev, +static __be16 ether1394_parse_encap(struct sk_buff *skb, struct net_device *dev, nodeid_t srcid, nodeid_t destid, - u16 ether_type) + __be16 ether_type) { struct eth1394_priv *priv = netdev_priv(dev); - u64 dest_hw; - unsigned short ret = 0; + __be64 dest_hw; + __be16 ret = 0; /* Setup our hw addresses. We use these to build the ethernet header. */ if (destid == (LOCAL_BUS | ALL_NODES)) - dest_hw = ~0ULL; /* broadcast */ + dest_hw = ~cpu_to_be64(0); /* broadcast */ else dest_hw = cpu_to_be64((u64)priv->host->csr.guid_hi << 32 | priv->host->csr.guid_lo); @@ -873,7 +873,7 @@ static u16 ether1394_parse_encap(struct sk_buff *skb, struct net_device *dev, node = eth1394_find_node_guid(&priv->ip_node_list, be64_to_cpu(guid)); if (!node) - return 0; + return cpu_to_be16(0); node_info = (struct eth1394_node_info *)node->ud->device.driver_data; @@ -1063,7 +1063,7 @@ static int ether1394_data_handler(struct net_device *dev, int srcid, int destid, unsigned long flags; struct eth1394_priv *priv = netdev_priv(dev); union eth1394_hdr *hdr = (union eth1394_hdr *)buf; - u16 ether_type = 0; /* initialized to clear warning */ + __be16 ether_type = cpu_to_be16(0); /* initialized to clear warning */ int hdr_len; struct unit_directory *ud = priv->ud_list[NODEID_TO_NODE(srcid)]; struct eth1394_node_info *node_info; @@ -1259,7 +1259,7 @@ static int ether1394_write(struct hpsb_host *host, int srcid, int destid, static void ether1394_iso(struct hpsb_iso *iso) { - quadlet_t *data; + __be32 *data; char *buf; struct eth1394_host_info *hi; struct net_device *dev; @@ -1283,7 +1283,7 @@ static void ether1394_iso(struct hpsb_iso *iso) for (i = 0; i < nready; i++) { struct hpsb_iso_packet_info *info = &iso->infos[(iso->first_packet + i) % iso->buf_packets]; - data = (quadlet_t *)(iso->data_buf.kvirt + info->offset); + data = (__be32 *)(iso->data_buf.kvirt + info->offset); /* skip over GASP header */ buf = (char *)data + 8; @@ -1614,7 +1614,7 @@ static int ether1394_tx(struct sk_buff *skb, struct net_device *dev) if (max_payload < dg_size + hdr_type_len[ETH1394_HDR_LF_UF]) priv->bc_dgl++; } else { - __be64 guid = get_unaligned((u64 *)hdr_buf.h_dest); + __be64 guid = get_unaligned((__be64 *)hdr_buf.h_dest); node = eth1394_find_node_guid(&priv->ip_node_list, be64_to_cpu(guid)); diff --git a/drivers/ieee1394/eth1394.h b/drivers/ieee1394/eth1394.h index 4f3e2dd46f00..e1b5ea80f623 100644 --- a/drivers/ieee1394/eth1394.h +++ b/drivers/ieee1394/eth1394.h @@ -82,7 +82,7 @@ struct eth1394_priv { struct eth1394hdr { unsigned char h_dest[ETH1394_ALEN]; /* destination eth1394 addr */ - unsigned short h_proto; /* packet type ID field */ + __be16 h_proto; /* packet type ID field */ } __attribute__((packed)); static inline struct eth1394hdr *eth1394_hdr(const struct sk_buff *skb) @@ -99,13 +99,13 @@ typedef enum {ETH1394_GASP, ETH1394_WRREQ} eth1394_tx_type; struct eth1394_uf_hdr { u16 lf:2; u16 res:14; - u16 ether_type; /* Ethernet packet type */ + __be16 ether_type; /* Ethernet packet type */ } __attribute__((packed)); #elif defined __LITTLE_ENDIAN_BITFIELD struct eth1394_uf_hdr { u16 res:14; u16 lf:2; - u16 ether_type; + __be16 ether_type; } __attribute__((packed)); #else #error Unknown bit field type @@ -117,7 +117,7 @@ struct eth1394_ff_hdr { u16 lf:2; u16 res1:2; u16 dg_size:12; /* Datagram size */ - u16 ether_type; /* Ethernet packet type */ + __be16 ether_type; /* Ethernet packet type */ u16 dgl; /* Datagram label */ u16 res2; } __attribute__((packed)); @@ -126,7 +126,7 @@ struct eth1394_ff_hdr { u16 dg_size:12; u16 res1:2; u16 lf:2; - u16 ether_type; + __be16 ether_type; u16 dgl; u16 res2; } __attribute__((packed)); @@ -207,11 +207,11 @@ struct eth1394_arp { u16 opcode; /* ARP Opcode */ /* Above is exactly the same format as struct arphdr */ - u64 s_uniq_id; /* Sender's 64bit EUI */ + __be64 s_uniq_id; /* Sender's 64bit EUI */ u8 max_rec; /* Sender's max packet size */ u8 sspd; /* Sender's max speed */ - u16 fifo_hi; /* hi 16bits of sender's FIFO addr */ - u32 fifo_lo; /* lo 32bits of sender's FIFO addr */ + __be16 fifo_hi; /* hi 16bits of sender's FIFO addr */ + __be32 fifo_lo; /* lo 32bits of sender's FIFO addr */ u32 sip; /* Sender's IP Address */ u32 tip; /* IP Address of requested hw addr */ }; diff --git a/drivers/ieee1394/highlevel.c b/drivers/ieee1394/highlevel.c index 272543a42a43..600e391c8fe7 100644 --- a/drivers/ieee1394/highlevel.c +++ b/drivers/ieee1394/highlevel.c @@ -320,7 +320,7 @@ void hpsb_unregister_highlevel(struct hpsb_highlevel *hl) */ u64 hpsb_allocate_and_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, - struct hpsb_address_ops *ops, + const struct hpsb_address_ops *ops, u64 size, u64 alignment, u64 start, u64 end) { @@ -407,7 +407,8 @@ u64 hpsb_allocate_and_register_addrspace(struct hpsb_highlevel *hl, * are automatically deallocated together with the hpsb_highlevel @hl. */ int hpsb_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, - struct hpsb_address_ops *ops, u64 start, u64 end) + const struct hpsb_address_ops *ops, + u64 start, u64 end) { struct hpsb_address_serve *as; struct list_head *lh; @@ -420,7 +421,7 @@ int hpsb_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, return 0; } - as = kmalloc(sizeof(*as), GFP_ATOMIC); + as = kmalloc(sizeof(*as), GFP_KERNEL); if (!as) return 0; @@ -477,7 +478,7 @@ int hpsb_unregister_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, return retval; } -static struct hpsb_address_ops dummy_ops; +const static struct hpsb_address_ops dummy_ops; /* dummy address spaces as lower and upper bounds of the host's a.s. list */ static void init_hpsb_highlevel(struct hpsb_host *host) diff --git a/drivers/ieee1394/highlevel.h b/drivers/ieee1394/highlevel.h index bc5d0854c17e..9dba89fc60ad 100644 --- a/drivers/ieee1394/highlevel.h +++ b/drivers/ieee1394/highlevel.h @@ -15,7 +15,7 @@ struct hpsb_host; struct hpsb_address_serve { struct list_head host_list; /* per host list */ struct list_head hl_list; /* hpsb_highlevel list */ - struct hpsb_address_ops *op; + const struct hpsb_address_ops *op; struct hpsb_host *host; u64 start; /* first address handled, quadlet aligned */ u64 end; /* first address behind, quadlet aligned */ @@ -119,11 +119,12 @@ void hpsb_unregister_highlevel(struct hpsb_highlevel *hl); u64 hpsb_allocate_and_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, - struct hpsb_address_ops *ops, + const struct hpsb_address_ops *ops, u64 size, u64 alignment, u64 start, u64 end); int hpsb_register_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, - struct hpsb_address_ops *ops, u64 start, u64 end); + const struct hpsb_address_ops *ops, + u64 start, u64 end); int hpsb_unregister_addrspace(struct hpsb_highlevel *hl, struct hpsb_host *host, u64 start); diff --git a/drivers/ieee1394/hosts.c b/drivers/ieee1394/hosts.c index 237d0c9d69c6..e947d8ffac85 100644 --- a/drivers/ieee1394/hosts.c +++ b/drivers/ieee1394/hosts.c @@ -34,18 +34,18 @@ static void delayed_reset_bus(struct work_struct *work) { struct hpsb_host *host = container_of(work, struct hpsb_host, delayed_reset.work); - int generation = host->csr.generation + 1; + u8 generation = host->csr.generation + 1; /* The generation field rolls over to 2 rather than 0 per IEEE * 1394a-2000. */ if (generation > 0xf || generation < 2) generation = 2; - CSR_SET_BUS_INFO_GENERATION(host->csr.rom, generation); + csr_set_bus_info_generation(host->csr.rom, generation); if (csr1212_generate_csr_image(host->csr.rom) != CSR1212_SUCCESS) { /* CSR image creation failed. * Reset generation field and do not issue a bus reset. */ - CSR_SET_BUS_INFO_GENERATION(host->csr.rom, + csr_set_bus_info_generation(host->csr.rom, host->csr.generation); return; } diff --git a/drivers/ieee1394/hosts.h b/drivers/ieee1394/hosts.h index dd229950acca..49c359022c54 100644 --- a/drivers/ieee1394/hosts.h +++ b/drivers/ieee1394/hosts.h @@ -154,7 +154,7 @@ struct hpsb_host_driver { * to set the hardware ConfigROM if the hardware supports handling * reads to the ConfigROM on its own. */ void (*set_hw_config_rom)(struct hpsb_host *host, - quadlet_t *config_rom); + __be32 *config_rom); /* This function shall implement packet transmission based on * packet->type. It shall CRC both parts of the packet (unless diff --git a/drivers/ieee1394/ieee1394.h b/drivers/ieee1394/ieee1394.h index 40492074c013..e0ae0d3d747f 100644 --- a/drivers/ieee1394/ieee1394.h +++ b/drivers/ieee1394/ieee1394.h @@ -121,6 +121,9 @@ extern const char *hpsb_speedto_str[]; #include <asm/byteorder.h> +/* '1' '3' '9' '4' in ASCII */ +#define IEEE1394_BUSID_MAGIC cpu_to_be32(0x31333934) + #ifdef __BIG_ENDIAN_BITFIELD struct selfid { diff --git a/drivers/ieee1394/nodemgr.c b/drivers/ieee1394/nodemgr.c index 79ef5fd928ae..906c5a98d814 100644 --- a/drivers/ieee1394/nodemgr.c +++ b/drivers/ieee1394/nodemgr.c @@ -67,7 +67,7 @@ static int nodemgr_check_speed(struct nodemgr_csr_info *ci, u64 addr, for (i = IEEE1394_SPEED_100; i <= old_speed; i++) { *speed = i; error = hpsb_read(ci->host, ci->nodeid, ci->generation, addr, - &q, sizeof(quadlet_t)); + &q, 4); if (error) break; *buffer = q; @@ -85,7 +85,7 @@ static int nodemgr_check_speed(struct nodemgr_csr_info *ci, u64 addr, return error; } -static int nodemgr_bus_read(struct csr1212_csr *csr, u64 addr, u16 length, +static int nodemgr_bus_read(struct csr1212_csr *csr, u64 addr, void *buffer, void *__ci) { struct nodemgr_csr_info *ci = (struct nodemgr_csr_info*)__ci; @@ -93,7 +93,7 @@ static int nodemgr_bus_read(struct csr1212_csr *csr, u64 addr, u16 length, for (i = 1; ; i++) { error = hpsb_read(ci->host, ci->nodeid, ci->generation, addr, - buffer, length); + buffer, 4); if (!error) { ci->speed_unverified = 0; break; @@ -104,7 +104,7 @@ static int nodemgr_bus_read(struct csr1212_csr *csr, u64 addr, u16 length, /* The ieee1394_core guessed the node's speed capability from * the self ID. Check whether a lower speed works. */ - if (ci->speed_unverified && length == sizeof(quadlet_t)) { + if (ci->speed_unverified) { error = nodemgr_check_speed(ci, addr, buffer); if (!error) break; @@ -115,20 +115,8 @@ static int nodemgr_bus_read(struct csr1212_csr *csr, u64 addr, u16 length, return error; } -#define OUI_FREECOM_TECHNOLOGIES_GMBH 0x0001db - -static int nodemgr_get_max_rom(quadlet_t *bus_info_data, void *__ci) -{ - /* Freecom FireWire Hard Drive firmware bug */ - if (be32_to_cpu(bus_info_data[3]) >> 8 == OUI_FREECOM_TECHNOLOGIES_GMBH) - return 0; - - return (be32_to_cpu(bus_info_data[2]) >> 8) & 0x3; -} - static struct csr1212_bus_ops nodemgr_csr_ops = { .bus_read = nodemgr_bus_read, - .get_max_rom = nodemgr_get_max_rom }; diff --git a/drivers/ieee1394/nodemgr.h b/drivers/ieee1394/nodemgr.h index 4f287a3561ba..15ea09733e84 100644 --- a/drivers/ieee1394/nodemgr.h +++ b/drivers/ieee1394/nodemgr.h @@ -31,9 +31,6 @@ struct csr1212_keyval; struct hpsb_host; struct ieee1394_device_id; -/* '1' '3' '9' '4' in ASCII */ -#define IEEE1394_BUSID_MAGIC __constant_cpu_to_be32(0x31333934) - /* This is the start of a Node entry structure. It should be a stable API * for which to gather info from the Node Manager about devices attached * to the bus. */ diff --git a/drivers/ieee1394/ohci1394.c b/drivers/ieee1394/ohci1394.c index e509e13cb7a7..65c1429e4129 100644 --- a/drivers/ieee1394/ohci1394.c +++ b/drivers/ieee1394/ohci1394.c @@ -2973,7 +2973,7 @@ alloc_dma_trm_ctx(struct ti_ohci *ohci, struct dma_trm_ctx *d, return 0; } -static void ohci_set_hw_config_rom(struct hpsb_host *host, quadlet_t *config_rom) +static void ohci_set_hw_config_rom(struct hpsb_host *host, __be32 *config_rom) { struct ti_ohci *ohci = host->hostdata; @@ -3199,15 +3199,16 @@ static int __devinit ohci1394_pci_probe(struct pci_dev *dev, /* Now enable LPS, which we need in order to start accessing * most of the registers. In fact, on some cards (ALI M5251), * accessing registers in the SClk domain without LPS enabled - * will lock up the machine. Wait 50msec to make sure we have - * full link enabled. */ + * will lock up the machine. */ reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_LPS); /* Disable and clear interrupts */ reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); - mdelay(50); + /* Flush MMIO writes and wait to make sure we have full link enabled. */ + reg_read(ohci, OHCI1394_Version); + msleep(50); /* Determine the number of available IR and IT contexts. */ ohci->nb_iso_rcv_ctx = @@ -3233,8 +3234,9 @@ static int __devinit ohci1394_pci_probe(struct pci_dev *dev, * we need to get to that "no event", so enough should be initialized * by that point. */ - if (request_irq(dev->irq, ohci_irq_handler, IRQF_SHARED, - OHCI1394_DRIVER_NAME, ohci)) { + err = request_irq(dev->irq, ohci_irq_handler, IRQF_SHARED, + OHCI1394_DRIVER_NAME, ohci); + if (err) { PRINT_G(KERN_ERR, "Failed to allocate interrupt %d", dev->irq); goto err; } @@ -3381,6 +3383,7 @@ static int ohci1394_pci_suspend(struct pci_dev *dev, pm_message_t state) ohci_devctl(ohci->host, RESET_BUS, LONG_RESET_NO_FORCE_ROOT); ohci_soft_reset(ohci); + free_irq(dev->irq, ohci); err = pci_save_state(dev); if (err) { PRINT(KERN_ERR, "pci_save_state failed with %d", err); @@ -3420,7 +3423,16 @@ static int ohci1394_pci_resume(struct pci_dev *dev) reg_write(ohci, OHCI1394_HCControlSet, OHCI1394_HCControl_LPS); reg_write(ohci, OHCI1394_IntEventClear, 0xffffffff); reg_write(ohci, OHCI1394_IntMaskClear, 0xffffffff); - mdelay(50); + reg_read(ohci, OHCI1394_Version); + msleep(50); + + err = request_irq(dev->irq, ohci_irq_handler, IRQF_SHARED, + OHCI1394_DRIVER_NAME, ohci); + if (err) { + PRINT_G(KERN_ERR, "Failed to allocate interrupt %d", dev->irq); + return err; + } + ohci_initialize(ohci); hpsb_resume_host(ohci->host); diff --git a/drivers/ieee1394/pcilynx.c b/drivers/ieee1394/pcilynx.c index 7aee1ac97c80..dc15cadb06ef 100644 --- a/drivers/ieee1394/pcilynx.c +++ b/drivers/ieee1394/pcilynx.c @@ -1463,7 +1463,7 @@ static int __devinit add_card(struct pci_dev *dev, /* info_length, crc_length and 1394 magic number to check, if it is really a bus info block */ if (((be32_to_cpu(lynx->bus_info_block[0]) & 0xffff0000) == 0x04040000) && - (lynx->bus_info_block[1] == __constant_cpu_to_be32(0x31333934))) + (lynx->bus_info_block[1] == IEEE1394_BUSID_MAGIC)) { PRINT(KERN_DEBUG, lynx->id, "read a valid bus info block from"); } else { diff --git a/drivers/ieee1394/pcilynx.h b/drivers/ieee1394/pcilynx.h index ec27321f6724..693a169acea3 100644 --- a/drivers/ieee1394/pcilynx.h +++ b/drivers/ieee1394/pcilynx.h @@ -52,7 +52,7 @@ struct ti_lynx { void __iomem *local_rom; void __iomem *local_ram; void __iomem *aux_port; - quadlet_t bus_info_block[5]; + __be32 bus_info_block[5]; /* * use local RAM of LOCALRAM_SIZE bytes for PCLs, which allows for diff --git a/drivers/ieee1394/raw1394.c b/drivers/ieee1394/raw1394.c index bf7e761c12b1..bad66c65b0d6 100644 --- a/drivers/ieee1394/raw1394.c +++ b/drivers/ieee1394/raw1394.c @@ -90,7 +90,7 @@ static int arm_lock(struct hpsb_host *host, int nodeid, quadlet_t * store, static int arm_lock64(struct hpsb_host *host, int nodeid, octlet_t * store, u64 addr, octlet_t data, octlet_t arg, int ext_tcode, u16 flags); -static struct hpsb_address_ops arm_ops = { +const static struct hpsb_address_ops arm_ops = { .read = arm_read, .write = arm_write, .lock = arm_lock, diff --git a/drivers/ieee1394/sbp2.c b/drivers/ieee1394/sbp2.c index a373c18cf7b8..ab1034ccb7fb 100644 --- a/drivers/ieee1394/sbp2.c +++ b/drivers/ieee1394/sbp2.c @@ -265,7 +265,7 @@ static struct hpsb_highlevel sbp2_highlevel = { .host_reset = sbp2_host_reset, }; -static struct hpsb_address_ops sbp2_ops = { +const static struct hpsb_address_ops sbp2_ops = { .write = sbp2_handle_status_write }; @@ -275,7 +275,7 @@ static int sbp2_handle_physdma_write(struct hpsb_host *, int, int, quadlet_t *, static int sbp2_handle_physdma_read(struct hpsb_host *, int, quadlet_t *, u64, size_t, u16); -static struct hpsb_address_ops sbp2_physdma_ops = { +const static struct hpsb_address_ops sbp2_physdma_ops = { .read = sbp2_handle_physdma_read, .write = sbp2_handle_physdma_write, }; diff --git a/drivers/infiniband/hw/mlx4/cq.c b/drivers/infiniband/hw/mlx4/cq.c index a3c5af1d7ec0..de5263beab4a 100644 --- a/drivers/infiniband/hw/mlx4/cq.c +++ b/drivers/infiniband/hw/mlx4/cq.c @@ -367,7 +367,7 @@ int mlx4_ib_resize_cq(struct ib_cq *ibcq, int entries, struct ib_udata *udata) if (err) goto out; } else { - /* Can't be smaller then the number of outstanding CQEs */ + /* Can't be smaller than the number of outstanding CQEs */ outst_cqe = mlx4_ib_get_outstanding_cqes(cq); if (entries < outst_cqe + 1) { err = 0; diff --git a/drivers/input/keyboard/atkbd.c b/drivers/input/keyboard/atkbd.c index f6e9f39a527b..c3c8b9bc40ae 100644 --- a/drivers/input/keyboard/atkbd.c +++ b/drivers/input/keyboard/atkbd.c @@ -65,7 +65,7 @@ MODULE_PARM_DESC(extra, "Enable extra LEDs and keys on IBM RapidAcces, EzKey and /* * Scancode to keycode tables. These are just the default setting, and - * are loadable via an userland utility. + * are loadable via a userland utility. */ static const unsigned short atkbd_set2_keycode[512] = { diff --git a/drivers/input/misc/apanel.c b/drivers/input/misc/apanel.c index d82f7f727f7a..71b82434264d 100644 --- a/drivers/input/misc/apanel.c +++ b/drivers/input/misc/apanel.c @@ -57,7 +57,7 @@ static enum apanel_chip device_chip[APANEL_DEV_MAX]; struct apanel { struct input_polled_dev *ipdev; - struct i2c_client client; + struct i2c_client *client; unsigned short keymap[MAX_PANEL_KEYS]; u16 nkeys; u16 led_bits; @@ -66,16 +66,7 @@ struct apanel { }; -static int apanel_probe(struct i2c_adapter *, int, int); - -/* for now, we only support one address */ -static unsigned short normal_i2c[] = {0, I2C_CLIENT_END}; -static unsigned short ignore = I2C_CLIENT_END; -static struct i2c_client_address_data addr_data = { - .normal_i2c = normal_i2c, - .probe = &ignore, - .ignore = &ignore, -}; +static int apanel_probe(struct i2c_client *, const struct i2c_device_id *); static void report_key(struct input_dev *input, unsigned keycode) { @@ -103,12 +94,12 @@ static void apanel_poll(struct input_polled_dev *ipdev) s32 data; int i; - data = i2c_smbus_read_word_data(&ap->client, cmd); + data = i2c_smbus_read_word_data(ap->client, cmd); if (data < 0) return; /* ignore errors (due to ACPI??) */ /* write back to clear latch */ - i2c_smbus_write_word_data(&ap->client, cmd, 0); + i2c_smbus_write_word_data(ap->client, cmd, 0); if (!data) return; @@ -124,7 +115,7 @@ static void led_update(struct work_struct *work) { struct apanel *ap = container_of(work, struct apanel, led_work); - i2c_smbus_write_word_data(&ap->client, 0x10, ap->led_bits); + i2c_smbus_write_word_data(ap->client, 0x10, ap->led_bits); } static void mail_led_set(struct led_classdev *led, @@ -140,7 +131,7 @@ static void mail_led_set(struct led_classdev *led, schedule_work(&ap->led_work); } -static int apanel_detach_client(struct i2c_client *client) +static int apanel_remove(struct i2c_client *client) { struct apanel *ap = i2c_get_clientdata(client); @@ -148,43 +139,33 @@ static int apanel_detach_client(struct i2c_client *client) led_classdev_unregister(&ap->mail_led); input_unregister_polled_device(ap->ipdev); - i2c_detach_client(&ap->client); input_free_polled_device(ap->ipdev); return 0; } -/* Function is invoked for every i2c adapter. */ -static int apanel_attach_adapter(struct i2c_adapter *adap) -{ - dev_dbg(&adap->dev, APANEL ": attach adapter id=%d\n", adap->id); - - /* Our device is connected only to i801 on laptop */ - if (adap->id != I2C_HW_SMBUS_I801) - return -ENODEV; - - return i2c_probe(adap, &addr_data, apanel_probe); -} - static void apanel_shutdown(struct i2c_client *client) { - apanel_detach_client(client); + apanel_remove(client); } +static struct i2c_device_id apanel_id[] = { + { "fujitsu_apanel", 0 }, + { } +}; +MODULE_DEVICE_TABLE(i2c, apanel_id); + static struct i2c_driver apanel_driver = { .driver = { .name = APANEL, }, - .attach_adapter = &apanel_attach_adapter, - .detach_client = &apanel_detach_client, + .probe = &apanel_probe, + .remove = &apanel_remove, .shutdown = &apanel_shutdown, + .id_table = apanel_id, }; static struct apanel apanel = { - .client = { - .driver = &apanel_driver, - .name = APANEL, - }, .keymap = { [0] = KEY_MAIL, [1] = KEY_WWW, @@ -204,7 +185,8 @@ static struct apanel apanel = { }; /* NB: Only one panel on the i2c. */ -static int apanel_probe(struct i2c_adapter *bus, int address, int kind) +static int apanel_probe(struct i2c_client *client, + const struct i2c_device_id *id) { struct apanel *ap; struct input_polled_dev *ipdev; @@ -212,9 +194,6 @@ static int apanel_probe(struct i2c_adapter *bus, int address, int kind) u8 cmd = device_chip[APANEL_DEV_APPBTN] == CHIP_OZ992C ? 0 : 8; int i, err = -ENOMEM; - dev_dbg(&bus->dev, APANEL ": probe adapter %p addr %d kind %d\n", - bus, address, kind); - ap = &apanel; ipdev = input_allocate_polled_device(); @@ -222,18 +201,13 @@ static int apanel_probe(struct i2c_adapter *bus, int address, int kind) goto out1; ap->ipdev = ipdev; - ap->client.adapter = bus; - ap->client.addr = address; - - i2c_set_clientdata(&ap->client, ap); + ap->client = client; - err = i2c_attach_client(&ap->client); - if (err) - goto out2; + i2c_set_clientdata(client, ap); - err = i2c_smbus_write_word_data(&ap->client, cmd, 0); + err = i2c_smbus_write_word_data(client, cmd, 0); if (err) { - dev_warn(&ap->client.dev, APANEL ": smbus write error %d\n", + dev_warn(&client->dev, APANEL ": smbus write error %d\n", err); goto out3; } @@ -246,7 +220,7 @@ static int apanel_probe(struct i2c_adapter *bus, int address, int kind) idev->name = APANEL_NAME " buttons"; idev->phys = "apanel/input0"; idev->id.bustype = BUS_HOST; - idev->dev.parent = &ap->client.dev; + idev->dev.parent = &client->dev; set_bit(EV_KEY, idev->evbit); @@ -264,7 +238,7 @@ static int apanel_probe(struct i2c_adapter *bus, int address, int kind) INIT_WORK(&ap->led_work, led_update); if (device_chip[APANEL_DEV_LED] != CHIP_NONE) { - err = led_classdev_register(&ap->client.dev, &ap->mail_led); + err = led_classdev_register(&client->dev, &ap->mail_led); if (err) goto out4; } @@ -273,8 +247,6 @@ static int apanel_probe(struct i2c_adapter *bus, int address, int kind) out4: input_unregister_polled_device(ipdev); out3: - i2c_detach_client(&ap->client); -out2: input_free_polled_device(ipdev); out1: return err; @@ -301,6 +273,7 @@ static int __init apanel_init(void) void __iomem *bios; const void __iomem *p; u8 devno; + unsigned char i2c_addr; int found = 0; bios = ioremap(0xF0000, 0x10000); /* Can't fail */ @@ -313,7 +286,7 @@ static int __init apanel_init(void) /* just use the first address */ p += 8; - normal_i2c[0] = readb(p+3) >> 1; + i2c_addr = readb(p + 3) >> 1; for ( ; (devno = readb(p)) & 0x7f; p += 4) { unsigned char method, slave, chip; @@ -322,7 +295,7 @@ static int __init apanel_init(void) chip = readb(p + 2); slave = readb(p + 3) >> 1; - if (slave != normal_i2c[0]) { + if (slave != i2c_addr) { pr_notice(APANEL ": only one SMBus slave " "address supported, skiping device...\n"); continue; diff --git a/drivers/input/touchscreen/da9034-ts.c b/drivers/input/touchscreen/da9034-ts.c index 4342e77814b5..fa67d782c3c3 100644 --- a/drivers/input/touchscreen/da9034-ts.c +++ b/drivers/input/touchscreen/da9034-ts.c @@ -16,6 +16,7 @@ #include <linux/delay.h> #include <linux/platform_device.h> #include <linux/input.h> +#include <linux/workqueue.h> #include <linux/mfd/da903x.h> #define DA9034_MANUAL_CTRL 0x50 diff --git a/drivers/macintosh/Kconfig b/drivers/macintosh/Kconfig index b52659620d50..173cf55c64d0 100644 --- a/drivers/macintosh/Kconfig +++ b/drivers/macintosh/Kconfig @@ -138,7 +138,7 @@ config PMAC_BACKLIGHT Say Y here to enable Macintosh specific extensions of the generic backlight code. With this enabled, the brightness keys on older PowerBooks will be enabled so you can change the screen brightness. - Newer models should use an userspace daemon like pbbuttonsd. + Newer models should use a userspace daemon like pbbuttonsd. config PMAC_BACKLIGHT_LEGACY bool "Provide legacy ioctl's on /dev/pmu for the backlight" diff --git a/drivers/media/video/cafe_ccic.c b/drivers/media/video/cafe_ccic.c index 34a39d2e4703..46fd573a4f15 100644 --- a/drivers/media/video/cafe_ccic.c +++ b/drivers/media/video/cafe_ccic.c @@ -569,7 +569,6 @@ static int cafe_smbus_setup(struct cafe_camera *cam) cafe_smbus_enable_irq(cam); adap->id = I2C_HW_SMBUS_CAFE; - adap->class = I2C_CLASS_CAM_DIGITAL; adap->owner = THIS_MODULE; adap->client_register = cafe_smbus_attach; adap->client_unregister = cafe_smbus_detach; diff --git a/drivers/media/video/ov7670.c b/drivers/media/video/ov7670.c index ca26b0c50cf2..05c14a29375a 100644 --- a/drivers/media/video/ov7670.c +++ b/drivers/media/video/ov7670.c @@ -1347,7 +1347,6 @@ static struct i2c_driver ov7670_driver = { .name = "ov7670", }, .id = I2C_DRIVERID_OV7670, - .class = I2C_CLASS_CAM_DIGITAL, .attach_adapter = ov7670_attach, .detach_client = ov7670_detach, .command = ov7670_command, diff --git a/drivers/media/video/ovcamchip/ovcamchip_core.c b/drivers/media/video/ovcamchip/ovcamchip_core.c index 2c4acbf5a4fe..c841f4e4fbe4 100644 --- a/drivers/media/video/ovcamchip/ovcamchip_core.c +++ b/drivers/media/video/ovcamchip/ovcamchip_core.c @@ -405,7 +405,6 @@ static struct i2c_driver driver = { .name = "ovcamchip", }, .id = I2C_DRIVERID_OVCAMCHIP, - .class = I2C_CLASS_CAM_DIGITAL, .attach_adapter = ovcamchip_attach, .detach_client = ovcamchip_detach, .command = ovcamchip_command, diff --git a/drivers/media/video/pvrusb2/pvrusb2-hdw.c b/drivers/media/video/pvrusb2/pvrusb2-hdw.c index 8fb92ac78c7b..fa304e5f252a 100644 --- a/drivers/media/video/pvrusb2/pvrusb2-hdw.c +++ b/drivers/media/video/pvrusb2/pvrusb2-hdw.c @@ -3655,7 +3655,7 @@ void pvr2_hdw_device_reset(struct pvr2_hdw *hdw) int ret; pvr2_trace(PVR2_TRACE_INIT,"Performing a device reset..."); ret = usb_lock_device_for_reset(hdw->usb_dev,NULL); - if (ret == 1) { + if (ret == 0) { ret = usb_reset_device(hdw->usb_dev); usb_unlock_device(hdw->usb_dev); } else { diff --git a/drivers/media/video/w9968cf.c b/drivers/media/video/w9968cf.c index a3997b7d4366..105a832531f2 100644 --- a/drivers/media/video/w9968cf.c +++ b/drivers/media/video/w9968cf.c @@ -1553,7 +1553,6 @@ static int w9968cf_i2c_init(struct w9968cf_device* cam) static struct i2c_adapter adap = { .id = I2C_HW_SMBUS_W9968CF, - .class = I2C_CLASS_CAM_DIGITAL, .owner = THIS_MODULE, .client_register = w9968cf_i2c_attach_inform, .client_unregister = w9968cf_i2c_detach_inform, diff --git a/drivers/message/i2o/i2o_scsi.c b/drivers/message/i2o/i2o_scsi.c index 1bcdbbb9e7d3..3d45817e6dcd 100644 --- a/drivers/message/i2o/i2o_scsi.c +++ b/drivers/message/i2o/i2o_scsi.c @@ -390,7 +390,7 @@ static int i2o_scsi_reply(struct i2o_controller *c, u32 m, * @i2o_dev: the I2O device which was added * * If a I2O device is added we catch the notification, because I2O classes - * other then SCSI peripheral will not be received through + * other than SCSI peripheral will not be received through * i2o_scsi_probe(). */ static void i2o_scsi_notify_device_add(struct i2o_device *i2o_dev) diff --git a/drivers/misc/ibmasm/event.c b/drivers/misc/ibmasm/event.c index fda6a4d3bf23..68a0a5b94795 100644 --- a/drivers/misc/ibmasm/event.c +++ b/drivers/misc/ibmasm/event.c @@ -50,7 +50,7 @@ static void wake_up_event_readers(struct service_processor *sp) * Store the event in the circular event buffer, wake up any sleeping * event readers. * There is no reader marker in the buffer, therefore readers are - * responsible for keeping up with the writer, or they will loose events. + * responsible for keeping up with the writer, or they will lose events. */ void ibmasm_receive_event(struct service_processor *sp, void *data, unsigned int data_size) { diff --git a/drivers/misc/phantom.c b/drivers/misc/phantom.c index abdebe347383..fa57b67593ae 100644 --- a/drivers/misc/phantom.c +++ b/drivers/misc/phantom.c @@ -6,7 +6,7 @@ * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * - * You need an userspace library to cooperate with this driver. It (and other + * You need a userspace library to cooperate with this driver. It (and other * info) may be obtained here: * http://www.fi.muni.cz/~xslaby/phantom.html * or alternatively, you might use OpenHaptics provided by Sensable. diff --git a/drivers/mmc/host/atmel-mci.c b/drivers/mmc/host/atmel-mci.c index 7a3f2436b011..1e97916914ad 100644 --- a/drivers/mmc/host/atmel-mci.c +++ b/drivers/mmc/host/atmel-mci.c @@ -25,8 +25,8 @@ #include <linux/stat.h> #include <linux/mmc/host.h> +#include <linux/atmel-mci.h> -#include <asm/atmel-mci.h> #include <asm/io.h> #include <asm/unaligned.h> diff --git a/drivers/mtd/devices/pmc551.c b/drivers/mtd/devices/pmc551.c index d38bca64bb15..d2fd550f7e09 100644 --- a/drivers/mtd/devices/pmc551.c +++ b/drivers/mtd/devices/pmc551.c @@ -34,7 +34,7 @@ * aperture size, not the dram size, and the V370PDC supplies no * other method for memory size discovery. This problem is * mostly only relevant when compiled as a module, as the - * unloading of the module with an aperture size smaller then + * unloading of the module with an aperture size smaller than * the ram will cause the driver to detect the onboard memory * size to be equal to the aperture size when the module is * reloaded. Soooo, to help, the module supports an msize diff --git a/drivers/mtd/ubi/eba.c b/drivers/mtd/ubi/eba.c index 048a606cebde..25def348e5ba 100644 --- a/drivers/mtd/ubi/eba.c +++ b/drivers/mtd/ubi/eba.c @@ -717,7 +717,7 @@ write_error: * to the real data size, although the @buf buffer has to contain the * alignment. In all other cases, @len has to be aligned. * - * It is prohibited to write more then once to logical eraseblocks of static + * It is prohibited to write more than once to logical eraseblocks of static * volumes. This function returns zero in case of success and a negative error * code in case of failure. */ diff --git a/drivers/mtd/ubi/io.c b/drivers/mtd/ubi/io.c index a74118c05745..fe81039f2a7c 100644 --- a/drivers/mtd/ubi/io.c +++ b/drivers/mtd/ubi/io.c @@ -465,7 +465,7 @@ out: * This function synchronously erases physical eraseblock @pnum. If @torture * flag is not zero, the physical eraseblock is checked by means of writing * different patterns to it and reading them back. If the torturing is enabled, - * the physical eraseblock is erased more then once. + * the physical eraseblock is erased more than once. * * This function returns the number of erasures made in case of success, %-EIO * if the erasure failed or the torturing test failed, and other negative error diff --git a/drivers/mtd/ubi/scan.c b/drivers/mtd/ubi/scan.c index 41d47e1cf15c..ecde202a5a12 100644 --- a/drivers/mtd/ubi/scan.c +++ b/drivers/mtd/ubi/scan.c @@ -478,7 +478,7 @@ int ubi_scan_add_used(struct ubi_device *ubi, struct ubi_scan_info *si, return 0; } else { /* - * This logical eraseblock is older then the one found + * This logical eraseblock is older than the one found * previously. */ if (cmp_res & 4) diff --git a/drivers/mtd/ubi/ubi-media.h b/drivers/mtd/ubi/ubi-media.h index 2ad940409053..8419fdccc79c 100644 --- a/drivers/mtd/ubi/ubi-media.h +++ b/drivers/mtd/ubi/ubi-media.h @@ -135,7 +135,7 @@ enum { * The erase counter header takes 64 bytes and has a plenty of unused space for * future usage. The unused fields are zeroed. The @version field is used to * indicate the version of UBI implementation which is supposed to be able to - * work with this UBI image. If @version is greater then the current UBI + * work with this UBI image. If @version is greater than the current UBI * version, the image is rejected. This may be useful in future if something * is changed radically. This field is duplicated in the volume identifier * header. @@ -187,7 +187,7 @@ struct ubi_ec_hdr { * (sequence number) is used to distinguish between older and newer versions of * logical eraseblocks. * - * There are 2 situations when there may be more then one physical eraseblock + * There are 2 situations when there may be more than one physical eraseblock * corresponding to the same logical eraseblock, i.e., having the same @vol_id * and @lnum values in the volume identifier header. Suppose we have a logical * eraseblock L and it is mapped to the physical eraseblock P. diff --git a/drivers/mtd/ubi/vtbl.c b/drivers/mtd/ubi/vtbl.c index 333c8941552f..1afc61e7455d 100644 --- a/drivers/mtd/ubi/vtbl.c +++ b/drivers/mtd/ubi/vtbl.c @@ -577,7 +577,7 @@ static int init_volumes(struct ubi_device *ubi, const struct ubi_scan_info *si, if (vtbl[i].flags & UBI_VTBL_AUTORESIZE_FLG) { /* Auto re-size flag may be set only for one volume */ if (ubi->autoresize_vol_id != -1) { - ubi_err("more then one auto-resize volume (%d " + ubi_err("more than one auto-resize volume (%d " "and %d)", ubi->autoresize_vol_id, i); kfree(vol); return -EINVAL; diff --git a/drivers/mtd/ubi/wl.c b/drivers/mtd/ubi/wl.c index 14901cb82c18..891534f8210d 100644 --- a/drivers/mtd/ubi/wl.c +++ b/drivers/mtd/ubi/wl.c @@ -128,7 +128,7 @@ * situation when the picked physical eraseblock is constantly erased after the * data is written to it. So, we have a constant which limits the highest erase * counter of the free physical eraseblock to pick. Namely, the WL sub-system - * does not pick eraseblocks with erase counter greater then the lowest erase + * does not pick eraseblocks with erase counter greater than the lowest erase * counter plus %WL_FREE_MAX_DIFF. */ #define WL_FREE_MAX_DIFF (2*UBI_WL_THRESHOLD) @@ -917,7 +917,7 @@ static int ensure_wear_leveling(struct ubi_device *ubi) /* * We schedule wear-leveling only if the difference between the * lowest erase counter of used physical eraseblocks and a high - * erase counter of free physical eraseblocks is greater then + * erase counter of free physical eraseblocks is greater than * %UBI_WL_THRESHOLD. */ e1 = rb_entry(rb_first(&ubi->used), struct ubi_wl_entry, u.rb); diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 9a18270c1081..97ea7c60e002 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2614,6 +2614,8 @@ source "drivers/net/tokenring/Kconfig" source "drivers/net/wireless/Kconfig" +source "drivers/net/wimax/Kconfig" + source "drivers/net/usb/Kconfig" source "drivers/net/pcmcia/Kconfig" diff --git a/drivers/net/Makefile b/drivers/net/Makefile index e5c34b464211..a3c5c002f224 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -263,3 +263,4 @@ obj-$(CONFIG_NIU) += niu.o obj-$(CONFIG_VIRTIO_NET) += virtio_net.o obj-$(CONFIG_SFC) += sfc/ +obj-$(CONFIG_WIMAX) += wimax/ diff --git a/drivers/net/bnx2x_link.c b/drivers/net/bnx2x_link.c index 67de94f1f30e..fefa6ab13064 100644 --- a/drivers/net/bnx2x_link.c +++ b/drivers/net/bnx2x_link.c @@ -3359,7 +3359,7 @@ static u8 bnx2x_format_ver(u32 num, u8 *str, u16 len) u8 shift = 8*4; u8 digit; if (len < 10) { - /* Need more then 10chars for this format */ + /* Need more than 10chars for this format */ *str_ptr = '\0'; return -EINVAL; } diff --git a/drivers/net/e1000/e1000_hw.c b/drivers/net/e1000/e1000_hw.c index d04eef53571e..e1a3fc1303ee 100644 --- a/drivers/net/e1000/e1000_hw.c +++ b/drivers/net/e1000/e1000_hw.c @@ -6758,7 +6758,7 @@ static s32 e1000_get_cable_length(struct e1000_hw *hw, u16 *min_length, * returns: - E1000_ERR_XXX * E1000_SUCCESS * - * For phy's older then IGP, this function simply reads the polarity bit in the + * For phy's older than IGP, this function simply reads the polarity bit in the * Phy Status register. For IGP phy's, this bit is valid only if link speed is * 10 Mbps. If the link speed is 100 Mbps there is no polarity so this bit will * return 0. If the link speed is 1000 Mbps the polarity status is in the @@ -6834,7 +6834,7 @@ static s32 e1000_check_polarity(struct e1000_hw *hw, * returns: - E1000_ERR_XXX * E1000_SUCCESS * - * For phy's older then IGP, this function reads the Downshift bit in the Phy + * For phy's older than IGP, this function reads the Downshift bit in the Phy * Specific Status register. For IGP phy's, it reads the Downgrade bit in the * Link Health register. In IGP this bit is latched high, so the driver must * read it immediately after link is established. diff --git a/drivers/net/slip.h b/drivers/net/slip.h index 853e0f6ec710..9ea5c11287d2 100644 --- a/drivers/net/slip.h +++ b/drivers/net/slip.h @@ -75,7 +75,7 @@ struct slip { unsigned long tx_errors; /* Planned stuff */ unsigned long rx_dropped; /* No memory for skb */ unsigned long tx_dropped; /* When MTU change */ - unsigned long rx_over_errors; /* Frame bigger then SLIP buf. */ + unsigned long rx_over_errors; /* Frame bigger than SLIP buf. */ #ifdef SL_INCLUDE_CSLIP unsigned long tx_compressed; unsigned long rx_compressed; diff --git a/drivers/net/tehuti.c b/drivers/net/tehuti.c index a10a83a11d9f..a7a4dc4d6313 100644 --- a/drivers/net/tehuti.c +++ b/drivers/net/tehuti.c @@ -1004,7 +1004,7 @@ static inline void bdx_rxdb_free_elem(struct rxdb *db, int n) * skb for rx. It assumes that Rx is desabled in HW * funcs are grouped for better cache usage * - * RxD fifo is smaller then RxF fifo by design. Upon high load, RxD will be + * RxD fifo is smaller than RxF fifo by design. Upon high load, RxD will be * filled and packets will be dropped by nic without getting into host or * cousing interrupt. Anyway, in that condition, host has no chance to proccess * all packets, but dropping in nic is cheaper, since it takes 0 cpu cycles @@ -1826,7 +1826,7 @@ static void bdx_tx_free(struct bdx_priv *priv) * * Pushes desc to TxD fifo and overlaps it if needed. * NOTE: this func does not check for available space. this is responsibility - * of the caller. Neither does it check that data size is smaller then + * of the caller. Neither does it check that data size is smaller than * fifo size. */ static void bdx_tx_push_desc(struct bdx_priv *priv, void *data, int size) diff --git a/drivers/net/tokenring/smctr.c b/drivers/net/tokenring/smctr.c index a011666342ff..50eb29ce3c87 100644 --- a/drivers/net/tokenring/smctr.c +++ b/drivers/net/tokenring/smctr.c @@ -3064,7 +3064,7 @@ static int smctr_load_node_addr(struct net_device *dev) * will consequently cause a timeout. * * NOTE 1: If the monitor_state is MS_BEACON_TEST_STATE, all transmit - * queues other then the one used for the lobe_media_test should be + * queues other than the one used for the lobe_media_test should be * disabled.!? * * NOTE 2: If the monitor_state is MS_BEACON_TEST_STATE and the receive_mask diff --git a/drivers/net/usb/kaweth.c b/drivers/net/usb/kaweth.c index 2ee034f70d1c..3073ca25a0b0 100644 --- a/drivers/net/usb/kaweth.c +++ b/drivers/net/usb/kaweth.c @@ -283,9 +283,9 @@ static int kaweth_control(struct kaweth_device *kaweth, dr->bRequestType= requesttype; dr->bRequest = request; - dr->wValue = cpu_to_le16p(&value); - dr->wIndex = cpu_to_le16p(&index); - dr->wLength = cpu_to_le16p(&size); + dr->wValue = cpu_to_le16(value); + dr->wIndex = cpu_to_le16(index); + dr->wLength = cpu_to_le16(size); return kaweth_internal_control_msg(kaweth->dev, pipe, diff --git a/drivers/net/usb/pegasus.c b/drivers/net/usb/pegasus.c index 166880c113d6..d9241f1c0803 100644 --- a/drivers/net/usb/pegasus.c +++ b/drivers/net/usb/pegasus.c @@ -150,8 +150,8 @@ static int get_registers(pegasus_t * pegasus, __u16 indx, __u16 size, pegasus->dr.bRequestType = PEGASUS_REQT_READ; pegasus->dr.bRequest = PEGASUS_REQ_GET_REGS; pegasus->dr.wValue = cpu_to_le16(0); - pegasus->dr.wIndex = cpu_to_le16p(&indx); - pegasus->dr.wLength = cpu_to_le16p(&size); + pegasus->dr.wIndex = cpu_to_le16(indx); + pegasus->dr.wLength = cpu_to_le16(size); pegasus->ctrl_urb->transfer_buffer_length = size; usb_fill_control_urb(pegasus->ctrl_urb, pegasus->usb, @@ -208,8 +208,8 @@ static int set_registers(pegasus_t * pegasus, __u16 indx, __u16 size, pegasus->dr.bRequestType = PEGASUS_REQT_WRITE; pegasus->dr.bRequest = PEGASUS_REQ_SET_REGS; pegasus->dr.wValue = cpu_to_le16(0); - pegasus->dr.wIndex = cpu_to_le16p(&indx); - pegasus->dr.wLength = cpu_to_le16p(&size); + pegasus->dr.wIndex = cpu_to_le16(indx); + pegasus->dr.wLength = cpu_to_le16(size); pegasus->ctrl_urb->transfer_buffer_length = size; usb_fill_control_urb(pegasus->ctrl_urb, pegasus->usb, @@ -261,7 +261,7 @@ static int set_register(pegasus_t * pegasus, __u16 indx, __u8 data) pegasus->dr.bRequestType = PEGASUS_REQT_WRITE; pegasus->dr.bRequest = PEGASUS_REQ_SET_REG; pegasus->dr.wValue = cpu_to_le16(data); - pegasus->dr.wIndex = cpu_to_le16p(&indx); + pegasus->dr.wIndex = cpu_to_le16(indx); pegasus->dr.wLength = cpu_to_le16(1); pegasus->ctrl_urb->transfer_buffer_length = 1; @@ -476,7 +476,7 @@ static inline void get_node_id(pegasus_t * pegasus, __u8 * id) for (i = 0; i < 3; i++) { read_eprom_word(pegasus, i, &w16); - ((__le16 *) id)[i] = cpu_to_le16p(&w16); + ((__le16 *) id)[i] = cpu_to_le16(w16); } } diff --git a/drivers/net/wimax/Kconfig b/drivers/net/wimax/Kconfig new file mode 100644 index 000000000000..565018ec1e3b --- /dev/null +++ b/drivers/net/wimax/Kconfig @@ -0,0 +1,17 @@ +# +# WiMAX LAN device drivers configuration +# + + +comment "Enable WiMAX (Networking options) to see the WiMAX drivers" + depends on WIMAX = n + +if WIMAX + +menu "WiMAX Wireless Broadband devices" + +source "drivers/net/wimax/i2400m/Kconfig" + +endmenu + +endif diff --git a/drivers/net/wimax/Makefile b/drivers/net/wimax/Makefile new file mode 100644 index 000000000000..992bc02bc016 --- /dev/null +++ b/drivers/net/wimax/Makefile @@ -0,0 +1,5 @@ + +obj-$(CONFIG_WIMAX_I2400M) += i2400m/ + +# (from Sam Ravnborg) force kbuild to create built-in.o +obj- := dummy.o diff --git a/drivers/net/wimax/i2400m/Kconfig b/drivers/net/wimax/i2400m/Kconfig new file mode 100644 index 000000000000..d623b3d99a4b --- /dev/null +++ b/drivers/net/wimax/i2400m/Kconfig @@ -0,0 +1,49 @@ + +config WIMAX_I2400M + tristate + depends on WIMAX + select FW_LOADER + +comment "Enable USB support to see WiMAX USB drivers" + depends on USB = n + +comment "Enable MMC support to see WiMAX SDIO drivers" + depends on MMC = n + +config WIMAX_I2400M_USB + tristate "Intel Wireless WiMAX Connection 2400 over USB (including 5x50)" + depends on WIMAX && USB + select WIMAX_I2400M + help + Select if you have a device based on the Intel WiMAX + Connection 2400 over USB (like any of the Intel Wireless + WiMAX/WiFi Link 5x50 series). + + If unsure, it is safe to select M (module). + +config WIMAX_I2400M_SDIO + tristate "Intel Wireless WiMAX Connection 2400 over SDIO" + depends on WIMAX && MMC + select WIMAX_I2400M + help + Select if you have a device based on the Intel WiMAX + Connection 2400 over SDIO. + + If unsure, it is safe to select M (module). + +config WIMAX_I2400M_DEBUG_LEVEL + int "WiMAX i2400m debug level" + depends on WIMAX_I2400M + default 8 + help + + Select the maximum debug verbosity level to be compiled into + the WiMAX i2400m driver code. + + By default, this is disabled at runtime and can be + selectively enabled at runtime for different parts of the + code using the sysfs debug-levels file. + + If set at zero, this will compile out all the debug code. + + It is recommended that it is left at 8. diff --git a/drivers/net/wimax/i2400m/Makefile b/drivers/net/wimax/i2400m/Makefile new file mode 100644 index 000000000000..1696e936cf5a --- /dev/null +++ b/drivers/net/wimax/i2400m/Makefile @@ -0,0 +1,29 @@ + +obj-$(CONFIG_WIMAX_I2400M) += i2400m.o +obj-$(CONFIG_WIMAX_I2400M_USB) += i2400m-usb.o +obj-$(CONFIG_WIMAX_I2400M_SDIO) += i2400m-sdio.o + +i2400m-y := \ + control.o \ + driver.o \ + fw.o \ + op-rfkill.o \ + netdev.o \ + tx.o \ + rx.o + +i2400m-$(CONFIG_DEBUG_FS) += debugfs.o + +i2400m-usb-y := \ + usb-fw.o \ + usb-notif.o \ + usb-tx.o \ + usb-rx.o \ + usb.o + + +i2400m-sdio-y := \ + sdio.o \ + sdio-tx.o \ + sdio-fw.o \ + sdio-rx.o diff --git a/drivers/net/wimax/i2400m/control.c b/drivers/net/wimax/i2400m/control.c new file mode 100644 index 000000000000..d3d37fed6893 --- /dev/null +++ b/drivers/net/wimax/i2400m/control.c @@ -0,0 +1,1291 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Miscellaneous control functions for managing the device + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Initial implementation + * + * This is a collection of functions used to control the device (plus + * a few helpers). + * + * There are utilities for handling TLV buffers, hooks on the device's + * reports to act on device changes of state [i2400m_report_hook()], + * on acks to commands [i2400m_msg_ack_hook()], a helper for sending + * commands to the device and blocking until a reply arrives + * [i2400m_msg_to_dev()], a few high level commands for manipulating + * the device state, powersving mode and configuration plus the + * routines to setup the device once communication is stablished with + * it [i2400m_dev_initialize()]. + * + * ROADMAP + * + * i2400m_dev_initalize() Called by i2400m_dev_start() + * i2400m_set_init_config() + * i2400m_firmware_check() + * i2400m_cmd_get_state() + * i2400m_dev_shutdown() Called by i2400m_dev_stop() + * i2400m->bus_reset() + * + * i2400m_{cmd,get,set}_*() + * i2400m_msg_to_dev() + * i2400m_msg_check_status() + * + * i2400m_report_hook() Called on reception of an event + * i2400m_report_state_hook() + * i2400m_tlv_buffer_walk() + * i2400m_tlv_match() + * i2400m_report_tlv_system_state() + * i2400m_report_tlv_rf_switches_status() + * i2400m_report_tlv_media_status() + * i2400m_cmd_enter_powersave() + * + * i2400m_msg_ack_hook() Called on reception of a reply to a + * command, get or set + */ + +#include <stdarg.h> +#include "i2400m.h" +#include <linux/kernel.h> +#include <linux/wimax/i2400m.h> + + +#define D_SUBMODULE control +#include "debug-levels.h" + + +/* + * Return if a TLV is of a give type and size + * + * @tlv_hdr: pointer to the TLV + * @tlv_type: type of the TLV we are looking for + * @tlv_size: expected size of the TLV we are looking for (if -1, + * don't check the size). This includes the header + * Returns: 0 if the TLV matches + * < 0 if it doesn't match at all + * > 0 total TLV + payload size, if the type matches, but not + * the size + */ +static +ssize_t i2400m_tlv_match(const struct i2400m_tlv_hdr *tlv, + enum i2400m_tlv tlv_type, ssize_t tlv_size) +{ + if (le16_to_cpu(tlv->type) != tlv_type) /* Not our type? skip */ + return -1; + if (tlv_size != -1 + && le16_to_cpu(tlv->length) + sizeof(*tlv) != tlv_size) { + size_t size = le16_to_cpu(tlv->length) + sizeof(*tlv); + printk(KERN_WARNING "W: tlv type 0x%x mismatched because of " + "size (got %zu vs %zu expected)\n", + tlv_type, size, tlv_size); + return size; + } + return 0; +} + + +/* + * Given a buffer of TLVs, iterate over them + * + * @i2400m: device instance + * @tlv_buf: pointer to the beginning of the TLV buffer + * @buf_size: buffer size in bytes + * @tlv_pos: seek position; this is assumed to be a pointer returned + * by i2400m_tlv_buffer_walk() [and thus, validated]. The + * TLV returned will be the one following this one. + * + * Usage: + * + * tlv_itr = NULL; + * while (tlv_itr = i2400m_tlv_buffer_walk(i2400m, buf, size, tlv_itr)) { + * ... + * // Do stuff with tlv_itr, DON'T MODIFY IT + * ... + * } + */ +static +const struct i2400m_tlv_hdr *i2400m_tlv_buffer_walk( + struct i2400m *i2400m, + const void *tlv_buf, size_t buf_size, + const struct i2400m_tlv_hdr *tlv_pos) +{ + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_tlv_hdr *tlv_top = tlv_buf + buf_size; + size_t offset, length, avail_size; + unsigned type; + + if (tlv_pos == NULL) /* Take the first one? */ + tlv_pos = tlv_buf; + else /* Nope, the next one */ + tlv_pos = (void *) tlv_pos + + le16_to_cpu(tlv_pos->length) + sizeof(*tlv_pos); + if (tlv_pos == tlv_top) { /* buffer done */ + tlv_pos = NULL; + goto error_beyond_end; + } + if (tlv_pos > tlv_top) { + tlv_pos = NULL; + WARN_ON(1); + goto error_beyond_end; + } + offset = (void *) tlv_pos - (void *) tlv_buf; + avail_size = buf_size - offset; + if (avail_size < sizeof(*tlv_pos)) { + dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], tlv @%zu: " + "short header\n", tlv_buf, buf_size, offset); + goto error_short_header; + } + type = le16_to_cpu(tlv_pos->type); + length = le16_to_cpu(tlv_pos->length); + if (avail_size < sizeof(*tlv_pos) + length) { + dev_err(dev, "HW BUG? tlv_buf %p [%zu bytes], " + "tlv type 0x%04x @%zu: " + "short data (%zu bytes vs %zu needed)\n", + tlv_buf, buf_size, type, offset, avail_size, + sizeof(*tlv_pos) + length); + goto error_short_header; + } +error_short_header: +error_beyond_end: + return tlv_pos; +} + + +/* + * Find a TLV in a buffer of sequential TLVs + * + * @i2400m: device descriptor + * @tlv_hdr: pointer to the first TLV in the sequence + * @size: size of the buffer in bytes; all TLVs are assumed to fit + * fully in the buffer (otherwise we'll complain). + * @tlv_type: type of the TLV we are looking for + * @tlv_size: expected size of the TLV we are looking for (if -1, + * don't check the size). This includes the header + * + * Returns: NULL if the TLV is not found, otherwise a pointer to + * it. If the sizes don't match, an error is printed and NULL + * returned. + */ +static +const struct i2400m_tlv_hdr *i2400m_tlv_find( + struct i2400m *i2400m, + const struct i2400m_tlv_hdr *tlv_hdr, size_t size, + enum i2400m_tlv tlv_type, ssize_t tlv_size) +{ + ssize_t match; + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_tlv_hdr *tlv = NULL; + while ((tlv = i2400m_tlv_buffer_walk(i2400m, tlv_hdr, size, tlv))) { + match = i2400m_tlv_match(tlv, tlv_type, tlv_size); + if (match == 0) /* found it :) */ + break; + if (match > 0) + dev_warn(dev, "TLV type 0x%04x found with size " + "mismatch (%zu vs %zu needed)\n", + tlv_type, match, tlv_size); + } + return tlv; +} + + +static const struct +{ + char *msg; + int errno; +} ms_to_errno[I2400M_MS_MAX] = { + [I2400M_MS_DONE_OK] = { "", 0 }, + [I2400M_MS_DONE_IN_PROGRESS] = { "", 0 }, + [I2400M_MS_INVALID_OP] = { "invalid opcode", -ENOSYS }, + [I2400M_MS_BAD_STATE] = { "invalid state", -EILSEQ }, + [I2400M_MS_ILLEGAL_VALUE] = { "illegal value", -EINVAL }, + [I2400M_MS_MISSING_PARAMS] = { "missing parameters", -ENOMSG }, + [I2400M_MS_VERSION_ERROR] = { "bad version", -EIO }, + [I2400M_MS_ACCESSIBILITY_ERROR] = { "accesibility error", -EIO }, + [I2400M_MS_BUSY] = { "busy", -EBUSY }, + [I2400M_MS_CORRUPTED_TLV] = { "corrupted TLV", -EILSEQ }, + [I2400M_MS_UNINITIALIZED] = { "not unitialized", -EILSEQ }, + [I2400M_MS_UNKNOWN_ERROR] = { "unknown error", -EIO }, + [I2400M_MS_PRODUCTION_ERROR] = { "production error", -EIO }, + [I2400M_MS_NO_RF] = { "no RF", -EIO }, + [I2400M_MS_NOT_READY_FOR_POWERSAVE] = + { "not ready for powersave", -EACCES }, + [I2400M_MS_THERMAL_CRITICAL] = { "thermal critical", -EL3HLT }, +}; + + +/* + * i2400m_msg_check_status - translate a message's status code + * + * @i2400m: device descriptor + * @l3l4_hdr: message header + * @strbuf: buffer to place a formatted error message (unless NULL). + * @strbuf_size: max amount of available space; larger messages will + * be truncated. + * + * Returns: errno code corresponding to the status code in @l3l4_hdr + * and a message in @strbuf describing the error. + */ +int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *l3l4_hdr, + char *strbuf, size_t strbuf_size) +{ + int result; + enum i2400m_ms status = le16_to_cpu(l3l4_hdr->status); + const char *str; + + if (status == 0) + return 0; + if (status > ARRAY_SIZE(ms_to_errno)) { + str = "unknown status code"; + result = -EBADR; + } else { + str = ms_to_errno[status].msg; + result = ms_to_errno[status].errno; + } + if (strbuf) + snprintf(strbuf, strbuf_size, "%s (%d)", str, status); + return result; +} + + +/* + * Act on a TLV System State reported by the device + * + * @i2400m: device descriptor + * @ss: validated System State TLV + */ +static +void i2400m_report_tlv_system_state(struct i2400m *i2400m, + const struct i2400m_tlv_system_state *ss) +{ + struct device *dev = i2400m_dev(i2400m); + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + enum i2400m_system_state i2400m_state = le32_to_cpu(ss->state); + + d_fnstart(3, dev, "(i2400m %p ss %p [%u])\n", i2400m, ss, i2400m_state); + + if (unlikely(i2400m->ready == 0)) /* act if up */ + goto out; + if (i2400m->state != i2400m_state) { + i2400m->state = i2400m_state; + wake_up_all(&i2400m->state_wq); + } + switch (i2400m_state) { + case I2400M_SS_UNINITIALIZED: + case I2400M_SS_INIT: + case I2400M_SS_CONFIG: + case I2400M_SS_PRODUCTION: + wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED); + break; + + case I2400M_SS_RF_OFF: + case I2400M_SS_RF_SHUTDOWN: + wimax_state_change(wimax_dev, WIMAX_ST_RADIO_OFF); + break; + + case I2400M_SS_READY: + case I2400M_SS_STANDBY: + case I2400M_SS_SLEEPACTIVE: + wimax_state_change(wimax_dev, WIMAX_ST_READY); + break; + + case I2400M_SS_CONNECTING: + case I2400M_SS_WIMAX_CONNECTED: + wimax_state_change(wimax_dev, WIMAX_ST_READY); + break; + + case I2400M_SS_SCAN: + case I2400M_SS_OUT_OF_ZONE: + wimax_state_change(wimax_dev, WIMAX_ST_SCANNING); + break; + + case I2400M_SS_IDLE: + d_printf(1, dev, "entering BS-negotiated idle mode\n"); + case I2400M_SS_DISCONNECTING: + case I2400M_SS_DATA_PATH_CONNECTED: + wimax_state_change(wimax_dev, WIMAX_ST_CONNECTED); + break; + + default: + /* Huh? just in case, shut it down */ + dev_err(dev, "HW BUG? unknown state %u: shutting down\n", + i2400m_state); + i2400m->bus_reset(i2400m, I2400M_RT_WARM); + break; + }; +out: + d_fnend(3, dev, "(i2400m %p ss %p [%u]) = void\n", + i2400m, ss, i2400m_state); +} + + +/* + * Parse and act on a TLV Media Status sent by the device + * + * @i2400m: device descriptor + * @ms: validated Media Status TLV + * + * This will set the carrier up on down based on the device's link + * report. This is done asides of what the WiMAX stack does based on + * the device's state as sometimes we need to do a link-renew (the BS + * wants us to renew a DHCP lease, for example). + * + * In fact, doc says that everytime we get a link-up, we should do a + * DHCP negotiation... + */ +static +void i2400m_report_tlv_media_status(struct i2400m *i2400m, + const struct i2400m_tlv_media_status *ms) +{ + struct device *dev = i2400m_dev(i2400m); + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + struct net_device *net_dev = wimax_dev->net_dev; + enum i2400m_media_status status = le32_to_cpu(ms->media_status); + + d_fnstart(3, dev, "(i2400m %p ms %p [%u])\n", i2400m, ms, status); + + if (unlikely(i2400m->ready == 0)) /* act if up */ + goto out; + switch (status) { + case I2400M_MEDIA_STATUS_LINK_UP: + netif_carrier_on(net_dev); + break; + case I2400M_MEDIA_STATUS_LINK_DOWN: + netif_carrier_off(net_dev); + break; + /* + * This is the network telling us we need to retrain the DHCP + * lease -- so far, we are trusting the WiMAX Network Service + * in user space to pick this up and poke the DHCP client. + */ + case I2400M_MEDIA_STATUS_LINK_RENEW: + netif_carrier_on(net_dev); + break; + default: + dev_err(dev, "HW BUG? unknown media status %u\n", + status); + }; +out: + d_fnend(3, dev, "(i2400m %p ms %p [%u]) = void\n", + i2400m, ms, status); +} + + +/* + * Parse a 'state report' and extract carrier on/off information + * + * @i2400m: device descriptor + * @l3l4_hdr: pointer to message; it has been already validated for + * consistent size. + * @size: size of the message (header + payload). The header length + * declaration is assumed to be congruent with @size (as in + * sizeof(*l3l4_hdr) + l3l4_hdr->length == size) + * + * Extract from the report state the system state TLV and infer from + * there if we have a carrier or not. Update our local state and tell + * netdev. + * + * When setting the carrier, it's fine to set OFF twice (for example), + * as netif_carrier_off() will not generate two OFF events (just on + * the transitions). + */ +static +void i2400m_report_state_hook(struct i2400m *i2400m, + const struct i2400m_l3l4_hdr *l3l4_hdr, + size_t size, const char *tag) +{ + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_tlv_hdr *tlv; + const struct i2400m_tlv_system_state *ss; + const struct i2400m_tlv_rf_switches_status *rfss; + const struct i2400m_tlv_media_status *ms; + size_t tlv_size = le16_to_cpu(l3l4_hdr->length); + + d_fnstart(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s)\n", + i2400m, l3l4_hdr, size, tag); + tlv = NULL; + + while ((tlv = i2400m_tlv_buffer_walk(i2400m, &l3l4_hdr->pl, + tlv_size, tlv))) { + if (0 == i2400m_tlv_match(tlv, I2400M_TLV_SYSTEM_STATE, + sizeof(*ss))) { + ss = container_of(tlv, typeof(*ss), hdr); + d_printf(2, dev, "%s: system state TLV " + "found (0x%04x), state 0x%08x\n", + tag, I2400M_TLV_SYSTEM_STATE, + le32_to_cpu(ss->state)); + i2400m_report_tlv_system_state(i2400m, ss); + } + if (0 == i2400m_tlv_match(tlv, I2400M_TLV_RF_STATUS, + sizeof(*rfss))) { + rfss = container_of(tlv, typeof(*rfss), hdr); + d_printf(2, dev, "%s: RF status TLV " + "found (0x%04x), sw 0x%02x hw 0x%02x\n", + tag, I2400M_TLV_RF_STATUS, + le32_to_cpu(rfss->sw_rf_switch), + le32_to_cpu(rfss->hw_rf_switch)); + i2400m_report_tlv_rf_switches_status(i2400m, rfss); + } + if (0 == i2400m_tlv_match(tlv, I2400M_TLV_MEDIA_STATUS, + sizeof(*ms))) { + ms = container_of(tlv, typeof(*ms), hdr); + d_printf(2, dev, "%s: Media Status TLV: %u\n", + tag, le32_to_cpu(ms->media_status)); + i2400m_report_tlv_media_status(i2400m, ms); + } + } + d_fnend(4, dev, "(i2400m %p, l3l4_hdr %p, size %zu, %s) = void\n", + i2400m, l3l4_hdr, size, tag); +} + + +/* + * i2400m_report_hook - (maybe) act on a report + * + * @i2400m: device descriptor + * @l3l4_hdr: pointer to message; it has been already validated for + * consistent size. + * @size: size of the message (header + payload). The header length + * declaration is assumed to be congruent with @size (as in + * sizeof(*l3l4_hdr) + l3l4_hdr->length == size) + * + * Extract information we might need (like carrien on/off) from a + * device report. + */ +void i2400m_report_hook(struct i2400m *i2400m, + const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size) +{ + struct device *dev = i2400m_dev(i2400m); + unsigned msg_type; + + d_fnstart(3, dev, "(i2400m %p l3l4_hdr %p size %zu)\n", + i2400m, l3l4_hdr, size); + /* Chew on the message, we might need some information from + * here */ + msg_type = le16_to_cpu(l3l4_hdr->type); + switch (msg_type) { + case I2400M_MT_REPORT_STATE: /* carrier detection... */ + i2400m_report_state_hook(i2400m, + l3l4_hdr, size, "REPORT STATE"); + break; + /* If the device is ready for power save, then ask it to do + * it. */ + case I2400M_MT_REPORT_POWERSAVE_READY: /* zzzzz */ + if (l3l4_hdr->status == cpu_to_le16(I2400M_MS_DONE_OK)) { + d_printf(1, dev, "ready for powersave, requesting\n"); + i2400m_cmd_enter_powersave(i2400m); + } + break; + }; + d_fnend(3, dev, "(i2400m %p l3l4_hdr %p size %zu) = void\n", + i2400m, l3l4_hdr, size); +} + + +/* + * i2400m_msg_ack_hook - process cmd/set/get ack for internal status + * + * @i2400m: device descriptor + * @l3l4_hdr: pointer to message; it has been already validated for + * consistent size. + * @size: size of the message + * + * Extract information we might need from acks to commands and act on + * it. This is akin to i2400m_report_hook(). Note most of this + * processing should be done in the function that calls the + * command. This is here for some cases where it can't happen... + */ +void i2400m_msg_ack_hook(struct i2400m *i2400m, + const struct i2400m_l3l4_hdr *l3l4_hdr, size_t size) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + unsigned ack_type, ack_status; + char strerr[32]; + + /* Chew on the message, we might need some information from + * here */ + ack_type = le16_to_cpu(l3l4_hdr->type); + ack_status = le16_to_cpu(l3l4_hdr->status); + switch (ack_type) { + case I2400M_MT_CMD_ENTER_POWERSAVE: + /* This is just left here for the sake of example, as + * the processing is done somewhere else. */ + if (0) { + result = i2400m_msg_check_status( + l3l4_hdr, strerr, sizeof(strerr)); + if (result >= 0) + d_printf(1, dev, "ready for power save: %zd\n", + size); + } + break; + }; + return; +} + + +/* + * i2400m_msg_size_check() - verify message size and header are congruent + * + * It is ok if the total message size is larger than the expected + * size, as there can be padding. + */ +int i2400m_msg_size_check(struct i2400m *i2400m, + const struct i2400m_l3l4_hdr *l3l4_hdr, + size_t msg_size) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + size_t expected_size; + d_fnstart(4, dev, "(i2400m %p l3l4_hdr %p msg_size %zu)\n", + i2400m, l3l4_hdr, msg_size); + if (msg_size < sizeof(*l3l4_hdr)) { + dev_err(dev, "bad size for message header " + "(expected at least %zu, got %zu)\n", + (size_t) sizeof(*l3l4_hdr), msg_size); + result = -EIO; + goto error_hdr_size; + } + expected_size = le16_to_cpu(l3l4_hdr->length) + sizeof(*l3l4_hdr); + if (msg_size < expected_size) { + dev_err(dev, "bad size for message code 0x%04x (expected %zu, " + "got %zu)\n", le16_to_cpu(l3l4_hdr->type), + expected_size, msg_size); + result = -EIO; + } else + result = 0; +error_hdr_size: + d_fnend(4, dev, + "(i2400m %p l3l4_hdr %p msg_size %zu) = %d\n", + i2400m, l3l4_hdr, msg_size, result); + return result; +} + + + +/* + * Cancel a wait for a command ACK + * + * @i2400m: device descriptor + * @code: [negative] errno code to cancel with (don't use + * -EINPROGRESS) + * + * If there is an ack already filled out, free it. + */ +void i2400m_msg_to_dev_cancel_wait(struct i2400m *i2400m, int code) +{ + struct sk_buff *ack_skb; + unsigned long flags; + + spin_lock_irqsave(&i2400m->rx_lock, flags); + ack_skb = i2400m->ack_skb; + if (ack_skb && !IS_ERR(ack_skb)) + kfree(ack_skb); + i2400m->ack_skb = ERR_PTR(code); + spin_unlock_irqrestore(&i2400m->rx_lock, flags); +} + + +/** + * i2400m_msg_to_dev - Send a control message to the device and get a response + * + * @i2400m: device descriptor + * + * @msg_skb: an skb * + * + * @buf: pointer to the buffer containing the message to be sent; it + * has to start with a &struct i2400M_l3l4_hdr and then + * followed by the payload. Once this function returns, the + * buffer can be reused. + * + * @buf_len: buffer size + * + * Returns: + * + * Pointer to skb containing the ack message. You need to check the + * pointer with IS_ERR(), as it might be an error code. Error codes + * could happen because: + * + * - the message wasn't formatted correctly + * - couldn't send the message + * - failed waiting for a response + * - the ack message wasn't formatted correctly + * + * The returned skb has been allocated with wimax_msg_to_user_alloc(), + * it contains the reponse in a netlink attribute and is ready to be + * passed up to user space with wimax_msg_to_user_send(). To access + * the payload and its length, use wimax_msg_{data,len}() on the skb. + * + * The skb has to be freed with kfree_skb() once done. + * + * Description: + * + * This function delivers a message/command to the device and waits + * for an ack to be received. The format is described in + * linux/wimax/i2400m.h. In summary, a command/get/set is followed by an + * ack. + * + * This function will not check the ack status, that's left up to the + * caller. Once done with the ack skb, it has to be kfree_skb()ed. + * + * The i2400m handles only one message at the same time, thus we need + * the mutex to exclude other players. + * + * We write the message and then wait for an answer to come back. The + * RX path intercepts control messages and handles them in + * i2400m_rx_ctl(). Reports (notifications) are (maybe) processed + * locally and then forwarded (as needed) to user space on the WiMAX + * stack message pipe. Acks are saved and passed back to us through an + * skb in i2400m->ack_skb which is ready to be given to generic + * netlink if need be. + */ +struct sk_buff *i2400m_msg_to_dev(struct i2400m *i2400m, + const void *buf, size_t buf_len) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_l3l4_hdr *msg_l3l4_hdr; + struct sk_buff *ack_skb; + const struct i2400m_l3l4_hdr *ack_l3l4_hdr; + size_t ack_len; + int ack_timeout; + unsigned msg_type; + unsigned long flags; + + d_fnstart(3, dev, "(i2400m %p buf %p len %zu)\n", + i2400m, buf, buf_len); + + if (i2400m->boot_mode) + return ERR_PTR(-ENODEV); + + msg_l3l4_hdr = buf; + /* Check msg & payload consistency */ + result = i2400m_msg_size_check(i2400m, msg_l3l4_hdr, buf_len); + if (result < 0) + goto error_bad_msg; + msg_type = le16_to_cpu(msg_l3l4_hdr->type); + d_printf(1, dev, "CMD/GET/SET 0x%04x %zu bytes\n", + msg_type, buf_len); + d_dump(2, dev, buf, buf_len); + + /* Setup the completion, ack_skb ("we are waiting") and send + * the message to the device */ + mutex_lock(&i2400m->msg_mutex); + spin_lock_irqsave(&i2400m->rx_lock, flags); + i2400m->ack_skb = ERR_PTR(-EINPROGRESS); + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + init_completion(&i2400m->msg_completion); + result = i2400m_tx(i2400m, buf, buf_len, I2400M_PT_CTRL); + if (result < 0) { + dev_err(dev, "can't send message 0x%04x: %d\n", + le16_to_cpu(msg_l3l4_hdr->type), result); + goto error_tx; + } + + /* Some commands take longer to execute because of crypto ops, + * so we give them some more leeway on timeout */ + switch (msg_type) { + case I2400M_MT_GET_TLS_OPERATION_RESULT: + case I2400M_MT_CMD_SEND_EAP_RESPONSE: + ack_timeout = 5 * HZ; + break; + default: + ack_timeout = HZ; + }; + + /* The RX path in rx.c will put any response for this message + * in i2400m->ack_skb and wake us up. If we cancel the wait, + * we need to change the value of i2400m->ack_skb to something + * not -EINPROGRESS so RX knows there is no one waiting. */ + result = wait_for_completion_interruptible_timeout( + &i2400m->msg_completion, ack_timeout); + if (result == 0) { + dev_err(dev, "timeout waiting for reply to message 0x%04x\n", + msg_type); + result = -ETIMEDOUT; + i2400m_msg_to_dev_cancel_wait(i2400m, result); + goto error_wait_for_completion; + } else if (result < 0) { + dev_err(dev, "error waiting for reply to message 0x%04x: %d\n", + msg_type, result); + i2400m_msg_to_dev_cancel_wait(i2400m, result); + goto error_wait_for_completion; + } + + /* Pull out the ack data from i2400m->ack_skb -- see if it is + * an error and act accordingly */ + spin_lock_irqsave(&i2400m->rx_lock, flags); + ack_skb = i2400m->ack_skb; + if (IS_ERR(ack_skb)) + result = PTR_ERR(ack_skb); + else + result = 0; + i2400m->ack_skb = NULL; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + if (result < 0) + goto error_ack_status; + ack_l3l4_hdr = wimax_msg_data_len(ack_skb, &ack_len); + + /* Check the ack and deliver it if it is ok */ + result = i2400m_msg_size_check(i2400m, ack_l3l4_hdr, ack_len); + if (result < 0) { + dev_err(dev, "HW BUG? reply to message 0x%04x: %d\n", + msg_type, result); + goto error_bad_ack_len; + } + if (msg_type != le16_to_cpu(ack_l3l4_hdr->type)) { + dev_err(dev, "HW BUG? bad reply 0x%04x to message 0x%04x\n", + le16_to_cpu(ack_l3l4_hdr->type), msg_type); + result = -EIO; + goto error_bad_ack_type; + } + i2400m_msg_ack_hook(i2400m, ack_l3l4_hdr, ack_len); + mutex_unlock(&i2400m->msg_mutex); + d_fnend(3, dev, "(i2400m %p buf %p len %zu) = %p\n", + i2400m, buf, buf_len, ack_skb); + return ack_skb; + +error_bad_ack_type: +error_bad_ack_len: + kfree_skb(ack_skb); +error_ack_status: +error_wait_for_completion: +error_tx: + mutex_unlock(&i2400m->msg_mutex); +error_bad_msg: + d_fnend(3, dev, "(i2400m %p buf %p len %zu) = %d\n", + i2400m, buf, buf_len, result); + return ERR_PTR(result); +} + + +/* + * Definitions for the Enter Power Save command + * + * The Enter Power Save command requests the device to go into power + * saving mode. The device will ack or nak the command depending on it + * being ready for it. If it acks, we tell the USB subsystem to + * + * As well, the device might request to go into power saving mode by + * sending a report (REPORT_POWERSAVE_READY), in which case, we issue + * this command. The hookups in the RX coder allow + */ +enum { + I2400M_WAKEUP_ENABLED = 0x01, + I2400M_WAKEUP_DISABLED = 0x02, + I2400M_TLV_TYPE_WAKEUP_MODE = 144, +}; + +struct i2400m_cmd_enter_power_save { + struct i2400m_l3l4_hdr hdr; + struct i2400m_tlv_hdr tlv; + __le32 val; +} __attribute__((packed)); + + +/* + * Request entering power save + * + * This command is (mainly) executed when the device indicates that it + * is ready to go into powersave mode via a REPORT_POWERSAVE_READY. + */ +int i2400m_cmd_enter_powersave(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + struct i2400m_cmd_enter_power_save *cmd; + char strerr[32]; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_alloc; + cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_ENTER_POWERSAVE); + cmd->hdr.length = cpu_to_le16(sizeof(*cmd) - sizeof(cmd->hdr)); + cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); + cmd->tlv.type = cpu_to_le16(I2400M_TLV_TYPE_WAKEUP_MODE); + cmd->tlv.length = cpu_to_le16(sizeof(cmd->val)); + cmd->val = cpu_to_le32(I2400M_WAKEUP_ENABLED); + + ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); + result = PTR_ERR(ack_skb); + if (IS_ERR(ack_skb)) { + dev_err(dev, "Failed to issue 'Enter power save' command: %d\n", + result); + goto error_msg_to_dev; + } + result = i2400m_msg_check_status(wimax_msg_data(ack_skb), + strerr, sizeof(strerr)); + if (result == -EACCES) + d_printf(1, dev, "Cannot enter power save mode\n"); + else if (result < 0) + dev_err(dev, "'Enter power save' (0x%04x) command failed: " + "%d - %s\n", I2400M_MT_CMD_ENTER_POWERSAVE, + result, strerr); + else + d_printf(1, dev, "device ready to power save\n"); + kfree_skb(ack_skb); +error_msg_to_dev: + kfree(cmd); +error_alloc: + return result; +} +EXPORT_SYMBOL_GPL(i2400m_cmd_enter_powersave); + + +/* + * Definitions for getting device information + */ +enum { + I2400M_TLV_DETAILED_DEVICE_INFO = 140 +}; + +/** + * i2400m_get_device_info - Query the device for detailed device information + * + * @i2400m: device descriptor + * + * Returns: an skb whose skb->data points to a 'struct + * i2400m_tlv_detailed_device_info'. When done, kfree_skb() it. The + * skb is *guaranteed* to contain the whole TLV data structure. + * + * On error, IS_ERR(skb) is true and ERR_PTR(skb) is the error + * code. + */ +struct sk_buff *i2400m_get_device_info(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + struct i2400m_l3l4_hdr *cmd; + const struct i2400m_l3l4_hdr *ack; + size_t ack_len; + const struct i2400m_tlv_hdr *tlv; + const struct i2400m_tlv_detailed_device_info *ddi; + char strerr[32]; + + ack_skb = ERR_PTR(-ENOMEM); + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_alloc; + cmd->type = cpu_to_le16(I2400M_MT_GET_DEVICE_INFO); + cmd->length = 0; + cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); + + ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); + if (IS_ERR(ack_skb)) { + dev_err(dev, "Failed to issue 'get device info' command: %ld\n", + PTR_ERR(ack_skb)); + goto error_msg_to_dev; + } + ack = wimax_msg_data_len(ack_skb, &ack_len); + result = i2400m_msg_check_status(ack, strerr, sizeof(strerr)); + if (result < 0) { + dev_err(dev, "'get device info' (0x%04x) command failed: " + "%d - %s\n", I2400M_MT_GET_DEVICE_INFO, result, + strerr); + goto error_cmd_failed; + } + tlv = i2400m_tlv_find(i2400m, ack->pl, ack_len - sizeof(*ack), + I2400M_TLV_DETAILED_DEVICE_INFO, sizeof(*ddi)); + if (tlv == NULL) { + dev_err(dev, "GET DEVICE INFO: " + "detailed device info TLV not found (0x%04x)\n", + I2400M_TLV_DETAILED_DEVICE_INFO); + result = -EIO; + goto error_no_tlv; + } + skb_pull(ack_skb, (void *) tlv - (void *) ack_skb->data); +error_msg_to_dev: + kfree(cmd); +error_alloc: + return ack_skb; + +error_no_tlv: +error_cmd_failed: + kfree_skb(ack_skb); + kfree(cmd); + return ERR_PTR(result); +} + + +/* Firmware interface versions we support */ +enum { + I2400M_HDIv_MAJOR = 9, + I2400M_HDIv_MAJOR_2 = 8, + I2400M_HDIv_MINOR = 1, +}; + + +/** + * i2400m_firmware_check - check firmware versions are compatible with + * the driver + * + * @i2400m: device descriptor + * + * Returns: 0 if ok, < 0 errno code an error and a message in the + * kernel log. + * + * Long function, but quite simple; first chunk launches the command + * and double checks the reply for the right TLV. Then we process the + * TLV (where the meat is). + */ +int i2400m_firmware_check(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + struct i2400m_l3l4_hdr *cmd; + const struct i2400m_l3l4_hdr *ack; + size_t ack_len; + const struct i2400m_tlv_hdr *tlv; + const struct i2400m_tlv_l4_message_versions *l4mv; + char strerr[32]; + unsigned major, minor, branch; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_alloc; + cmd->type = cpu_to_le16(I2400M_MT_GET_LM_VERSION); + cmd->length = 0; + cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); + + ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); + if (IS_ERR(ack_skb)) { + result = PTR_ERR(ack_skb); + dev_err(dev, "Failed to issue 'get lm version' command: %-d\n", + result); + goto error_msg_to_dev; + } + ack = wimax_msg_data_len(ack_skb, &ack_len); + result = i2400m_msg_check_status(ack, strerr, sizeof(strerr)); + if (result < 0) { + dev_err(dev, "'get lm version' (0x%04x) command failed: " + "%d - %s\n", I2400M_MT_GET_LM_VERSION, result, + strerr); + goto error_cmd_failed; + } + tlv = i2400m_tlv_find(i2400m, ack->pl, ack_len - sizeof(*ack), + I2400M_TLV_L4_MESSAGE_VERSIONS, sizeof(*l4mv)); + if (tlv == NULL) { + dev_err(dev, "get lm version: TLV not found (0x%04x)\n", + I2400M_TLV_L4_MESSAGE_VERSIONS); + result = -EIO; + goto error_no_tlv; + } + l4mv = container_of(tlv, typeof(*l4mv), hdr); + major = le16_to_cpu(l4mv->major); + minor = le16_to_cpu(l4mv->minor); + branch = le16_to_cpu(l4mv->branch); + result = -EINVAL; + if (major != I2400M_HDIv_MAJOR + && major != I2400M_HDIv_MAJOR_2) { + dev_err(dev, "unsupported major fw interface version " + "%u.%u.%u\n", major, minor, branch); + goto error_bad_major; + } + if (major == I2400M_HDIv_MAJOR_2) + dev_err(dev, "deprecated major fw interface version " + "%u.%u.%u\n", major, minor, branch); + result = 0; + if (minor != I2400M_HDIv_MINOR) + dev_warn(dev, "untested minor fw firmware version %u.%u.%u\n", + major, minor, branch); +error_bad_major: + dev_info(dev, "firmware interface version %u.%u.%u\n", + major, minor, branch); +error_no_tlv: +error_cmd_failed: + kfree_skb(ack_skb); +error_msg_to_dev: + kfree(cmd); +error_alloc: + return result; +} + + +/* + * Send an DoExitIdle command to the device to ask it to go out of + * basestation-idle mode. + * + * @i2400m: device descriptor + * + * This starts a renegotiation with the basestation that might involve + * another crypto handshake with user space. + * + * Returns: 0 if ok, < 0 errno code on error. + */ +int i2400m_cmd_exit_idle(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + struct i2400m_l3l4_hdr *cmd; + char strerr[32]; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_alloc; + cmd->type = cpu_to_le16(I2400M_MT_CMD_EXIT_IDLE); + cmd->length = 0; + cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); + + ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); + result = PTR_ERR(ack_skb); + if (IS_ERR(ack_skb)) { + dev_err(dev, "Failed to issue 'exit idle' command: %d\n", + result); + goto error_msg_to_dev; + } + result = i2400m_msg_check_status(wimax_msg_data(ack_skb), + strerr, sizeof(strerr)); + kfree_skb(ack_skb); +error_msg_to_dev: + kfree(cmd); +error_alloc: + return result; + +} + + +/* + * Query the device for its state, update the WiMAX stack's idea of it + * + * @i2400m: device descriptor + * + * Returns: 0 if ok, < 0 errno code on error. + * + * Executes a 'Get State' command and parses the returned + * TLVs. + * + * Because this is almost identical to a 'Report State', we use + * i2400m_report_state_hook() to parse the answer. This will set the + * carrier state, as well as the RF Kill switches state. + */ +int i2400m_cmd_get_state(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + struct i2400m_l3l4_hdr *cmd; + const struct i2400m_l3l4_hdr *ack; + size_t ack_len; + char strerr[32]; + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_alloc; + cmd->type = cpu_to_le16(I2400M_MT_GET_STATE); + cmd->length = 0; + cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); + + ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); + if (IS_ERR(ack_skb)) { + dev_err(dev, "Failed to issue 'get state' command: %ld\n", + PTR_ERR(ack_skb)); + result = PTR_ERR(ack_skb); + goto error_msg_to_dev; + } + ack = wimax_msg_data_len(ack_skb, &ack_len); + result = i2400m_msg_check_status(ack, strerr, sizeof(strerr)); + if (result < 0) { + dev_err(dev, "'get state' (0x%04x) command failed: " + "%d - %s\n", I2400M_MT_GET_STATE, result, strerr); + goto error_cmd_failed; + } + i2400m_report_state_hook(i2400m, ack, ack_len - sizeof(*ack), + "GET STATE"); + result = 0; + kfree_skb(ack_skb); +error_cmd_failed: +error_msg_to_dev: + kfree(cmd); +error_alloc: + return result; +} +EXPORT_SYMBOL_GPL(i2400m_cmd_get_state); + + +/** + * Set basic configuration settings + * + * @i2400m: device descriptor + * @args: array of pointers to the TLV headers to send for + * configuration (each followed by its payload). + * TLV headers and payloads must be properly initialized, with the + * right endianess (LE). + * @arg_size: number of pointers in the @args array + */ +int i2400m_set_init_config(struct i2400m *i2400m, + const struct i2400m_tlv_hdr **arg, size_t args) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + struct i2400m_l3l4_hdr *cmd; + char strerr[32]; + unsigned argc, argsize, tlv_size; + const struct i2400m_tlv_hdr *tlv_hdr; + void *buf, *itr; + + d_fnstart(3, dev, "(i2400m %p arg %p args %zu)\n", i2400m, arg, args); + result = 0; + if (args == 0) + goto none; + /* Compute the size of all the TLVs, so we can alloc a + * contiguous command block to copy them. */ + argsize = 0; + for (argc = 0; argc < args; argc++) { + tlv_hdr = arg[argc]; + argsize += sizeof(*tlv_hdr) + le16_to_cpu(tlv_hdr->length); + } + WARN_ON(argc >= 9); /* As per hw spec */ + + /* Alloc the space for the command and TLVs*/ + result = -ENOMEM; + buf = kzalloc(sizeof(*cmd) + argsize, GFP_KERNEL); + if (buf == NULL) + goto error_alloc; + cmd = buf; + cmd->type = cpu_to_le16(I2400M_MT_SET_INIT_CONFIG); + cmd->length = cpu_to_le16(argsize); + cmd->version = cpu_to_le16(I2400M_L3L4_VERSION); + + /* Copy the TLVs */ + itr = buf + sizeof(*cmd); + for (argc = 0; argc < args; argc++) { + tlv_hdr = arg[argc]; + tlv_size = sizeof(*tlv_hdr) + le16_to_cpu(tlv_hdr->length); + memcpy(itr, tlv_hdr, tlv_size); + itr += tlv_size; + } + + /* Send the message! */ + ack_skb = i2400m_msg_to_dev(i2400m, buf, sizeof(*cmd) + argsize); + result = PTR_ERR(ack_skb); + if (IS_ERR(ack_skb)) { + dev_err(dev, "Failed to issue 'init config' command: %d\n", + result); + + goto error_msg_to_dev; + } + result = i2400m_msg_check_status(wimax_msg_data(ack_skb), + strerr, sizeof(strerr)); + if (result < 0) + dev_err(dev, "'init config' (0x%04x) command failed: %d - %s\n", + I2400M_MT_SET_INIT_CONFIG, result, strerr); + kfree_skb(ack_skb); +error_msg_to_dev: + kfree(buf); +error_alloc: +none: + d_fnend(3, dev, "(i2400m %p arg %p args %zu) = %d\n", + i2400m, arg, args, result); + return result; + +} +EXPORT_SYMBOL_GPL(i2400m_set_init_config); + + +/** + * i2400m_dev_initialize - Initialize the device once communications are ready + * + * @i2400m: device descriptor + * + * Returns: 0 if ok, < 0 errno code on error. + * + * Configures the device to work the way we like it. + * + * At the point of this call, the device is registered with the WiMAX + * and netdev stacks, firmware is uploaded and we can talk to the + * device normally. + */ +int i2400m_dev_initialize(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct i2400m_tlv_config_idle_parameters idle_params; + const struct i2400m_tlv_hdr *args[9]; + unsigned argc = 0; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + /* Useless for now...might change */ + if (i2400m_idle_mode_disabled) { + idle_params.hdr.type = + cpu_to_le16(I2400M_TLV_CONFIG_IDLE_PARAMETERS); + idle_params.hdr.length = cpu_to_le16( + sizeof(idle_params) - sizeof(idle_params.hdr)); + idle_params.idle_timeout = 0; + idle_params.idle_paging_interval = 0; + args[argc++] = &idle_params.hdr; + } + result = i2400m_set_init_config(i2400m, args, argc); + if (result < 0) + goto error; + result = i2400m_firmware_check(i2400m); /* fw versions ok? */ + if (result < 0) + goto error; + /* + * Update state: Here it just calls a get state; parsing the + * result (System State TLV and RF Status TLV [done in the rx + * path hooks]) will set the hardware and software RF-Kill + * status. + */ + result = i2400m_cmd_get_state(i2400m); +error: + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; +} + + +/** + * i2400m_dev_shutdown - Shutdown a running device + * + * @i2400m: device descriptor + * + * Gracefully stops the device, moving it to the lowest power + * consumption state possible. + */ +void i2400m_dev_shutdown(struct i2400m *i2400m) +{ + int result = -ENODEV; + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + result = i2400m->bus_reset(i2400m, I2400M_RT_WARM); + d_fnend(3, dev, "(i2400m %p) = void [%d]\n", i2400m, result); + return; +} diff --git a/drivers/net/wimax/i2400m/debug-levels.h b/drivers/net/wimax/i2400m/debug-levels.h new file mode 100644 index 000000000000..3183baa16a52 --- /dev/null +++ b/drivers/net/wimax/i2400m/debug-levels.h @@ -0,0 +1,45 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Debug levels control file for the i2400m module + * + * + * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.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. + * + * 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. + */ +#ifndef __debug_levels__h__ +#define __debug_levels__h__ + +/* Maximum compile and run time debug level for all submodules */ +#define D_MODULENAME i2400m +#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL + +#include <linux/wimax/debug.h> + +/* List of all the enabled modules */ +enum d_module { + D_SUBMODULE_DECLARE(control), + D_SUBMODULE_DECLARE(driver), + D_SUBMODULE_DECLARE(debugfs), + D_SUBMODULE_DECLARE(fw), + D_SUBMODULE_DECLARE(netdev), + D_SUBMODULE_DECLARE(rfkill), + D_SUBMODULE_DECLARE(rx), + D_SUBMODULE_DECLARE(tx), +}; + + +#endif /* #ifndef __debug_levels__h__ */ diff --git a/drivers/net/wimax/i2400m/debugfs.c b/drivers/net/wimax/i2400m/debugfs.c new file mode 100644 index 000000000000..626632985977 --- /dev/null +++ b/drivers/net/wimax/i2400m/debugfs.c @@ -0,0 +1,392 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Debugfs interfaces to manipulate driver and device information + * + * + * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.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. + * + * 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. + */ + +#include <linux/debugfs.h> +#include <linux/netdevice.h> +#include <linux/etherdevice.h> +#include <linux/spinlock.h> +#include <linux/device.h> +#include "i2400m.h" + + +#define D_SUBMODULE debugfs +#include "debug-levels.h" + +static +int debugfs_netdev_queue_stopped_get(void *data, u64 *val) +{ + struct i2400m *i2400m = data; + *val = netif_queue_stopped(i2400m->wimax_dev.net_dev); + return 0; +} +DEFINE_SIMPLE_ATTRIBUTE(fops_netdev_queue_stopped, + debugfs_netdev_queue_stopped_get, + NULL, "%llu\n"); + + +static +struct dentry *debugfs_create_netdev_queue_stopped( + const char *name, struct dentry *parent, struct i2400m *i2400m) +{ + return debugfs_create_file(name, 0400, parent, i2400m, + &fops_netdev_queue_stopped); +} + + +/* + * inode->i_private has the @data argument to debugfs_create_file() + */ +static +int i2400m_stats_open(struct inode *inode, struct file *filp) +{ + filp->private_data = inode->i_private; + return 0; +} + +/* + * We don't allow partial reads of this file, as then the reader would + * get weirdly confused data as it is updated. + * + * So or you read it all or nothing; if you try to read with an offset + * != 0, we consider you are done reading. + */ +static +ssize_t i2400m_rx_stats_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct i2400m *i2400m = filp->private_data; + char buf[128]; + unsigned long flags; + + if (*ppos != 0) + return 0; + if (count < sizeof(buf)) + return -ENOSPC; + spin_lock_irqsave(&i2400m->rx_lock, flags); + snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n", + i2400m->rx_pl_num, i2400m->rx_pl_min, + i2400m->rx_pl_max, i2400m->rx_num, + i2400m->rx_size_acc, + i2400m->rx_size_min, i2400m->rx_size_max); + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); +} + + +/* Any write clears the stats */ +static +ssize_t i2400m_rx_stats_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct i2400m *i2400m = filp->private_data; + unsigned long flags; + + spin_lock_irqsave(&i2400m->rx_lock, flags); + i2400m->rx_pl_num = 0; + i2400m->rx_pl_max = 0; + i2400m->rx_pl_min = UINT_MAX; + i2400m->rx_num = 0; + i2400m->rx_size_acc = 0; + i2400m->rx_size_min = UINT_MAX; + i2400m->rx_size_max = 0; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + return count; +} + +static +const struct file_operations i2400m_rx_stats_fops = { + .owner = THIS_MODULE, + .open = i2400m_stats_open, + .read = i2400m_rx_stats_read, + .write = i2400m_rx_stats_write, +}; + + +/* See i2400m_rx_stats_read() */ +static +ssize_t i2400m_tx_stats_read(struct file *filp, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct i2400m *i2400m = filp->private_data; + char buf[128]; + unsigned long flags; + + if (*ppos != 0) + return 0; + if (count < sizeof(buf)) + return -ENOSPC; + spin_lock_irqsave(&i2400m->tx_lock, flags); + snprintf(buf, sizeof(buf), "%u %u %u %u %u %u %u\n", + i2400m->tx_pl_num, i2400m->tx_pl_min, + i2400m->tx_pl_max, i2400m->tx_num, + i2400m->tx_size_acc, + i2400m->tx_size_min, i2400m->tx_size_max); + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + return simple_read_from_buffer(buffer, count, ppos, buf, strlen(buf)); +} + +/* Any write clears the stats */ +static +ssize_t i2400m_tx_stats_write(struct file *filp, const char __user *buffer, + size_t count, loff_t *ppos) +{ + struct i2400m *i2400m = filp->private_data; + unsigned long flags; + + spin_lock_irqsave(&i2400m->tx_lock, flags); + i2400m->tx_pl_num = 0; + i2400m->tx_pl_max = 0; + i2400m->tx_pl_min = UINT_MAX; + i2400m->tx_num = 0; + i2400m->tx_size_acc = 0; + i2400m->tx_size_min = UINT_MAX; + i2400m->tx_size_max = 0; + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + return count; +} + +static +const struct file_operations i2400m_tx_stats_fops = { + .owner = THIS_MODULE, + .open = i2400m_stats_open, + .read = i2400m_tx_stats_read, + .write = i2400m_tx_stats_write, +}; + + +/* Write 1 to ask the device to go into suspend */ +static +int debugfs_i2400m_suspend_set(void *data, u64 val) +{ + int result; + struct i2400m *i2400m = data; + result = i2400m_cmd_enter_powersave(i2400m); + if (result >= 0) + result = 0; + return result; +} +DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_suspend, + NULL, debugfs_i2400m_suspend_set, + "%llu\n"); + +static +struct dentry *debugfs_create_i2400m_suspend( + const char *name, struct dentry *parent, struct i2400m *i2400m) +{ + return debugfs_create_file(name, 0200, parent, i2400m, + &fops_i2400m_suspend); +} + + +/* + * Reset the device + * + * Write 0 to ask the device to soft reset, 1 to cold reset, 2 to bus + * reset (as defined by enum i2400m_reset_type). + */ +static +int debugfs_i2400m_reset_set(void *data, u64 val) +{ + int result; + struct i2400m *i2400m = data; + enum i2400m_reset_type rt = val; + switch(rt) { + case I2400M_RT_WARM: + case I2400M_RT_COLD: + case I2400M_RT_BUS: + result = i2400m->bus_reset(i2400m, rt); + if (result >= 0) + result = 0; + default: + result = -EINVAL; + } + return result; +} +DEFINE_SIMPLE_ATTRIBUTE(fops_i2400m_reset, + NULL, debugfs_i2400m_reset_set, + "%llu\n"); + +static +struct dentry *debugfs_create_i2400m_reset( + const char *name, struct dentry *parent, struct i2400m *i2400m) +{ + return debugfs_create_file(name, 0200, parent, i2400m, + &fops_i2400m_reset); +} + +/* + * Debug levels control; see debug.h + */ +struct d_level D_LEVEL[] = { + D_SUBMODULE_DEFINE(control), + D_SUBMODULE_DEFINE(driver), + D_SUBMODULE_DEFINE(debugfs), + D_SUBMODULE_DEFINE(fw), + D_SUBMODULE_DEFINE(netdev), + D_SUBMODULE_DEFINE(rfkill), + D_SUBMODULE_DEFINE(rx), + D_SUBMODULE_DEFINE(tx), +}; +size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL); + +#define __debugfs_register(prefix, name, parent) \ +do { \ + result = d_level_register_debugfs(prefix, name, parent); \ + if (result < 0) \ + goto error; \ +} while (0) + + +int i2400m_debugfs_add(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct dentry *dentry = i2400m->wimax_dev.debugfs_dentry; + struct dentry *fd; + + dentry = debugfs_create_dir("i2400m", dentry); + result = PTR_ERR(dentry); + if (IS_ERR(dentry)) { + if (result == -ENODEV) + result = 0; /* No debugfs support */ + goto error; + } + i2400m->debugfs_dentry = dentry; + __debugfs_register("dl_", control, dentry); + __debugfs_register("dl_", driver, dentry); + __debugfs_register("dl_", debugfs, dentry); + __debugfs_register("dl_", fw, dentry); + __debugfs_register("dl_", netdev, dentry); + __debugfs_register("dl_", rfkill, dentry); + __debugfs_register("dl_", rx, dentry); + __debugfs_register("dl_", tx, dentry); + + fd = debugfs_create_size_t("tx_in", 0400, dentry, + &i2400m->tx_in); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "tx_in: %d\n", result); + goto error; + } + + fd = debugfs_create_size_t("tx_out", 0400, dentry, + &i2400m->tx_out); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "tx_out: %d\n", result); + goto error; + } + + fd = debugfs_create_u32("state", 0600, dentry, + &i2400m->state); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "state: %d\n", result); + goto error; + } + + /* + * Trace received messages from user space + * + * In order to tap the bidirectional message stream in the + * 'msg' pipe, user space can read from the 'msg' pipe; + * however, due to limitations in libnl, we can't know what + * the different applications are sending down to the kernel. + * + * So we have this hack where the driver will echo any message + * received on the msg pipe from user space [through a call to + * wimax_dev->op_msg_from_user() into + * i2400m_op_msg_from_user()] into the 'trace' pipe that this + * driver creates. + * + * So then, reading from both the 'trace' and 'msg' pipes in + * user space will provide a full dump of the traffic. + * + * Write 1 to activate, 0 to clear. + * + * It is not really very atomic, but it is also not too + * critical. + */ + fd = debugfs_create_u8("trace_msg_from_user", 0600, dentry, + &i2400m->trace_msg_from_user); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "trace_msg_from_user: %d\n", result); + goto error; + } + + fd = debugfs_create_netdev_queue_stopped("netdev_queue_stopped", + dentry, i2400m); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "netdev_queue_stopped: %d\n", result); + goto error; + } + + fd = debugfs_create_file("rx_stats", 0600, dentry, i2400m, + &i2400m_rx_stats_fops); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "rx_stats: %d\n", result); + goto error; + } + + fd = debugfs_create_file("tx_stats", 0600, dentry, i2400m, + &i2400m_tx_stats_fops); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "tx_stats: %d\n", result); + goto error; + } + + fd = debugfs_create_i2400m_suspend("suspend", dentry, i2400m); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry suspend: %d\n", + result); + goto error; + } + + fd = debugfs_create_i2400m_reset("reset", dentry, i2400m); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry reset: %d\n", result); + goto error; + } + + result = 0; +error: + return result; +} + +void i2400m_debugfs_rm(struct i2400m *i2400m) +{ + debugfs_remove_recursive(i2400m->debugfs_dentry); +} diff --git a/drivers/net/wimax/i2400m/driver.c b/drivers/net/wimax/i2400m/driver.c new file mode 100644 index 000000000000..5f98047e18cf --- /dev/null +++ b/drivers/net/wimax/i2400m/driver.c @@ -0,0 +1,728 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Generic probe/disconnect, reset and message passing + * + * + * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.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. + * + * 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. + * + * + * See i2400m.h for driver documentation. This contains helpers for + * the driver model glue [_setup()/_release()], handling device resets + * [_dev_reset_handle()], and the backends for the WiMAX stack ops + * reset [_op_reset()] and message from user [_op_msg_from_user()]. + * + * ROADMAP: + * + * i2400m_op_msg_from_user() + * i2400m_msg_to_dev() + * wimax_msg_to_user_send() + * + * i2400m_op_reset() + * i240m->bus_reset() + * + * i2400m_dev_reset_handle() + * __i2400m_dev_reset_handle() + * __i2400m_dev_stop() + * __i2400m_dev_start() + * + * i2400m_setup() + * i2400m_bootrom_init() + * register_netdev() + * i2400m_dev_start() + * __i2400m_dev_start() + * i2400m_dev_bootstrap() + * i2400m_tx_setup() + * i2400m->bus_dev_start() + * i2400m_check_mac_addr() + * wimax_dev_add() + * + * i2400m_release() + * wimax_dev_rm() + * i2400m_dev_stop() + * __i2400m_dev_stop() + * i2400m_dev_shutdown() + * i2400m->bus_dev_stop() + * i2400m_tx_release() + * unregister_netdev() + */ +#include "i2400m.h" +#include <linux/wimax/i2400m.h> +#include <linux/module.h> +#include <linux/moduleparam.h> + +#define D_SUBMODULE driver +#include "debug-levels.h" + + +int i2400m_idle_mode_disabled; /* 0 (idle mode enabled) by default */ +module_param_named(idle_mode_disabled, i2400m_idle_mode_disabled, int, 0644); +MODULE_PARM_DESC(idle_mode_disabled, + "If true, the device will not enable idle mode negotiation " + "with the base station (when connected) to save power."); + +/** + * i2400m_queue_work - schedule work on a i2400m's queue + * + * @i2400m: device descriptor + * + * @fn: function to run to execute work. It gets passed a 'struct + * work_struct' that is wrapped in a 'struct i2400m_work'. Once + * done, you have to (1) i2400m_put(i2400m_work->i2400m) and then + * (2) kfree(i2400m_work). + * + * @gfp_flags: GFP flags for memory allocation. + * + * @pl: pointer to a payload buffer that you want to pass to the _work + * function. Use this to pack (for example) a struct with extra + * arguments. + * + * @pl_size: size of the payload buffer. + * + * We do this quite often, so this just saves typing; allocate a + * wrapper for a i2400m, get a ref to it, pack arguments and launch + * the work. + * + * A usual workflow is: + * + * struct my_work_args { + * void *something; + * int whatever; + * }; + * ... + * + * struct my_work_args my_args = { + * .something = FOO, + * .whaetever = BLAH + * }; + * i2400m_queue_work(i2400m, 1, my_work_function, GFP_KERNEL, + * &args, sizeof(args)) + * + * And now the work function can unpack the arguments and call the + * real function (or do the job itself): + * + * static + * void my_work_fn((struct work_struct *ws) + * { + * struct i2400m_work *iw = + * container_of(ws, struct i2400m_work, ws); + * struct my_work_args *my_args = (void *) iw->pl; + * + * my_work(iw->i2400m, my_args->something, my_args->whatevert); + * } + */ +int i2400m_queue_work(struct i2400m *i2400m, + void (*fn)(struct work_struct *), gfp_t gfp_flags, + const void *pl, size_t pl_size) +{ + int result; + struct i2400m_work *iw; + + BUG_ON(i2400m->work_queue == NULL); + result = -ENOMEM; + iw = kzalloc(sizeof(*iw) + pl_size, gfp_flags); + if (iw == NULL) + goto error_kzalloc; + iw->i2400m = i2400m_get(i2400m); + memcpy(iw->pl, pl, pl_size); + INIT_WORK(&iw->ws, fn); + result = queue_work(i2400m->work_queue, &iw->ws); +error_kzalloc: + return result; +} +EXPORT_SYMBOL_GPL(i2400m_queue_work); + + +/* + * Schedule i2400m's specific work on the system's queue. + * + * Used for a few cases where we really need it; otherwise, identical + * to i2400m_queue_work(). + * + * Returns < 0 errno code on error, 1 if ok. + * + * If it returns zero, something really bad happened, as it means the + * works struct was already queued, but we have just allocated it, so + * it should not happen. + */ +int i2400m_schedule_work(struct i2400m *i2400m, + void (*fn)(struct work_struct *), gfp_t gfp_flags) +{ + int result; + struct i2400m_work *iw; + + BUG_ON(i2400m->work_queue == NULL); + result = -ENOMEM; + iw = kzalloc(sizeof(*iw), gfp_flags); + if (iw == NULL) + goto error_kzalloc; + iw->i2400m = i2400m_get(i2400m); + INIT_WORK(&iw->ws, fn); + result = schedule_work(&iw->ws); + if (result == 0) + result = -ENXIO; +error_kzalloc: + return result; +} + + +/* + * WiMAX stack operation: relay a message from user space + * + * @wimax_dev: device descriptor + * @pipe_name: named pipe the message is for + * @msg_buf: pointer to the message bytes + * @msg_len: length of the buffer + * @genl_info: passed by the generic netlink layer + * + * The WiMAX stack will call this function when a message was received + * from user space. + * + * For the i2400m, this is an L3L4 message, as specified in + * include/linux/wimax/i2400m.h, and thus prefixed with a 'struct + * i2400m_l3l4_hdr'. Driver (and device) expect the messages to be + * coded in Little Endian. + * + * This function just verifies that the header declaration and the + * payload are consistent and then deals with it, either forwarding it + * to the device or procesing it locally. + * + * In the i2400m, messages are basically commands that will carry an + * ack, so we use i2400m_msg_to_dev() and then deliver the ack back to + * user space. The rx.c code might intercept the response and use it + * to update the driver's state, but then it will pass it on so it can + * be relayed back to user space. + * + * Note that asynchronous events from the device are processed and + * sent to user space in rx.c. + */ +static +int i2400m_op_msg_from_user(struct wimax_dev *wimax_dev, + const char *pipe_name, + const void *msg_buf, size_t msg_len, + const struct genl_info *genl_info) +{ + int result; + struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev); + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + + d_fnstart(4, dev, "(wimax_dev %p [i2400m %p] msg_buf %p " + "msg_len %zu genl_info %p)\n", wimax_dev, i2400m, + msg_buf, msg_len, genl_info); + ack_skb = i2400m_msg_to_dev(i2400m, msg_buf, msg_len); + result = PTR_ERR(ack_skb); + if (IS_ERR(ack_skb)) + goto error_msg_to_dev; + if (unlikely(i2400m->trace_msg_from_user)) + wimax_msg(&i2400m->wimax_dev, "trace", + msg_buf, msg_len, GFP_KERNEL); + result = wimax_msg_send(&i2400m->wimax_dev, ack_skb); +error_msg_to_dev: + d_fnend(4, dev, "(wimax_dev %p [i2400m %p] msg_buf %p msg_len %zu " + "genl_info %p) = %d\n", wimax_dev, i2400m, msg_buf, msg_len, + genl_info, result); + return result; +} + + +/* + * Context to wait for a reset to finalize + */ +struct i2400m_reset_ctx { + struct completion completion; + int result; +}; + + +/* + * WiMAX stack operation: reset a device + * + * @wimax_dev: device descriptor + * + * See the documentation for wimax_reset() and wimax_dev->op_reset for + * the requirements of this function. The WiMAX stack guarantees + * serialization on calls to this function. + * + * Do a warm reset on the device; if it fails, resort to a cold reset + * and return -ENODEV. On successful warm reset, we need to block + * until it is complete. + * + * The bus-driver implementation of reset takes care of falling back + * to cold reset if warm fails. + */ +static +int i2400m_op_reset(struct wimax_dev *wimax_dev) +{ + int result; + struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev); + struct device *dev = i2400m_dev(i2400m); + struct i2400m_reset_ctx ctx = { + .completion = COMPLETION_INITIALIZER_ONSTACK(ctx.completion), + .result = 0, + }; + + d_fnstart(4, dev, "(wimax_dev %p)\n", wimax_dev); + mutex_lock(&i2400m->init_mutex); + i2400m->reset_ctx = &ctx; + mutex_unlock(&i2400m->init_mutex); + result = i2400m->bus_reset(i2400m, I2400M_RT_WARM); + if (result < 0) + goto out; + result = wait_for_completion_timeout(&ctx.completion, 4*HZ); + if (result == 0) + result = -ETIMEDOUT; + else if (result > 0) + result = ctx.result; + /* if result < 0, pass it on */ + mutex_lock(&i2400m->init_mutex); + i2400m->reset_ctx = NULL; + mutex_unlock(&i2400m->init_mutex); +out: + d_fnend(4, dev, "(wimax_dev %p) = %d\n", wimax_dev, result); + return result; +} + + +/* + * Check the MAC address we got from boot mode is ok + * + * @i2400m: device descriptor + * + * Returns: 0 if ok, < 0 errno code on error. + */ +static +int i2400m_check_mac_addr(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *skb; + const struct i2400m_tlv_detailed_device_info *ddi; + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + const unsigned char zeromac[ETH_ALEN] = { 0 }; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + skb = i2400m_get_device_info(i2400m); + if (IS_ERR(skb)) { + result = PTR_ERR(skb); + dev_err(dev, "Cannot verify MAC address, error reading: %d\n", + result); + goto error; + } + /* Extract MAC addresss */ + ddi = (void *) skb->data; + BUILD_BUG_ON(ETH_ALEN != sizeof(ddi->mac_address)); + d_printf(2, dev, "GET DEVICE INFO: mac addr " + "%02x:%02x:%02x:%02x:%02x:%02x\n", + ddi->mac_address[0], ddi->mac_address[1], + ddi->mac_address[2], ddi->mac_address[3], + ddi->mac_address[4], ddi->mac_address[5]); + if (!memcmp(net_dev->perm_addr, ddi->mac_address, + sizeof(ddi->mac_address))) + goto ok; + dev_warn(dev, "warning: device reports a different MAC address " + "to that of boot mode's\n"); + dev_warn(dev, "device reports %02x:%02x:%02x:%02x:%02x:%02x\n", + ddi->mac_address[0], ddi->mac_address[1], + ddi->mac_address[2], ddi->mac_address[3], + ddi->mac_address[4], ddi->mac_address[5]); + dev_warn(dev, "boot mode reported %02x:%02x:%02x:%02x:%02x:%02x\n", + net_dev->perm_addr[0], net_dev->perm_addr[1], + net_dev->perm_addr[2], net_dev->perm_addr[3], + net_dev->perm_addr[4], net_dev->perm_addr[5]); + if (!memcmp(zeromac, ddi->mac_address, sizeof(zeromac))) + dev_err(dev, "device reports an invalid MAC address, " + "not updating\n"); + else { + dev_warn(dev, "updating MAC address\n"); + net_dev->addr_len = ETH_ALEN; + memcpy(net_dev->perm_addr, ddi->mac_address, ETH_ALEN); + memcpy(net_dev->dev_addr, ddi->mac_address, ETH_ALEN); + } +ok: + result = 0; + kfree_skb(skb); +error: + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; +} + + +/** + * __i2400m_dev_start - Bring up driver communication with the device + * + * @i2400m: device descriptor + * @flags: boot mode flags + * + * Returns: 0 if ok, < 0 errno code on error. + * + * Uploads firmware and brings up all the resources needed to be able + * to communicate with the device. + * + * TX needs to be setup before the bus-specific code (otherwise on + * shutdown, the bus-tx code could try to access it). + */ +static +int __i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri flags) +{ + int result; + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + struct net_device *net_dev = wimax_dev->net_dev; + struct device *dev = i2400m_dev(i2400m); + int times = 3; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); +retry: + result = i2400m_dev_bootstrap(i2400m, flags); + if (result < 0) { + dev_err(dev, "cannot bootstrap device: %d\n", result); + goto error_bootstrap; + } + result = i2400m_tx_setup(i2400m); + if (result < 0) + goto error_tx_setup; + result = i2400m->bus_dev_start(i2400m); + if (result < 0) + goto error_bus_dev_start; + i2400m->work_queue = create_singlethread_workqueue(wimax_dev->name); + if (i2400m->work_queue == NULL) { + result = -ENOMEM; + dev_err(dev, "cannot create workqueue\n"); + goto error_create_workqueue; + } + /* At this point is ok to send commands to the device */ + result = i2400m_check_mac_addr(i2400m); + if (result < 0) + goto error_check_mac_addr; + i2400m->ready = 1; + wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED); + result = i2400m_dev_initialize(i2400m); + if (result < 0) + goto error_dev_initialize; + /* At this point, reports will come for the device and set it + * to the right state if it is different than UNINITIALIZED */ + d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n", + net_dev, i2400m, result); + return result; + +error_dev_initialize: +error_check_mac_addr: + destroy_workqueue(i2400m->work_queue); +error_create_workqueue: + i2400m->bus_dev_stop(i2400m); +error_bus_dev_start: + i2400m_tx_release(i2400m); +error_tx_setup: +error_bootstrap: + if (result == -ERESTARTSYS && times-- > 0) { + flags = I2400M_BRI_SOFT; + goto retry; + } + d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n", + net_dev, i2400m, result); + return result; +} + + +static +int i2400m_dev_start(struct i2400m *i2400m, enum i2400m_bri bm_flags) +{ + int result; + mutex_lock(&i2400m->init_mutex); /* Well, start the device */ + result = __i2400m_dev_start(i2400m, bm_flags); + if (result >= 0) + i2400m->updown = 1; + mutex_unlock(&i2400m->init_mutex); + return result; +} + + +/** + * i2400m_dev_stop - Tear down driver communication with the device + * + * @i2400m: device descriptor + * + * Returns: 0 if ok, < 0 errno code on error. + * + * Releases all the resources allocated to communicate with the device. + */ +static +void __i2400m_dev_stop(struct i2400m *i2400m) +{ + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + wimax_state_change(wimax_dev, __WIMAX_ST_QUIESCING); + i2400m_dev_shutdown(i2400m); + i2400m->ready = 0; + destroy_workqueue(i2400m->work_queue); + i2400m->bus_dev_stop(i2400m); + i2400m_tx_release(i2400m); + wimax_state_change(wimax_dev, WIMAX_ST_DOWN); + d_fnend(3, dev, "(i2400m %p) = 0\n", i2400m); +} + + +/* + * Watch out -- we only need to stop if there is a need for it. The + * device could have reset itself and failed to come up again (see + * _i2400m_dev_reset_handle()). + */ +static +void i2400m_dev_stop(struct i2400m *i2400m) +{ + mutex_lock(&i2400m->init_mutex); + if (i2400m->updown) { + __i2400m_dev_stop(i2400m); + i2400m->updown = 0; + } + mutex_unlock(&i2400m->init_mutex); +} + + +/* + * The device has rebooted; fix up the device and the driver + * + * Tear down the driver communication with the device, reload the + * firmware and reinitialize the communication with the device. + * + * If someone calls a reset when the device's firmware is down, in + * theory we won't see it because we are not listening. However, just + * in case, leave the code to handle it. + * + * If there is a reset context, use it; this means someone is waiting + * for us to tell him when the reset operation is complete and the + * device is ready to rock again. + * + * NOTE: if we are in the process of bringing up or down the + * communication with the device [running i2400m_dev_start() or + * _stop()], don't do anything, let it fail and handle it. + * + * This function is ran always in a thread context + */ +static +void __i2400m_dev_reset_handle(struct work_struct *ws) +{ + int result; + struct i2400m_work *iw = container_of(ws, struct i2400m_work, ws); + struct i2400m *i2400m = iw->i2400m; + struct device *dev = i2400m_dev(i2400m); + enum wimax_st wimax_state; + struct i2400m_reset_ctx *ctx = i2400m->reset_ctx; + + d_fnstart(3, dev, "(ws %p i2400m %p)\n", ws, i2400m); + result = 0; + if (mutex_trylock(&i2400m->init_mutex) == 0) { + /* We are still in i2400m_dev_start() [let it fail] or + * i2400m_dev_stop() [we are shutting down anyway, so + * ignore it] or we are resetting somewhere else. */ + dev_err(dev, "device rebooted\n"); + i2400m_msg_to_dev_cancel_wait(i2400m, -ERESTARTSYS); + complete(&i2400m->msg_completion); + goto out; + } + wimax_state = wimax_state_get(&i2400m->wimax_dev); + if (wimax_state < WIMAX_ST_UNINITIALIZED) { + dev_info(dev, "device rebooted: it is down, ignoring\n"); + goto out_unlock; /* ifconfig up/down wasn't called */ + } + dev_err(dev, "device rebooted: reinitializing driver\n"); + __i2400m_dev_stop(i2400m); + i2400m->updown = 0; + result = __i2400m_dev_start(i2400m, + I2400M_BRI_SOFT | I2400M_BRI_MAC_REINIT); + if (result < 0) { + dev_err(dev, "device reboot: cannot start the device: %d\n", + result); + result = i2400m->bus_reset(i2400m, I2400M_RT_BUS); + if (result >= 0) + result = -ENODEV; + } else + i2400m->updown = 1; +out_unlock: + if (i2400m->reset_ctx) { + ctx->result = result; + complete(&ctx->completion); + } + mutex_unlock(&i2400m->init_mutex); +out: + i2400m_put(i2400m); + kfree(iw); + d_fnend(3, dev, "(ws %p i2400m %p) = void\n", ws, i2400m); + return; +} + + +/** + * i2400m_dev_reset_handle - Handle a device's reset in a thread context + * + * Schedule a device reset handling out on a thread context, so it + * is safe to call from atomic context. We can't use the i2400m's + * queue as we are going to destroy it and reinitialize it as part of + * the driver bringup/bringup process. + * + * See __i2400m_dev_reset_handle() for details; that takes care of + * reinitializing the driver to handle the reset, calling into the + * bus-specific functions ops as needed. + */ +int i2400m_dev_reset_handle(struct i2400m *i2400m) +{ + return i2400m_schedule_work(i2400m, __i2400m_dev_reset_handle, + GFP_ATOMIC); +} +EXPORT_SYMBOL_GPL(i2400m_dev_reset_handle); + + +/** + * i2400m_setup - bus-generic setup function for the i2400m device + * + * @i2400m: device descriptor (bus-specific parts have been initialized) + * + * Returns: 0 if ok, < 0 errno code on error. + * + * Initializes the bus-generic parts of the i2400m driver; the + * bus-specific parts have been initialized, function pointers filled + * out by the bus-specific probe function. + * + * As well, this registers the WiMAX and net device nodes. Once this + * function returns, the device is operative and has to be ready to + * receive and send network traffic and WiMAX control operations. + */ +int i2400m_setup(struct i2400m *i2400m, enum i2400m_bri bm_flags) +{ + int result = -ENODEV; + struct device *dev = i2400m_dev(i2400m); + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + + snprintf(wimax_dev->name, sizeof(wimax_dev->name), + "i2400m-%s:%s", dev->bus->name, dev->bus_id); + + i2400m->bm_cmd_buf = kzalloc(I2400M_BM_CMD_BUF_SIZE, GFP_KERNEL); + if (i2400m->bm_cmd_buf == NULL) { + dev_err(dev, "cannot allocate USB command buffer\n"); + goto error_bm_cmd_kzalloc; + } + i2400m->bm_ack_buf = kzalloc(I2400M_BM_ACK_BUF_SIZE, GFP_KERNEL); + if (i2400m->bm_ack_buf == NULL) { + dev_err(dev, "cannot allocate USB ack buffer\n"); + goto error_bm_ack_buf_kzalloc; + } + result = i2400m_bootrom_init(i2400m, bm_flags); + if (result < 0) { + dev_err(dev, "read mac addr: bootrom init " + "failed: %d\n", result); + goto error_bootrom_init; + } + result = i2400m_read_mac_addr(i2400m); + if (result < 0) + goto error_read_mac_addr; + + result = register_netdev(net_dev); /* Okey dokey, bring it up */ + if (result < 0) { + dev_err(dev, "cannot register i2400m network device: %d\n", + result); + goto error_register_netdev; + } + netif_carrier_off(net_dev); + + result = i2400m_dev_start(i2400m, bm_flags); + if (result < 0) + goto error_dev_start; + + i2400m->wimax_dev.op_msg_from_user = i2400m_op_msg_from_user; + i2400m->wimax_dev.op_rfkill_sw_toggle = i2400m_op_rfkill_sw_toggle; + i2400m->wimax_dev.op_reset = i2400m_op_reset; + result = wimax_dev_add(&i2400m->wimax_dev, net_dev); + if (result < 0) + goto error_wimax_dev_add; + /* User space needs to do some init stuff */ + wimax_state_change(wimax_dev, WIMAX_ST_UNINITIALIZED); + + /* Now setup all that requires a registered net and wimax device. */ + result = i2400m_debugfs_add(i2400m); + if (result < 0) { + dev_err(dev, "cannot setup i2400m's debugfs: %d\n", result); + goto error_debugfs_setup; + } + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; + +error_debugfs_setup: + wimax_dev_rm(&i2400m->wimax_dev); +error_wimax_dev_add: + i2400m_dev_stop(i2400m); +error_dev_start: + unregister_netdev(net_dev); +error_register_netdev: +error_read_mac_addr: +error_bootrom_init: + kfree(i2400m->bm_ack_buf); +error_bm_ack_buf_kzalloc: + kfree(i2400m->bm_cmd_buf); +error_bm_cmd_kzalloc: + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; +} +EXPORT_SYMBOL_GPL(i2400m_setup); + + +/** + * i2400m_release - release the bus-generic driver resources + * + * Sends a disconnect message and undoes any setup done by i2400m_setup() + */ +void i2400m_release(struct i2400m *i2400m) +{ + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + netif_stop_queue(i2400m->wimax_dev.net_dev); + + i2400m_debugfs_rm(i2400m); + wimax_dev_rm(&i2400m->wimax_dev); + i2400m_dev_stop(i2400m); + unregister_netdev(i2400m->wimax_dev.net_dev); + kfree(i2400m->bm_ack_buf); + kfree(i2400m->bm_cmd_buf); + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); +} +EXPORT_SYMBOL_GPL(i2400m_release); + + +static +int __init i2400m_driver_init(void) +{ + return 0; +} +module_init(i2400m_driver_init); + +static +void __exit i2400m_driver_exit(void) +{ + /* for scheds i2400m_dev_reset_handle() */ + flush_scheduled_work(); + return; +} +module_exit(i2400m_driver_exit); + +MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>"); +MODULE_DESCRIPTION("Intel 2400M WiMAX networking bus-generic driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/wimax/i2400m/fw.c b/drivers/net/wimax/i2400m/fw.c new file mode 100644 index 000000000000..1d8271f34c38 --- /dev/null +++ b/drivers/net/wimax/i2400m/fw.c @@ -0,0 +1,1095 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Firmware uploader + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Initial implementation + * + * + * THE PROCEDURE + * + * (this is decribed for USB, but for SDIO is similar) + * + * The 2400m works in two modes: boot-mode or normal mode. In boot + * mode we can execute only a handful of commands targeted at + * uploading the firmware and launching it. + * + * The 2400m enters boot mode when it is first connected to the + * system, when it crashes and when you ask it to reboot. There are + * two submodes of the boot mode: signed and non-signed. Signed takes + * firmwares signed with a certain private key, non-signed takes any + * firmware. Normal hardware takes only signed firmware. + * + * Upon entrance to boot mode, the device sends a few zero length + * packets (ZLPs) on the notification endpoint, then a reboot barker + * (4 le32 words with value I2400M_{S,N}BOOT_BARKER). We ack it by + * sending the same barker on the bulk out endpoint. The device acks + * with a reboot ack barker (4 le32 words with value 0xfeedbabe) and + * then the device is fully rebooted. At this point we can upload the + * firmware. + * + * This process is accomplished by the i2400m_bootrom_init() + * function. All the device interaction happens through the + * i2400m_bm_cmd() [boot mode command]. Special return values will + * indicate if the device resets. + * + * After this, we read the MAC address and then (if needed) + * reinitialize the device. We need to read it ahead of time because + * in the future, we might not upload the firmware until userspace + * 'ifconfig up's the device. + * + * We can then upload the firmware file. The file is composed of a BCF + * header (basic data, keys and signatures) and a list of write + * commands and payloads. We first upload the header + * [i2400m_dnload_init()] and then pass the commands and payloads + * verbatim to the i2400m_bm_cmd() function + * [i2400m_dnload_bcf()]. Then we tell the device to jump to the new + * firmware [i2400m_dnload_finalize()]. + * + * Once firmware is uploaded, we are good to go :) + * + * When we don't know in which mode we are, we first try by sending a + * warm reset request that will take us to boot-mode. If we time out + * waiting for a reboot barker, that means maybe we are already in + * boot mode, so we send a reboot barker. + * + * COMMAND EXECUTION + * + * This code (and process) is single threaded; for executing commands, + * we post a URB to the notification endpoint, post the command, wait + * for data on the notification buffer. We don't need to worry about + * others as we know we are the only ones in there. + * + * BACKEND IMPLEMENTATION + * + * This code is bus-generic; the bus-specific driver provides back end + * implementations to send a boot mode command to the device and to + * read an acknolwedgement from it (or an asynchronous notification) + * from it. + * + * ROADMAP + * + * i2400m_dev_bootstrap Called by __i2400m_dev_start() + * request_firmware + * i2400m_fw_check + * i2400m_fw_dnload + * release_firmware + * + * i2400m_fw_dnload + * i2400m_bootrom_init + * i2400m_bm_cmd + * i2400m->bus_reset + * i2400m_dnload_init + * i2400m_dnload_init_signed + * i2400m_dnload_init_nonsigned + * i2400m_download_chunk + * i2400m_bm_cmd + * i2400m_dnload_bcf + * i2400m_bm_cmd + * i2400m_dnload_finalize + * i2400m_bm_cmd + * + * i2400m_bm_cmd + * i2400m->bus_bm_cmd_send() + * i2400m->bus_bm_wait_for_ack + * __i2400m_bm_ack_verify + * + * i2400m_bm_cmd_prepare Used by bus-drivers to prep + * commands before sending + */ +#include <linux/firmware.h> +#include <linux/sched.h> +#include <linux/usb.h> +#include "i2400m.h" + + +#define D_SUBMODULE fw +#include "debug-levels.h" + + +static const __le32 i2400m_ACK_BARKER[4] = { + __constant_cpu_to_le32(I2400M_ACK_BARKER), + __constant_cpu_to_le32(I2400M_ACK_BARKER), + __constant_cpu_to_le32(I2400M_ACK_BARKER), + __constant_cpu_to_le32(I2400M_ACK_BARKER) +}; + + +/** + * Prepare a boot-mode command for delivery + * + * @cmd: pointer to bootrom header to prepare + * + * Computes checksum if so needed. After calling this function, DO NOT + * modify the command or header as the checksum won't work anymore. + * + * We do it from here because some times we cannot do it in the + * original context the command was sent (it is a const), so when we + * copy it to our staging buffer, we add the checksum there. + */ +void i2400m_bm_cmd_prepare(struct i2400m_bootrom_header *cmd) +{ + if (i2400m_brh_get_use_checksum(cmd)) { + int i; + u32 checksum = 0; + const u32 *checksum_ptr = (void *) cmd->payload; + for (i = 0; i < cmd->data_size / 4; i++) + checksum += cpu_to_le32(*checksum_ptr++); + checksum += cmd->command + cmd->target_addr + cmd->data_size; + cmd->block_checksum = cpu_to_le32(checksum); + } +} +EXPORT_SYMBOL_GPL(i2400m_bm_cmd_prepare); + + +/* + * Verify the ack data received + * + * Given a reply to a boot mode command, chew it and verify everything + * is ok. + * + * @opcode: opcode which generated this ack. For error messages. + * @ack: pointer to ack data we received + * @ack_size: size of that data buffer + * @flags: I2400M_BM_CMD_* flags we called the command with. + * + * Way too long function -- maybe it should be further split + */ +static +ssize_t __i2400m_bm_ack_verify(struct i2400m *i2400m, int opcode, + struct i2400m_bootrom_header *ack, + size_t ack_size, int flags) +{ + ssize_t result = -ENOMEM; + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(8, dev, "(i2400m %p opcode %d ack %p size %zu)\n", + i2400m, opcode, ack, ack_size); + if (ack_size < sizeof(*ack)) { + result = -EIO; + dev_err(dev, "boot-mode cmd %d: HW BUG? notification didn't " + "return enough data (%zu bytes vs %zu expected)\n", + opcode, ack_size, sizeof(*ack)); + goto error_ack_short; + } + if (ack_size == sizeof(i2400m_NBOOT_BARKER) + && memcmp(ack, i2400m_NBOOT_BARKER, sizeof(*ack)) == 0) { + result = -ERESTARTSYS; + i2400m->sboot = 0; + d_printf(6, dev, "boot-mode cmd %d: " + "HW non-signed boot barker\n", opcode); + goto error_reboot; + } + if (ack_size == sizeof(i2400m_SBOOT_BARKER) + && memcmp(ack, i2400m_SBOOT_BARKER, sizeof(*ack)) == 0) { + result = -ERESTARTSYS; + i2400m->sboot = 1; + d_printf(6, dev, "boot-mode cmd %d: HW signed reboot barker\n", + opcode); + goto error_reboot; + } + if (ack_size == sizeof(i2400m_ACK_BARKER) + && memcmp(ack, i2400m_ACK_BARKER, sizeof(*ack)) == 0) { + result = -EISCONN; + d_printf(3, dev, "boot-mode cmd %d: HW reboot ack barker\n", + opcode); + goto error_reboot_ack; + } + result = 0; + if (flags & I2400M_BM_CMD_RAW) + goto out_raw; + ack->data_size = le32_to_cpu(ack->data_size); + ack->target_addr = le32_to_cpu(ack->target_addr); + ack->block_checksum = le32_to_cpu(ack->block_checksum); + d_printf(5, dev, "boot-mode cmd %d: notification for opcode %u " + "response %u csum %u rr %u da %u\n", + opcode, i2400m_brh_get_opcode(ack), + i2400m_brh_get_response(ack), + i2400m_brh_get_use_checksum(ack), + i2400m_brh_get_response_required(ack), + i2400m_brh_get_direct_access(ack)); + result = -EIO; + if (i2400m_brh_get_signature(ack) != 0xcbbc) { + dev_err(dev, "boot-mode cmd %d: HW BUG? wrong signature " + "0x%04x\n", opcode, i2400m_brh_get_signature(ack)); + goto error_ack_signature; + } + if (opcode != -1 && opcode != i2400m_brh_get_opcode(ack)) { + dev_err(dev, "boot-mode cmd %d: HW BUG? " + "received response for opcode %u, expected %u\n", + opcode, i2400m_brh_get_opcode(ack), opcode); + goto error_ack_opcode; + } + if (i2400m_brh_get_response(ack) != 0) { /* failed? */ + dev_err(dev, "boot-mode cmd %d: error; hw response %u\n", + opcode, i2400m_brh_get_response(ack)); + goto error_ack_failed; + } + if (ack_size < ack->data_size + sizeof(*ack)) { + dev_err(dev, "boot-mode cmd %d: SW BUG " + "driver provided only %zu bytes for %zu bytes " + "of data\n", opcode, ack_size, + (size_t) le32_to_cpu(ack->data_size) + sizeof(*ack)); + goto error_ack_short_buffer; + } + result = ack_size; + /* Don't you love this stack of empty targets? Well, I don't + * either, but it helps track exactly who comes in here and + * why :) */ +error_ack_short_buffer: +error_ack_failed: +error_ack_opcode: +error_ack_signature: +out_raw: +error_reboot_ack: +error_reboot: +error_ack_short: + d_fnend(8, dev, "(i2400m %p opcode %d ack %p size %zu) = %d\n", + i2400m, opcode, ack, ack_size, (int) result); + return result; +} + + +/** + * i2400m_bm_cmd - Execute a boot mode command + * + * @cmd: buffer containing the command data (pointing at the header). + * This data can be ANYWHERE (for USB, we will copy it to an + * specific buffer). Make sure everything is in proper little + * endian. + * + * A raw buffer can be also sent, just cast it and set flags to + * I2400M_BM_CMD_RAW. + * + * This function will generate a checksum for you if the + * checksum bit in the command is set (unless I2400M_BM_CMD_RAW + * is set). + * + * You can use the i2400m->bm_cmd_buf to stage your commands and + * send them. + * + * If NULL, no command is sent (we just wait for an ack). + * + * @cmd_size: size of the command. Will be auto padded to the + * bus-specific drivers padding requirements. + * + * @ack: buffer where to place the acknowledgement. If it is a regular + * command response, all fields will be returned with the right, + * native endianess. + * + * You *cannot* use i2400m->bm_ack_buf for this buffer. + * + * @ack_size: size of @ack, 16 aligned; you need to provide at least + * sizeof(*ack) bytes and then enough to contain the return data + * from the command + * + * @flags: see I2400M_BM_CMD_* above. + * + * @returns: bytes received by the notification; if < 0, an errno code + * denoting an error or: + * + * -ERESTARTSYS The device has rebooted + * + * Executes a boot-mode command and waits for a response, doing basic + * validation on it; if a zero length response is received, it retries + * waiting for a response until a non-zero one is received (timing out + * after %I2400M_BOOT_RETRIES retries). + */ +static +ssize_t i2400m_bm_cmd(struct i2400m *i2400m, + const struct i2400m_bootrom_header *cmd, size_t cmd_size, + struct i2400m_bootrom_header *ack, size_t ack_size, + int flags) +{ + ssize_t result = -ENOMEM, rx_bytes; + struct device *dev = i2400m_dev(i2400m); + int opcode = cmd == NULL ? -1 : i2400m_brh_get_opcode(cmd); + + d_fnstart(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu)\n", + i2400m, cmd, cmd_size, ack, ack_size); + BUG_ON(ack_size < sizeof(*ack)); + BUG_ON(i2400m->boot_mode == 0); + + if (cmd != NULL) { /* send the command */ + memcpy(i2400m->bm_cmd_buf, cmd, cmd_size); + result = i2400m->bus_bm_cmd_send(i2400m, cmd, cmd_size, flags); + if (result < 0) + goto error_cmd_send; + if ((flags & I2400M_BM_CMD_RAW) == 0) + d_printf(5, dev, + "boot-mode cmd %d csum %u rr %u da %u: " + "addr 0x%04x size %u block csum 0x%04x\n", + opcode, i2400m_brh_get_use_checksum(cmd), + i2400m_brh_get_response_required(cmd), + i2400m_brh_get_direct_access(cmd), + cmd->target_addr, cmd->data_size, + cmd->block_checksum); + } + result = i2400m->bus_bm_wait_for_ack(i2400m, ack, ack_size); + if (result < 0) { + dev_err(dev, "boot-mode cmd %d: error waiting for an ack: %d\n", + opcode, (int) result); /* bah, %zd doesn't work */ + goto error_wait_for_ack; + } + rx_bytes = result; + /* verify the ack and read more if neccessary [result is the + * final amount of bytes we get in the ack] */ + result = __i2400m_bm_ack_verify(i2400m, opcode, ack, ack_size, flags); + if (result < 0) + goto error_bad_ack; + /* Don't you love this stack of empty targets? Well, I don't + * either, but it helps track exactly who comes in here and + * why :) */ + result = rx_bytes; +error_bad_ack: +error_wait_for_ack: +error_cmd_send: + d_fnend(6, dev, "(i2400m %p cmd %p size %zu ack %p size %zu) = %d\n", + i2400m, cmd, cmd_size, ack, ack_size, (int) result); + return result; +} + + +/** + * i2400m_download_chunk - write a single chunk of data to the device's memory + * + * @i2400m: device descriptor + * @buf: the buffer to write + * @buf_len: length of the buffer to write + * @addr: address in the device memory space + * @direct: bootrom write mode + * @do_csum: should a checksum validation be performed + */ +static int i2400m_download_chunk(struct i2400m *i2400m, const void *chunk, + size_t __chunk_len, unsigned long addr, + unsigned int direct, unsigned int do_csum) +{ + int ret; + size_t chunk_len = ALIGN(__chunk_len, I2400M_PL_PAD); + struct device *dev = i2400m_dev(i2400m); + struct { + struct i2400m_bootrom_header cmd; + u8 cmd_payload[chunk_len]; + } __attribute__((packed)) *buf; + struct i2400m_bootrom_header ack; + + d_fnstart(5, dev, "(i2400m %p chunk %p __chunk_len %zu addr 0x%08lx " + "direct %u do_csum %u)\n", i2400m, chunk, __chunk_len, + addr, direct, do_csum); + buf = i2400m->bm_cmd_buf; + memcpy(buf->cmd_payload, chunk, __chunk_len); + memset(buf->cmd_payload + __chunk_len, 0xad, chunk_len - __chunk_len); + + buf->cmd.command = i2400m_brh_command(I2400M_BRH_WRITE, + __chunk_len & 0x3 ? 0 : do_csum, + __chunk_len & 0xf ? 0 : direct); + buf->cmd.target_addr = cpu_to_le32(addr); + buf->cmd.data_size = cpu_to_le32(__chunk_len); + ret = i2400m_bm_cmd(i2400m, &buf->cmd, sizeof(buf->cmd) + chunk_len, + &ack, sizeof(ack), 0); + if (ret >= 0) + ret = 0; + d_fnend(5, dev, "(i2400m %p chunk %p __chunk_len %zu addr 0x%08lx " + "direct %u do_csum %u) = %d\n", i2400m, chunk, __chunk_len, + addr, direct, do_csum, ret); + return ret; +} + + +/* + * Download a BCF file's sections to the device + * + * @i2400m: device descriptor + * @bcf: pointer to firmware data (followed by the payloads). Assumed + * verified and consistent. + * @bcf_len: length (in bytes) of the @bcf buffer. + * + * Returns: < 0 errno code on error or the offset to the jump instruction. + * + * Given a BCF file, downloads each section (a command and a payload) + * to the device's address space. Actually, it just executes each + * command i the BCF file. + * + * The section size has to be aligned to 4 bytes AND the padding has + * to be taken from the firmware file, as the signature takes it into + * account. + */ +static +ssize_t i2400m_dnload_bcf(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf, size_t bcf_len) +{ + ssize_t ret; + struct device *dev = i2400m_dev(i2400m); + size_t offset, /* iterator offset */ + data_size, /* Size of the data payload */ + section_size, /* Size of the whole section (cmd + payload) */ + section = 1; + const struct i2400m_bootrom_header *bh; + struct i2400m_bootrom_header ack; + + d_fnstart(3, dev, "(i2400m %p bcf %p bcf_len %zu)\n", + i2400m, bcf, bcf_len); + /* Iterate over the command blocks in the BCF file that start + * after the header */ + offset = le32_to_cpu(bcf->header_len) * sizeof(u32); + while (1) { /* start sending the file */ + bh = (void *) bcf + offset; + data_size = le32_to_cpu(bh->data_size); + section_size = ALIGN(sizeof(*bh) + data_size, 4); + d_printf(7, dev, + "downloading section #%zu (@%zu %zu B) to 0x%08x\n", + section, offset, sizeof(*bh) + data_size, + le32_to_cpu(bh->target_addr)); + if (i2400m_brh_get_opcode(bh) == I2400M_BRH_SIGNED_JUMP) { + /* Secure boot needs to stop here */ + d_printf(5, dev, "signed jump found @%zu\n", offset); + break; + } + if (offset + section_size == bcf_len) + /* Non-secure boot stops here */ + break; + if (offset + section_size > bcf_len) { + dev_err(dev, "fw %s: bad section #%zu, " + "end (@%zu) beyond EOF (@%zu)\n", + i2400m->bus_fw_name, section, + offset + section_size, bcf_len); + ret = -EINVAL; + goto error_section_beyond_eof; + } + __i2400m_msleep(20); + ret = i2400m_bm_cmd(i2400m, bh, section_size, + &ack, sizeof(ack), I2400M_BM_CMD_RAW); + if (ret < 0) { + dev_err(dev, "fw %s: section #%zu (@%zu %zu B) " + "failed %d\n", i2400m->bus_fw_name, section, + offset, sizeof(*bh) + data_size, (int) ret); + goto error_send; + } + offset += section_size; + section++; + } + ret = offset; +error_section_beyond_eof: +error_send: + d_fnend(3, dev, "(i2400m %p bcf %p bcf_len %zu) = %d\n", + i2400m, bcf, bcf_len, (int) ret); + return ret; +} + + +/* + * Do the final steps of uploading firmware + * + * Depending on the boot mode (signed vs non-signed), different + * actions need to be taken. + */ +static +int i2400m_dnload_finalize(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf, size_t offset) +{ + int ret = 0; + struct device *dev = i2400m_dev(i2400m); + struct i2400m_bootrom_header *cmd, ack; + struct { + struct i2400m_bootrom_header cmd; + u8 cmd_pl[0]; + } __attribute__((packed)) *cmd_buf; + size_t signature_block_offset, signature_block_size; + + d_fnstart(3, dev, "offset %zu\n", offset); + cmd = (void *) bcf + offset; + if (i2400m->sboot == 0) { + struct i2400m_bootrom_header jump_ack; + d_printf(3, dev, "unsecure boot, jumping to 0x%08x\n", + le32_to_cpu(cmd->target_addr)); + i2400m_brh_set_opcode(cmd, I2400M_BRH_JUMP); + cmd->data_size = 0; + ret = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), + &jump_ack, sizeof(jump_ack), 0); + } else { + d_printf(3, dev, "secure boot, jumping to 0x%08x\n", + le32_to_cpu(cmd->target_addr)); + cmd_buf = i2400m->bm_cmd_buf; + memcpy(&cmd_buf->cmd, cmd, sizeof(*cmd)); + signature_block_offset = + sizeof(*bcf) + + le32_to_cpu(bcf->key_size) * sizeof(u32) + + le32_to_cpu(bcf->exponent_size) * sizeof(u32); + signature_block_size = + le32_to_cpu(bcf->modulus_size) * sizeof(u32); + memcpy(cmd_buf->cmd_pl, (void *) bcf + signature_block_offset, + signature_block_size); + ret = i2400m_bm_cmd(i2400m, &cmd_buf->cmd, + sizeof(cmd_buf->cmd) + signature_block_size, + &ack, sizeof(ack), I2400M_BM_CMD_RAW); + } + d_fnend(3, dev, "returning %d\n", ret); + return ret; +} + + +/** + * i2400m_bootrom_init - Reboots a powered device into boot mode + * + * @i2400m: device descriptor + * @flags: + * I2400M_BRI_SOFT: a reboot notification has been seen + * already, so don't wait for it. + * + * I2400M_BRI_NO_REBOOT: Don't send a reboot command, but wait + * for a reboot barker notification. This is a one shot; if + * the state machine needs to send a reboot command it will. + * + * Returns: + * + * < 0 errno code on error, 0 if ok. + * + * i2400m->sboot set to 0 for unsecure boot process, 1 for secure + * boot process. + * + * Description: + * + * Tries hard enough to put the device in boot-mode. There are two + * main phases to this: + * + * a. (1) send a reboot command and (2) get a reboot barker + * b. (1) ack the reboot sending a reboot barker and (2) getting an + * ack barker in return + * + * We want to skip (a) in some cases [soft]. The state machine is + * horrible, but it is basically: on each phase, send what has to be + * sent (if any), wait for the answer and act on the answer. We might + * have to backtrack and retry, so we keep a max tries counter for + * that. + * + * If we get a timeout after sending a warm reset, we do it again. + */ +int i2400m_bootrom_init(struct i2400m *i2400m, enum i2400m_bri flags) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct i2400m_bootrom_header *cmd; + struct i2400m_bootrom_header ack; + int count = I2400M_BOOT_RETRIES; + int ack_timeout_cnt = 1; + + BUILD_BUG_ON(sizeof(*cmd) != sizeof(i2400m_NBOOT_BARKER)); + BUILD_BUG_ON(sizeof(ack) != sizeof(i2400m_ACK_BARKER)); + + d_fnstart(4, dev, "(i2400m %p flags 0x%08x)\n", i2400m, flags); + result = -ENOMEM; + cmd = i2400m->bm_cmd_buf; + if (flags & I2400M_BRI_SOFT) + goto do_reboot_ack; +do_reboot: + if (--count < 0) + goto error_timeout; + d_printf(4, dev, "device reboot: reboot command [%d # left]\n", + count); + if ((flags & I2400M_BRI_NO_REBOOT) == 0) + i2400m->bus_reset(i2400m, I2400M_RT_WARM); + result = i2400m_bm_cmd(i2400m, NULL, 0, &ack, sizeof(ack), + I2400M_BM_CMD_RAW); + flags &= ~I2400M_BRI_NO_REBOOT; + switch (result) { + case -ERESTARTSYS: + d_printf(4, dev, "device reboot: got reboot barker\n"); + break; + case -EISCONN: /* we don't know how it got here...but we follow it */ + d_printf(4, dev, "device reboot: got ack barker - whatever\n"); + goto do_reboot; + case -ETIMEDOUT: /* device has timed out, we might be in boot + * mode already and expecting an ack, let's try + * that */ + dev_info(dev, "warm reset timed out, trying an ack\n"); + goto do_reboot_ack; + case -EPROTO: + case -ESHUTDOWN: /* dev is gone */ + case -EINTR: /* user cancelled */ + goto error_dev_gone; + default: + dev_err(dev, "device reboot: error %d while waiting " + "for reboot barker - rebooting\n", result); + goto do_reboot; + } + /* At this point we ack back with 4 REBOOT barkers and expect + * 4 ACK barkers. This is ugly, as we send a raw command -- + * hence the cast. _bm_cmd() will catch the reboot ack + * notification and report it as -EISCONN. */ +do_reboot_ack: + d_printf(4, dev, "device reboot ack: sending ack [%d # left]\n", count); + if (i2400m->sboot == 0) + memcpy(cmd, i2400m_NBOOT_BARKER, + sizeof(i2400m_NBOOT_BARKER)); + else + memcpy(cmd, i2400m_SBOOT_BARKER, + sizeof(i2400m_SBOOT_BARKER)); + result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), + &ack, sizeof(ack), I2400M_BM_CMD_RAW); + switch (result) { + case -ERESTARTSYS: + d_printf(4, dev, "reboot ack: got reboot barker - retrying\n"); + if (--count < 0) + goto error_timeout; + goto do_reboot_ack; + case -EISCONN: + d_printf(4, dev, "reboot ack: got ack barker - good\n"); + break; + case -ETIMEDOUT: /* no response, maybe it is the other type? */ + if (ack_timeout_cnt-- >= 0) { + d_printf(4, dev, "reboot ack timedout: " + "trying the other type?\n"); + i2400m->sboot = !i2400m->sboot; + goto do_reboot_ack; + } else { + dev_err(dev, "reboot ack timedout too long: " + "trying reboot\n"); + goto do_reboot; + } + break; + case -EPROTO: + case -ESHUTDOWN: /* dev is gone */ + goto error_dev_gone; + default: + dev_err(dev, "device reboot ack: error %d while waiting for " + "reboot ack barker - rebooting\n", result); + goto do_reboot; + } + d_printf(2, dev, "device reboot ack: got ack barker - boot done\n"); + result = 0; +exit_timeout: +error_dev_gone: + d_fnend(4, dev, "(i2400m %p flags 0x%08x) = %d\n", + i2400m, flags, result); + return result; + +error_timeout: + dev_err(dev, "Timed out waiting for reboot ack, resetting\n"); + i2400m->bus_reset(i2400m, I2400M_RT_BUS); + result = -ETIMEDOUT; + goto exit_timeout; +} + + +/* + * Read the MAC addr + * + * The position this function reads is fixed in device memory and + * always available, even without firmware. + * + * Note we specify we want to read only six bytes, but provide space + * for 16, as we always get it rounded up. + */ +int i2400m_read_mac_addr(struct i2400m *i2400m) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + struct i2400m_bootrom_header *cmd; + struct { + struct i2400m_bootrom_header ack; + u8 ack_pl[16]; + } __attribute__((packed)) ack_buf; + + d_fnstart(5, dev, "(i2400m %p)\n", i2400m); + cmd = i2400m->bm_cmd_buf; + cmd->command = i2400m_brh_command(I2400M_BRH_READ, 0, 1); + cmd->target_addr = cpu_to_le32(0x00203fe8); + cmd->data_size = cpu_to_le32(6); + result = i2400m_bm_cmd(i2400m, cmd, sizeof(*cmd), + &ack_buf.ack, sizeof(ack_buf), 0); + if (result < 0) { + dev_err(dev, "BM: read mac addr failed: %d\n", result); + goto error_read_mac; + } + d_printf(2, dev, + "mac addr is %02x:%02x:%02x:%02x:%02x:%02x\n", + ack_buf.ack_pl[0], ack_buf.ack_pl[1], + ack_buf.ack_pl[2], ack_buf.ack_pl[3], + ack_buf.ack_pl[4], ack_buf.ack_pl[5]); + if (i2400m->bus_bm_mac_addr_impaired == 1) { + ack_buf.ack_pl[0] = 0x00; + ack_buf.ack_pl[1] = 0x16; + ack_buf.ack_pl[2] = 0xd3; + get_random_bytes(&ack_buf.ack_pl[3], 3); + dev_err(dev, "BM is MAC addr impaired, faking MAC addr to " + "mac addr is %02x:%02x:%02x:%02x:%02x:%02x\n", + ack_buf.ack_pl[0], ack_buf.ack_pl[1], + ack_buf.ack_pl[2], ack_buf.ack_pl[3], + ack_buf.ack_pl[4], ack_buf.ack_pl[5]); + result = 0; + } + net_dev->addr_len = ETH_ALEN; + memcpy(net_dev->perm_addr, ack_buf.ack_pl, ETH_ALEN); + memcpy(net_dev->dev_addr, ack_buf.ack_pl, ETH_ALEN); +error_read_mac: + d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; +} + + +/* + * Initialize a non signed boot + * + * This implies sending some magic values to the device's memory. Note + * we convert the values to little endian in the same array + * declaration. + */ +static +int i2400m_dnload_init_nonsigned(struct i2400m *i2400m) +{ +#define POKE(a, d) { \ + .address = __constant_cpu_to_le32(a), \ + .data = __constant_cpu_to_le32(d) \ +} + static const struct { + __le32 address; + __le32 data; + } i2400m_pokes[] = { + POKE(0x081A58, 0xA7810230), + POKE(0x080040, 0x00000000), + POKE(0x080048, 0x00000082), + POKE(0x08004C, 0x0000081F), + POKE(0x080054, 0x00000085), + POKE(0x080058, 0x00000180), + POKE(0x08005C, 0x00000018), + POKE(0x080060, 0x00000010), + POKE(0x080574, 0x00000001), + POKE(0x080550, 0x00000005), + POKE(0xAE0000, 0x00000000), + }; +#undef POKE + unsigned i; + int ret; + struct device *dev = i2400m_dev(i2400m); + + dev_warn(dev, "WARNING!!! non-signed boot UNTESTED PATH!\n"); + + d_fnstart(5, dev, "(i2400m %p)\n", i2400m); + for (i = 0; i < ARRAY_SIZE(i2400m_pokes); i++) { + ret = i2400m_download_chunk(i2400m, &i2400m_pokes[i].data, + sizeof(i2400m_pokes[i].data), + i2400m_pokes[i].address, 1, 1); + if (ret < 0) + break; + } + d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); + return ret; +} + + +/* + * Initialize the signed boot process + * + * @i2400m: device descriptor + * + * @bcf_hdr: pointer to the firmware header; assumes it is fully in + * memory (it has gone through basic validation). + * + * Returns: 0 if ok, < 0 errno code on error, -ERESTARTSYS if the hw + * rebooted. + * + * This writes the firmware BCF header to the device using the + * HASH_PAYLOAD_ONLY command. + */ +static +int i2400m_dnload_init_signed(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf_hdr) +{ + int ret; + struct device *dev = i2400m_dev(i2400m); + struct { + struct i2400m_bootrom_header cmd; + struct i2400m_bcf_hdr cmd_pl; + } __attribute__((packed)) *cmd_buf; + struct i2400m_bootrom_header ack; + + d_fnstart(5, dev, "(i2400m %p bcf_hdr %p)\n", i2400m, bcf_hdr); + cmd_buf = i2400m->bm_cmd_buf; + cmd_buf->cmd.command = + i2400m_brh_command(I2400M_BRH_HASH_PAYLOAD_ONLY, 0, 0); + cmd_buf->cmd.target_addr = 0; + cmd_buf->cmd.data_size = cpu_to_le32(sizeof(cmd_buf->cmd_pl)); + memcpy(&cmd_buf->cmd_pl, bcf_hdr, sizeof(*bcf_hdr)); + ret = i2400m_bm_cmd(i2400m, &cmd_buf->cmd, sizeof(*cmd_buf), + &ack, sizeof(ack), 0); + if (ret >= 0) + ret = 0; + d_fnend(5, dev, "(i2400m %p bcf_hdr %p) = %d\n", i2400m, bcf_hdr, ret); + return ret; +} + + +/* + * Initialize the firmware download at the device size + * + * Multiplex to the one that matters based on the device's mode + * (signed or non-signed). + */ +static +int i2400m_dnload_init(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + u32 module_id = le32_to_cpu(bcf->module_id); + + if (i2400m->sboot == 0 + && (module_id & I2400M_BCF_MOD_ID_POKES) == 0) { + /* non-signed boot process without pokes */ + result = i2400m_dnload_init_nonsigned(i2400m); + if (result == -ERESTARTSYS) + return result; + if (result < 0) + dev_err(dev, "fw %s: non-signed download " + "initialization failed: %d\n", + i2400m->bus_fw_name, result); + } else if (i2400m->sboot == 0 + && (module_id & I2400M_BCF_MOD_ID_POKES)) { + /* non-signed boot process with pokes, nothing to do */ + result = 0; + } else { /* signed boot process */ + result = i2400m_dnload_init_signed(i2400m, bcf); + if (result == -ERESTARTSYS) + return result; + if (result < 0) + dev_err(dev, "fw %s: signed boot download " + "initialization failed: %d\n", + i2400m->bus_fw_name, result); + } + return result; +} + + +/* + * Run quick consistency tests on the firmware file + * + * Check for the firmware being made for the i2400m device, + * etc...These checks are mostly informative, as the device will make + * them too; but the driver's response is more informative on what + * went wrong. + */ +static +int i2400m_fw_check(struct i2400m *i2400m, + const struct i2400m_bcf_hdr *bcf, + size_t bcf_size) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + unsigned module_type, header_len, major_version, minor_version, + module_id, module_vendor, date, size; + + /* Check hard errors */ + result = -EINVAL; + if (bcf_size < sizeof(*bcf)) { /* big enough header? */ + dev_err(dev, "firmware %s too short: " + "%zu B vs %zu (at least) expected\n", + i2400m->bus_fw_name, bcf_size, sizeof(*bcf)); + goto error; + } + + module_type = bcf->module_type; + header_len = sizeof(u32) * le32_to_cpu(bcf->header_len); + major_version = le32_to_cpu(bcf->header_version) & 0xffff0000 >> 16; + minor_version = le32_to_cpu(bcf->header_version) & 0x0000ffff; + module_id = le32_to_cpu(bcf->module_id); + module_vendor = le32_to_cpu(bcf->module_vendor); + date = le32_to_cpu(bcf->date); + size = sizeof(u32) * le32_to_cpu(bcf->size); + + if (bcf_size != size) { /* annoyingly paranoid */ + dev_err(dev, "firmware %s: bad size, got " + "%zu B vs %u expected\n", + i2400m->bus_fw_name, bcf_size, size); + goto error; + } + + d_printf(2, dev, "type 0x%x id 0x%x vendor 0x%x; header v%u.%u (%zu B) " + "date %08x (%zu B)\n", + module_type, module_id, module_vendor, + major_version, minor_version, (size_t) header_len, + date, (size_t) size); + + if (module_type != 6) { /* built for the right hardware? */ + dev_err(dev, "bad fw %s: unexpected module type 0x%x; " + "aborting\n", i2400m->bus_fw_name, module_type); + goto error; + } + + /* Check soft-er errors */ + result = 0; + if (module_vendor != 0x8086) + dev_err(dev, "bad fw %s? unexpected vendor 0x%04x\n", + i2400m->bus_fw_name, module_vendor); + if (date < 0x20080300) + dev_err(dev, "bad fw %s? build date too old %08x\n", + i2400m->bus_fw_name, date); +error: + return result; +} + + +/* + * Download the firmware to the device + * + * @i2400m: device descriptor + * @bcf: pointer to loaded (and minimally verified for consistency) + * firmware + * @bcf_size: size of the @bcf buffer (header plus payloads) + * + * The process for doing this is described in this file's header. + * + * Note we only reinitialize boot-mode if the flags say so. Some hw + * iterations need it, some don't. In any case, if we loop, we always + * need to reinitialize the boot room, hence the flags modification. + */ +static +int i2400m_fw_dnload(struct i2400m *i2400m, const struct i2400m_bcf_hdr *bcf, + size_t bcf_size, enum i2400m_bri flags) +{ + int ret = 0; + struct device *dev = i2400m_dev(i2400m); + int count = I2400M_BOOT_RETRIES; + + d_fnstart(5, dev, "(i2400m %p bcf %p size %zu)\n", + i2400m, bcf, bcf_size); + i2400m->boot_mode = 1; +hw_reboot: + if (count-- == 0) { + ret = -ERESTARTSYS; + dev_err(dev, "device rebooted too many times, aborting\n"); + goto error_too_many_reboots; + } + if (flags & I2400M_BRI_MAC_REINIT) { + ret = i2400m_bootrom_init(i2400m, flags); + if (ret < 0) { + dev_err(dev, "bootrom init failed: %d\n", ret); + goto error_bootrom_init; + } + } + flags |= I2400M_BRI_MAC_REINIT; + + /* + * Initialize the download, push the bytes to the device and + * then jump to the new firmware. Note @ret is passed with the + * offset of the jump instruction to _dnload_finalize() + */ + ret = i2400m_dnload_init(i2400m, bcf); /* Init device's dnload */ + if (ret == -ERESTARTSYS) + goto error_dev_rebooted; + if (ret < 0) + goto error_dnload_init; + + ret = i2400m_dnload_bcf(i2400m, bcf, bcf_size); + if (ret == -ERESTARTSYS) + goto error_dev_rebooted; + if (ret < 0) { + dev_err(dev, "fw %s: download failed: %d\n", + i2400m->bus_fw_name, ret); + goto error_dnload_bcf; + } + + ret = i2400m_dnload_finalize(i2400m, bcf, ret); + if (ret == -ERESTARTSYS) + goto error_dev_rebooted; + if (ret < 0) { + dev_err(dev, "fw %s: " + "download finalization failed: %d\n", + i2400m->bus_fw_name, ret); + goto error_dnload_finalize; + } + + d_printf(2, dev, "fw %s successfully uploaded\n", + i2400m->bus_fw_name); + i2400m->boot_mode = 0; +error_dnload_finalize: +error_dnload_bcf: +error_dnload_init: +error_bootrom_init: +error_too_many_reboots: + d_fnend(5, dev, "(i2400m %p bcf %p size %zu) = %d\n", + i2400m, bcf, bcf_size, ret); + return ret; + +error_dev_rebooted: + dev_err(dev, "device rebooted, %d tries left\n", count); + /* we got the notification already, no need to wait for it again */ + flags |= I2400M_BRI_SOFT; + goto hw_reboot; +} + + +/** + * i2400m_dev_bootstrap - Bring the device to a known state and upload firmware + * + * @i2400m: device descriptor + * + * Returns: >= 0 if ok, < 0 errno code on error. + * + * This sets up the firmware upload environment, loads the firmware + * file from disk, verifies and then calls the firmware upload process + * per se. + * + * Can be called either from probe, or after a warm reset. Can not be + * called from within an interrupt. All the flow in this code is + * single-threade; all I/Os are synchronous. + */ +int i2400m_dev_bootstrap(struct i2400m *i2400m, enum i2400m_bri flags) +{ + int ret = 0; + struct device *dev = i2400m_dev(i2400m); + const struct firmware *fw; + const struct i2400m_bcf_hdr *bcf; /* Firmware data */ + + d_fnstart(5, dev, "(i2400m %p)\n", i2400m); + /* Load firmware files to memory. */ + ret = request_firmware(&fw, i2400m->bus_fw_name, dev); + if (ret) { + dev_err(dev, "fw %s: request failed: %d\n", + i2400m->bus_fw_name, ret); + goto error_fw_req; + } + bcf = (void *) fw->data; + + ret = i2400m_fw_check(i2400m, bcf, fw->size); + if (ret < 0) + goto error_fw_bad; + ret = i2400m_fw_dnload(i2400m, bcf, fw->size, flags); +error_fw_bad: + release_firmware(fw); +error_fw_req: + d_fnend(5, dev, "(i2400m %p) = %d\n", i2400m, ret); + return ret; +} +EXPORT_SYMBOL_GPL(i2400m_dev_bootstrap); diff --git a/drivers/net/wimax/i2400m/i2400m-sdio.h b/drivers/net/wimax/i2400m/i2400m-sdio.h new file mode 100644 index 000000000000..08c2fb739234 --- /dev/null +++ b/drivers/net/wimax/i2400m/i2400m-sdio.h @@ -0,0 +1,132 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * SDIO-specific i2400m driver definitions + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Brian Bian <brian.bian@intel.com> + * Dirk Brandewie <dirk.j.brandewie@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * - Initial implementation + * + * + * This driver implements the bus-specific part of the i2400m for + * SDIO. Check i2400m.h for a generic driver description. + * + * ARCHITECTURE + * + * This driver sits under the bus-generic i2400m driver, providing the + * connection to the device. + * + * When probed, all the function pointers are setup and then the + * bus-generic code called. The generic driver will then use the + * provided pointers for uploading firmware (i2400ms_bus_bm*() in + * sdio-fw.c) and then setting up the device (i2400ms_dev_*() in + * sdio.c). + * + * Once firmware is uploaded, TX functions (sdio-tx.c) are called when + * data is ready for transmission in the TX fifo; then the SDIO IRQ is + * fired and data is available (sdio-rx.c), it is sent to the generic + * driver for processing with i2400m_rx. + */ + +#ifndef __I2400M_SDIO_H__ +#define __I2400M_SDIO_H__ + +#include "i2400m.h" + +/* Host-Device interface for SDIO */ +enum { + I2400MS_BLK_SIZE = 256, + I2400MS_PL_SIZE_MAX = 0x3E00, + + I2400MS_DATA_ADDR = 0x0, + I2400MS_INTR_STATUS_ADDR = 0x13, + I2400MS_INTR_CLEAR_ADDR = 0x13, + I2400MS_INTR_ENABLE_ADDR = 0x14, + I2400MS_INTR_GET_SIZE_ADDR = 0x2C, + /* The number of ticks to wait for the device to signal that + * it is ready */ + I2400MS_INIT_SLEEP_INTERVAL = 10, +}; + + +/** + * struct i2400ms - descriptor for a SDIO connected i2400m + * + * @i2400m: bus-generic i2400m implementation; has to be first (see + * it's documentation in i2400m.h). + * + * @func: pointer to our SDIO function + * + * @tx_worker: workqueue struct used to TX data when the bus-generic + * code signals packets are pending for transmission to the device. + * + * @tx_workqueue: workqeueue used for data TX; we don't use the + * system's workqueue as that might cause deadlocks with code in + * the bus-generic driver. + */ +struct i2400ms { + struct i2400m i2400m; /* FIRST! See doc */ + struct sdio_func *func; + + struct work_struct tx_worker; + struct workqueue_struct *tx_workqueue; + char tx_wq_name[32]; + + struct dentry *debugfs_dentry; +}; + + +static inline +void i2400ms_init(struct i2400ms *i2400ms) +{ + i2400m_init(&i2400ms->i2400m); +} + + +extern int i2400ms_rx_setup(struct i2400ms *); +extern void i2400ms_rx_release(struct i2400ms *); +extern ssize_t __i2400ms_rx_get_size(struct i2400ms *); + +extern int i2400ms_tx_setup(struct i2400ms *); +extern void i2400ms_tx_release(struct i2400ms *); +extern void i2400ms_bus_tx_kick(struct i2400m *); + +extern ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *, + const struct i2400m_bootrom_header *, + size_t, int); +extern ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *, + struct i2400m_bootrom_header *, + size_t); +#endif /* #ifndef __I2400M_SDIO_H__ */ diff --git a/drivers/net/wimax/i2400m/i2400m-usb.h b/drivers/net/wimax/i2400m/i2400m-usb.h new file mode 100644 index 000000000000..6f76558b170f --- /dev/null +++ b/drivers/net/wimax/i2400m/i2400m-usb.h @@ -0,0 +1,264 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * USB-specific i2400m driver definitions + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * - Initial implementation + * + * + * This driver implements the bus-specific part of the i2400m for + * USB. Check i2400m.h for a generic driver description. + * + * ARCHITECTURE + * + * This driver listens to notifications sent from the notification + * endpoint (in usb-notif.c); when data is ready to read, the code in + * there schedules a read from the device (usb-rx.c) and then passes + * the data to the generic RX code (rx.c). + * + * When the generic driver needs to send data (network or control), it + * queues up in the TX FIFO (tx.c) and that will notify the driver + * through the i2400m->bus_tx_kick() callback + * (usb-tx.c:i2400mu_bus_tx_kick) which will send the items in the + * FIFO queue. + * + * This driver, as well, implements the USB-specific ops for the generic + * driver to be able to setup/teardown communication with the device + * [i2400m_bus_dev_start() and i2400m_bus_dev_stop()], reseting the + * device [i2400m_bus_reset()] and performing firmware upload + * [i2400m_bus_bm_cmd() and i2400_bus_bm_wait_for_ack()]. + */ + +#ifndef __I2400M_USB_H__ +#define __I2400M_USB_H__ + +#include "i2400m.h" +#include <linux/kthread.h> + + +/* + * Error Density Count: cheapo error density (over time) counter + * + * Originally by Reinette Chatre <reinette.chatre@intel.com> + * + * Embed an 'struct edc' somewhere. Each time there is a soft or + * retryable error, call edc_inc() and check if the error top + * watermark has been reached. + */ +enum { + EDC_MAX_ERRORS = 10, + EDC_ERROR_TIMEFRAME = HZ, +}; + +/* error density counter */ +struct edc { + unsigned long timestart; + u16 errorcount; +}; + +static inline void edc_init(struct edc *edc) +{ + edc->timestart = jiffies; +} + +/** + * edc_inc - report a soft error and check if we are over the watermark + * + * @edc: pointer to error density counter. + * @max_err: maximum number of errors we can accept over the timeframe + * @timeframe: lenght of the timeframe (in jiffies). + * + * Returns: !0 1 if maximum acceptable errors per timeframe has been + * exceeded. 0 otherwise. + * + * This is way to determine if the number of acceptable errors per time + * period has been exceeded. It is not accurate as there are cases in which + * this scheme will not work, for example if there are periodic occurences + * of errors that straddle updates to the start time. This scheme is + * sufficient for our usage. + * + * To use, embed a 'struct edc' somewhere, initialize it with + * edc_init() and when an error hits: + * + * if (do_something_fails_with_a_soft_error) { + * if (edc_inc(&my->edc, MAX_ERRORS, MAX_TIMEFRAME)) + * Ops, hard error, do something about it + * else + * Retry or ignore, depending on whatever + * } + */ +static inline int edc_inc(struct edc *edc, u16 max_err, u16 timeframe) +{ + unsigned long now; + + now = jiffies; + if (now - edc->timestart > timeframe) { + edc->errorcount = 1; + edc->timestart = now; + } else if (++edc->errorcount > max_err) { + edc->errorcount = 0; + edc->timestart = now; + return 1; + } + return 0; +} + +/* Host-Device interface for USB */ +enum { + I2400MU_MAX_NOTIFICATION_LEN = 256, + I2400MU_BLK_SIZE = 16, + I2400MU_PL_SIZE_MAX = 0x3EFF, + + /* Endpoints */ + I2400MU_EP_BULK_OUT = 0, + I2400MU_EP_NOTIFICATION, + I2400MU_EP_RESET_COLD, + I2400MU_EP_BULK_IN, +}; + + +/** + * struct i2400mu - descriptor for a USB connected i2400m + * + * @i2400m: bus-generic i2400m implementation; has to be first (see + * it's documentation in i2400m.h). + * + * @usb_dev: pointer to our USB device + * + * @usb_iface: pointer to our USB interface + * + * @urb_edc: error density counter; used to keep a density-on-time tab + * on how many soft (retryable or ignorable) errors we get. If we + * go over the threshold, we consider the bus transport is failing + * too much and reset. + * + * @notif_urb: URB for receiving notifications from the device. + * + * @tx_kthread: thread we use for data TX. We use a thread because in + * order to do deep power saving and put the device to sleep, we + * need to call usb_autopm_*() [blocking functions]. + * + * @tx_wq: waitqueue for the TX kthread to sleep when there is no data + * to be sent; when more data is available, it is woken up by + * i2400mu_bus_tx_kick(). + * + * @rx_kthread: thread we use for data RX. We use a thread because in + * order to do deep power saving and put the device to sleep, we + * need to call usb_autopm_*() [blocking functions]. + * + * @rx_wq: waitqueue for the RX kthread to sleep when there is no data + * to receive. When data is available, it is woken up by + * usb-notif.c:i2400mu_notification_grok(). + * + * @rx_pending_count: number of rx-data-ready notifications that were + * still not handled by the RX kthread. + * + * @rx_size: current RX buffer size that is being used. + * + * @rx_size_acc: accumulator of the sizes of the previous read + * transactions. + * + * @rx_size_cnt: number of read transactions accumulated in + * @rx_size_acc. + * + * @do_autopm: disable(0)/enable(>0) calling the + * usb_autopm_get/put_interface() barriers when executing + * commands. See doc in i2400mu_suspend() for more information. + * + * @rx_size_auto_shrink: if true, the rx_size is shrinked + * automatically based on the average size of the received + * transactions. This allows the receive code to allocate smaller + * chunks of memory and thus reduce pressure on the memory + * allocator by not wasting so much space. By default it is + * enabled. + * + * @debugfs_dentry: hookup for debugfs files. + * These have to be in a separate directory, a child of + * (wimax_dev->debugfs_dentry) so they can be removed when the + * module unloads, as we don't keep each dentry. + */ +struct i2400mu { + struct i2400m i2400m; /* FIRST! See doc */ + + struct usb_device *usb_dev; + struct usb_interface *usb_iface; + struct edc urb_edc; /* Error density counter */ + + struct urb *notif_urb; + struct task_struct *tx_kthread; + wait_queue_head_t tx_wq; + + struct task_struct *rx_kthread; + wait_queue_head_t rx_wq; + atomic_t rx_pending_count; + size_t rx_size, rx_size_acc, rx_size_cnt; + atomic_t do_autopm; + u8 rx_size_auto_shrink; + + struct dentry *debugfs_dentry; +}; + + +static inline +void i2400mu_init(struct i2400mu *i2400mu) +{ + i2400m_init(&i2400mu->i2400m); + edc_init(&i2400mu->urb_edc); + init_waitqueue_head(&i2400mu->tx_wq); + atomic_set(&i2400mu->rx_pending_count, 0); + init_waitqueue_head(&i2400mu->rx_wq); + i2400mu->rx_size = PAGE_SIZE - sizeof(struct skb_shared_info); + atomic_set(&i2400mu->do_autopm, 1); + i2400mu->rx_size_auto_shrink = 1; +} + +extern int i2400mu_notification_setup(struct i2400mu *); +extern void i2400mu_notification_release(struct i2400mu *); + +extern int i2400mu_rx_setup(struct i2400mu *); +extern void i2400mu_rx_release(struct i2400mu *); +extern void i2400mu_rx_kick(struct i2400mu *); + +extern int i2400mu_tx_setup(struct i2400mu *); +extern void i2400mu_tx_release(struct i2400mu *); +extern void i2400mu_bus_tx_kick(struct i2400m *); + +extern ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *, + const struct i2400m_bootrom_header *, + size_t, int); +extern ssize_t i2400mu_bus_bm_wait_for_ack(struct i2400m *, + struct i2400m_bootrom_header *, + size_t); +#endif /* #ifndef __I2400M_USB_H__ */ diff --git a/drivers/net/wimax/i2400m/i2400m.h b/drivers/net/wimax/i2400m/i2400m.h new file mode 100644 index 000000000000..067c871cc226 --- /dev/null +++ b/drivers/net/wimax/i2400m/i2400m.h @@ -0,0 +1,755 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Declarations for bus-generic internal APIs + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * - Initial implementation + * + * + * GENERAL DRIVER ARCHITECTURE + * + * The i2400m driver is split in the following two major parts: + * + * - bus specific driver + * - bus generic driver (this part) + * + * The bus specific driver sets up stuff specific to the bus the + * device is connected to (USB, SDIO, PCI, tam-tam...non-authoritative + * nor binding list) which is basically the device-model management + * (probe/disconnect, etc), moving data from device to kernel and + * back, doing the power saving details and reseting the device. + * + * For details on each bus-specific driver, see it's include file, + * i2400m-BUSNAME.h + * + * The bus-generic functionality break up is: + * + * - Firmware upload: fw.c - takes care of uploading firmware to the + * device. bus-specific driver just needs to provides a way to + * execute boot-mode commands and to reset the device. + * + * - RX handling: rx.c - receives data from the bus-specific code and + * feeds it to the network or WiMAX stack or uses it to modify + * the driver state. bus-specific driver only has to receive + * frames and pass them to this module. + * + * - TX handling: tx.c - manages the TX FIFO queue and provides means + * for the bus-specific TX code to pull data from the FIFO + * queue. bus-specific code just pulls frames from this module + * to sends them to the device. + * + * - netdev glue: netdev.c - interface with Linux networking + * stack. Pass around data frames, and configure when the + * device is up and running or shutdown (through ifconfig up / + * down). Bus-generic only. + * + * - control ops: control.c - implements various commmands for + * controlling the device. bus-generic only. + * + * - device model glue: driver.c - implements helpers for the + * device-model glue done by the bus-specific layer + * (setup/release the driver resources), turning the device on + * and off, handling the device reboots/resets and a few simple + * WiMAX stack ops. + * + * Code is also broken up in linux-glue / device-glue. + * + * Linux glue contains functions that deal mostly with gluing with the + * rest of the Linux kernel. + * + * Device-glue are functions that deal mostly with the way the device + * does things and talk the device's language. + * + * device-glue code is licensed BSD so other open source OSes can take + * it to implement their drivers. + * + * + * APIs AND HEADER FILES + * + * This bus generic code exports three APIs: + * + * - HDI (host-device interface) definitions common to all busses + * (include/linux/wimax/i2400m.h); these can be also used by user + * space code. + * - internal API for the bus-generic code + * - external API for the bus-specific drivers + * + * + * LIFE CYCLE: + * + * When the bus-specific driver probes, it allocates a network device + * with enough space for it's data structue, that must contain a + * &struct i2400m at the top. + * + * On probe, it needs to fill the i2400m members marked as [fill], as + * well as i2400m->wimax_dev.net_dev and call i2400m_setup(). The + * i2400m driver will only register with the WiMAX and network stacks; + * the only access done to the device is to read the MAC address so we + * can register a network device. This calls i2400m_dev_start() to + * load firmware, setup communication with the device and configure it + * for operation. + * + * At this point, control and data communications are possible. + * + * On disconnect/driver unload, the bus-specific disconnect function + * calls i2400m_release() to undo i2400m_setup(). i2400m_dev_stop() + * shuts the firmware down and releases resources uses to communicate + * with the device. + * + * While the device is up, it might reset. The bus-specific driver has + * to catch that situation and call i2400m_dev_reset_handle() to deal + * with it (reset the internal driver structures and go back to square + * one). + */ + +#ifndef __I2400M_H__ +#define __I2400M_H__ + +#include <linux/usb.h> +#include <linux/netdevice.h> +#include <linux/completion.h> +#include <linux/rwsem.h> +#include <asm/atomic.h> +#include <net/wimax.h> +#include <linux/wimax/i2400m.h> +#include <asm/byteorder.h> + +/* Misc constants */ +enum { + /* Firmware uploading */ + I2400M_BOOT_RETRIES = 3, + /* Size of the Boot Mode Command buffer */ + I2400M_BM_CMD_BUF_SIZE = 16 * 1024, + I2400M_BM_ACK_BUF_SIZE = 256, +}; + + +/* Firmware version we request when pulling the fw image file */ +#define I2400M_FW_VERSION "1.3" + + +/** + * i2400m_reset_type - methods to reset a device + * + * @I2400M_RT_WARM: Reset without device disconnection, device handles + * are kept valid but state is back to power on, with firmware + * re-uploaded. + * @I2400M_RT_COLD: Tell the device to disconnect itself from the bus + * and reconnect. Renders all device handles invalid. + * @I2400M_RT_BUS: Tells the bus to reset the device; last measure + * used when both types above don't work. + */ +enum i2400m_reset_type { + I2400M_RT_WARM, /* first measure */ + I2400M_RT_COLD, /* second measure */ + I2400M_RT_BUS, /* call in artillery */ +}; + +struct i2400m_reset_ctx; + +/** + * struct i2400m - descriptor for an Intel 2400m + * + * Members marked with [fill] must be filled out/initialized before + * calling i2400m_setup(). + * + * @bus_tx_block_size: [fill] SDIO imposes a 256 block size, USB 16, + * so we have a tx_blk_size variable that the bus layer sets to + * tell the engine how much of that we need. + * + * @bus_pl_size_max: [fill] Maximum payload size. + * + * @bus_dev_start: [fill] Function called by the bus-generic code + * [i2400m_dev_start()] to setup the bus-specific communications + * to the the device. See LIFE CYCLE above. + * + * NOTE: Doesn't need to upload the firmware, as that is taken + * care of by the bus-generic code. + * + * @bus_dev_stop: [fill] Function called by the bus-generic code + * [i2400m_dev_stop()] to shutdown the bus-specific communications + * to the the device. See LIFE CYCLE above. + * + * This function does not need to reset the device, just tear down + * all the host resources created to handle communication with + * the device. + * + * @bus_tx_kick: [fill] Function called by the bus-generic code to let + * the bus-specific code know that there is data available in the + * TX FIFO for transmission to the device. + * + * This function cannot sleep. + * + * @bus_reset: [fill] Function called by the bus-generic code to reset + * the device in in various ways. Doesn't need to wait for the + * reset to finish. + * + * If warm or cold reset fail, this function is expected to do a + * bus-specific reset (eg: USB reset) to get the device to a + * working state (even if it implies device disconecction). + * + * Note the warm reset is used by the firmware uploader to + * reinitialize the device. + * + * IMPORTANT: this is called very early in the device setup + * process, so it cannot rely on common infrastructure being laid + * out. + * + * @bus_bm_cmd_send: [fill] Function called to send a boot-mode + * command. Flags are defined in 'enum i2400m_bm_cmd_flags'. This + * is synchronous and has to return 0 if ok or < 0 errno code in + * any error condition. + * + * @bus_bm_wait_for_ack: [fill] Function called to wait for a + * boot-mode notification (that can be a response to a previously + * issued command or an asynchronous one). Will read until all the + * indicated size is read or timeout. Reading more or less data + * than asked for is an error condition. Return 0 if ok, < 0 errno + * code on error. + * + * The caller to this function will check if the response is a + * barker that indicates the device going into reset mode. + * + * @bus_fw_name: [fill] name of the firmware image (in most cases, + * they are all the same for a single release, except that they + * have the type of the bus embedded in the name (eg: + * i2400m-fw-X-VERSION.sbcf, where X is the bus name). + * + * @bus_bm_mac_addr_impaired: [fill] Set to true if the device's MAC + * address provided in boot mode is kind of broken and needs to + * be re-read later on. + * + * + * @wimax_dev: WiMAX generic device for linkage into the kernel WiMAX + * stack. Due to the way a net_device is allocated, we need to + * force this to be the first field so that we can get from + * netdev_priv() the right pointer. + * + * @state: device's state (as reported by it) + * + * @state_wq: waitqueue that is woken up whenever the state changes + * + * @tx_lock: spinlock to protect TX members + * + * @tx_buf: FIFO buffer for TX; we queue data here + * + * @tx_in: FIFO index for incoming data. Note this doesn't wrap around + * and it is always greater than @tx_out. + * + * @tx_out: FIFO index for outgoing data + * + * @tx_msg: current TX message that is active in the FIFO for + * appending payloads. + * + * @tx_sequence: current sequence number for TX messages from the + * device to the host. + * + * @tx_msg_size: size of the current message being transmitted by the + * bus-specific code. + * + * @tx_pl_num: total number of payloads sent + * + * @tx_pl_max: maximum number of payloads sent in a TX message + * + * @tx_pl_min: minimum number of payloads sent in a TX message + * + * @tx_num: number of TX messages sent + * + * @tx_size_acc: number of bytes in all TX messages sent + * (this is different to net_dev's statistics as it also counts + * control messages). + * + * @tx_size_min: smallest TX message sent. + * + * @tx_size_max: biggest TX message sent. + * + * @rx_lock: spinlock to protect RX members + * + * @rx_pl_num: total number of payloads received + * + * @rx_pl_max: maximum number of payloads received in a RX message + * + * @rx_pl_min: minimum number of payloads received in a RX message + * + * @rx_num: number of RX messages received + * + * @rx_size_acc: number of bytes in all RX messages received + * (this is different to net_dev's statistics as it also counts + * control messages). + * + * @rx_size_min: smallest RX message received. + * + * @rx_size_max: buggest RX message received. + * + * @init_mutex: Mutex used for serializing the device bringup + * sequence; this way if the device reboots in the middle, we + * don't try to do a bringup again while we are tearing down the + * one that failed. + * + * Can't reuse @msg_mutex because from within the bringup sequence + * we need to send messages to the device and thus use @msg_mutex. + * + * @msg_mutex: mutex used to send control commands to the device (we + * only allow one at a time, per host-device interface design). + * + * @msg_completion: used to wait for an ack to a control command sent + * to the device. + * + * @ack_skb: used to store the actual ack to a control command if the + * reception of the command was successful. Otherwise, a ERR_PTR() + * errno code that indicates what failed with the ack reception. + * + * Only valid after @msg_completion is woken up. Only updateable + * if @msg_completion is armed. Only touched by + * i2400m_msg_to_dev(). + * + * Protected by @rx_lock. In theory the command execution flow is + * sequential, but in case the device sends an out-of-phase or + * very delayed response, we need to avoid it trampling current + * execution. + * + * @bm_cmd_buf: boot mode command buffer for composing firmware upload + * commands. + * + * USB can't r/w to stack, vmalloc, etc...as well, we end up + * having to alloc/free a lot to compose commands, so we use these + * for stagging and not having to realloc all the time. + * + * This assumes the code always runs serialized. Only one thread + * can call i2400m_bm_cmd() at the same time. + * + * @bm_ack_buf: boot mode acknoledge buffer for staging reception of + * responses to commands. + * + * See @bm_cmd_buf. + * + * @work_queue: work queue for processing device reports. This + * workqueue cannot be used for processing TX or RX to the device, + * as from it we'll process device reports, which might require + * further communication with the device. + * + * @debugfs_dentry: hookup for debugfs files. + * These have to be in a separate directory, a child of + * (wimax_dev->debugfs_dentry) so they can be removed when the + * module unloads, as we don't keep each dentry. + */ +struct i2400m { + struct wimax_dev wimax_dev; /* FIRST! See doc */ + + unsigned updown:1; /* Network device is up or down */ + unsigned boot_mode:1; /* is the device in boot mode? */ + unsigned sboot:1; /* signed or unsigned fw boot */ + unsigned ready:1; /* all probing steps done */ + u8 trace_msg_from_user; /* echo rx msgs to 'trace' pipe */ + /* typed u8 so debugfs/u8 can tweak */ + enum i2400m_system_state state; + wait_queue_head_t state_wq; /* Woken up when on state updates */ + + size_t bus_tx_block_size; + size_t bus_pl_size_max; + int (*bus_dev_start)(struct i2400m *); + void (*bus_dev_stop)(struct i2400m *); + void (*bus_tx_kick)(struct i2400m *); + int (*bus_reset)(struct i2400m *, enum i2400m_reset_type); + ssize_t (*bus_bm_cmd_send)(struct i2400m *, + const struct i2400m_bootrom_header *, + size_t, int flags); + ssize_t (*bus_bm_wait_for_ack)(struct i2400m *, + struct i2400m_bootrom_header *, size_t); + const char *bus_fw_name; + unsigned bus_bm_mac_addr_impaired:1; + + spinlock_t tx_lock; /* protect TX state */ + void *tx_buf; + size_t tx_in, tx_out; + struct i2400m_msg_hdr *tx_msg; + size_t tx_sequence, tx_msg_size; + /* TX stats */ + unsigned tx_pl_num, tx_pl_max, tx_pl_min, + tx_num, tx_size_acc, tx_size_min, tx_size_max; + + /* RX stats */ + spinlock_t rx_lock; /* protect RX state */ + unsigned rx_pl_num, rx_pl_max, rx_pl_min, + rx_num, rx_size_acc, rx_size_min, rx_size_max; + + struct mutex msg_mutex; /* serialize command execution */ + struct completion msg_completion; + struct sk_buff *ack_skb; /* protected by rx_lock */ + + void *bm_ack_buf; /* for receiving acks over USB */ + void *bm_cmd_buf; /* for issuing commands over USB */ + + struct workqueue_struct *work_queue; + + struct mutex init_mutex; /* protect bringup seq */ + struct i2400m_reset_ctx *reset_ctx; /* protected by init_mutex */ + + struct work_struct wake_tx_ws; + struct sk_buff *wake_tx_skb; + + struct dentry *debugfs_dentry; +}; + + +/* + * Initialize a 'struct i2400m' from all zeroes + * + * This is a bus-generic API call. + */ +static inline +void i2400m_init(struct i2400m *i2400m) +{ + wimax_dev_init(&i2400m->wimax_dev); + + i2400m->boot_mode = 1; + init_waitqueue_head(&i2400m->state_wq); + + spin_lock_init(&i2400m->tx_lock); + i2400m->tx_pl_min = UINT_MAX; + i2400m->tx_size_min = UINT_MAX; + + spin_lock_init(&i2400m->rx_lock); + i2400m->rx_pl_min = UINT_MAX; + i2400m->rx_size_min = UINT_MAX; + + mutex_init(&i2400m->msg_mutex); + init_completion(&i2400m->msg_completion); + + mutex_init(&i2400m->init_mutex); + /* wake_tx_ws is initialized in i2400m_tx_setup() */ +} + + +/* + * Bus-generic internal APIs + * ------------------------- + */ + +static inline +struct i2400m *wimax_dev_to_i2400m(struct wimax_dev *wimax_dev) +{ + return container_of(wimax_dev, struct i2400m, wimax_dev); +} + +static inline +struct i2400m *net_dev_to_i2400m(struct net_device *net_dev) +{ + return wimax_dev_to_i2400m(netdev_priv(net_dev)); +} + +/* + * Boot mode support + */ + +/** + * i2400m_bm_cmd_flags - flags to i2400m_bm_cmd() + * + * @I2400M_BM_CMD_RAW: send the command block as-is, without doing any + * extra processing for adding CRC. + */ +enum i2400m_bm_cmd_flags { + I2400M_BM_CMD_RAW = 1 << 2, +}; + +/** + * i2400m_bri - Boot-ROM indicators + * + * Flags for i2400m_bootrom_init() and i2400m_dev_bootstrap() [which + * are passed from things like i2400m_setup()]. Can be combined with + * |. + * + * @I2400M_BRI_SOFT: The device rebooted already and a reboot + * barker received, proceed directly to ack the boot sequence. + * @I2400M_BRI_NO_REBOOT: Do not reboot the device and proceed + * directly to wait for a reboot barker from the device. + * @I2400M_BRI_MAC_REINIT: We need to reinitialize the boot + * rom after reading the MAC adress. This is quite a dirty hack, + * if you ask me -- the device requires the bootrom to be + * intialized after reading the MAC address. + */ +enum i2400m_bri { + I2400M_BRI_SOFT = 1 << 1, + I2400M_BRI_NO_REBOOT = 1 << 2, + I2400M_BRI_MAC_REINIT = 1 << 3, +}; + +extern void i2400m_bm_cmd_prepare(struct i2400m_bootrom_header *); +extern int i2400m_dev_bootstrap(struct i2400m *, enum i2400m_bri); +extern int i2400m_read_mac_addr(struct i2400m *); +extern int i2400m_bootrom_init(struct i2400m *, enum i2400m_bri); + +/* Make/grok boot-rom header commands */ + +static inline +__le32 i2400m_brh_command(enum i2400m_brh_opcode opcode, unsigned use_checksum, + unsigned direct_access) +{ + return cpu_to_le32( + I2400M_BRH_SIGNATURE + | (direct_access ? I2400M_BRH_DIRECT_ACCESS : 0) + | I2400M_BRH_RESPONSE_REQUIRED /* response always required */ + | (use_checksum ? I2400M_BRH_USE_CHECKSUM : 0) + | (opcode & I2400M_BRH_OPCODE_MASK)); +} + +static inline +void i2400m_brh_set_opcode(struct i2400m_bootrom_header *hdr, + enum i2400m_brh_opcode opcode) +{ + hdr->command = cpu_to_le32( + (le32_to_cpu(hdr->command) & ~I2400M_BRH_OPCODE_MASK) + | (opcode & I2400M_BRH_OPCODE_MASK)); +} + +static inline +unsigned i2400m_brh_get_opcode(const struct i2400m_bootrom_header *hdr) +{ + return le32_to_cpu(hdr->command) & I2400M_BRH_OPCODE_MASK; +} + +static inline +unsigned i2400m_brh_get_response(const struct i2400m_bootrom_header *hdr) +{ + return (le32_to_cpu(hdr->command) & I2400M_BRH_RESPONSE_MASK) + >> I2400M_BRH_RESPONSE_SHIFT; +} + +static inline +unsigned i2400m_brh_get_use_checksum(const struct i2400m_bootrom_header *hdr) +{ + return le32_to_cpu(hdr->command) & I2400M_BRH_USE_CHECKSUM; +} + +static inline +unsigned i2400m_brh_get_response_required( + const struct i2400m_bootrom_header *hdr) +{ + return le32_to_cpu(hdr->command) & I2400M_BRH_RESPONSE_REQUIRED; +} + +static inline +unsigned i2400m_brh_get_direct_access(const struct i2400m_bootrom_header *hdr) +{ + return le32_to_cpu(hdr->command) & I2400M_BRH_DIRECT_ACCESS; +} + +static inline +unsigned i2400m_brh_get_signature(const struct i2400m_bootrom_header *hdr) +{ + return (le32_to_cpu(hdr->command) & I2400M_BRH_SIGNATURE_MASK) + >> I2400M_BRH_SIGNATURE_SHIFT; +} + + +/* + * Driver / device setup and internal functions + */ +extern void i2400m_netdev_setup(struct net_device *net_dev); +extern int i2400m_tx_setup(struct i2400m *); +extern void i2400m_wake_tx_work(struct work_struct *); +extern void i2400m_tx_release(struct i2400m *); + +extern void i2400m_net_rx(struct i2400m *, struct sk_buff *, unsigned, + const void *, int); +enum i2400m_pt; +extern int i2400m_tx(struct i2400m *, const void *, size_t, enum i2400m_pt); + +#ifdef CONFIG_DEBUG_FS +extern int i2400m_debugfs_add(struct i2400m *); +extern void i2400m_debugfs_rm(struct i2400m *); +#else +static inline int i2400m_debugfs_add(struct i2400m *i2400m) +{ + return 0; +} +static inline void i2400m_debugfs_rm(struct i2400m *i2400m) {} +#endif + +/* Called by _dev_start()/_dev_stop() to initialize the device itself */ +extern int i2400m_dev_initialize(struct i2400m *); +extern void i2400m_dev_shutdown(struct i2400m *); + +extern struct attribute_group i2400m_dev_attr_group; + +extern int i2400m_schedule_work(struct i2400m *, + void (*)(struct work_struct *), gfp_t); + +/* HDI message's payload description handling */ + +static inline +size_t i2400m_pld_size(const struct i2400m_pld *pld) +{ + return I2400M_PLD_SIZE_MASK & le32_to_cpu(pld->val); +} + +static inline +enum i2400m_pt i2400m_pld_type(const struct i2400m_pld *pld) +{ + return (I2400M_PLD_TYPE_MASK & le32_to_cpu(pld->val)) + >> I2400M_PLD_TYPE_SHIFT; +} + +static inline +void i2400m_pld_set(struct i2400m_pld *pld, size_t size, + enum i2400m_pt type) +{ + pld->val = cpu_to_le32( + ((type << I2400M_PLD_TYPE_SHIFT) & I2400M_PLD_TYPE_MASK) + | (size & I2400M_PLD_SIZE_MASK)); +} + + +/* + * API for the bus-specific drivers + * -------------------------------- + */ + +static inline +struct i2400m *i2400m_get(struct i2400m *i2400m) +{ + dev_hold(i2400m->wimax_dev.net_dev); + return i2400m; +} + +static inline +void i2400m_put(struct i2400m *i2400m) +{ + dev_put(i2400m->wimax_dev.net_dev); +} + +extern int i2400m_dev_reset_handle(struct i2400m *); + +/* + * _setup()/_release() are called by the probe/disconnect functions of + * the bus-specific drivers. + */ +extern int i2400m_setup(struct i2400m *, enum i2400m_bri bm_flags); +extern void i2400m_release(struct i2400m *); + +extern int i2400m_rx(struct i2400m *, struct sk_buff *); +extern struct i2400m_msg_hdr *i2400m_tx_msg_get(struct i2400m *, size_t *); +extern void i2400m_tx_msg_sent(struct i2400m *); + +static const __le32 i2400m_NBOOT_BARKER[4] = { + __constant_cpu_to_le32(I2400M_NBOOT_BARKER), + __constant_cpu_to_le32(I2400M_NBOOT_BARKER), + __constant_cpu_to_le32(I2400M_NBOOT_BARKER), + __constant_cpu_to_le32(I2400M_NBOOT_BARKER) +}; + +static const __le32 i2400m_SBOOT_BARKER[4] = { + __constant_cpu_to_le32(I2400M_SBOOT_BARKER), + __constant_cpu_to_le32(I2400M_SBOOT_BARKER), + __constant_cpu_to_le32(I2400M_SBOOT_BARKER), + __constant_cpu_to_le32(I2400M_SBOOT_BARKER) +}; + + +/* + * Utility functions + */ + +static inline +struct device *i2400m_dev(struct i2400m *i2400m) +{ + return i2400m->wimax_dev.net_dev->dev.parent; +} + +/* + * Helper for scheduling simple work functions + * + * This struct can get any kind of payload attached (normally in the + * form of a struct where you pack the stuff you want to pass to the + * _work function). + */ +struct i2400m_work { + struct work_struct ws; + struct i2400m *i2400m; + u8 pl[0]; +}; +extern int i2400m_queue_work(struct i2400m *, + void (*)(struct work_struct *), gfp_t, + const void *, size_t); + +extern int i2400m_msg_check_status(const struct i2400m_l3l4_hdr *, + char *, size_t); +extern int i2400m_msg_size_check(struct i2400m *, + const struct i2400m_l3l4_hdr *, size_t); +extern struct sk_buff *i2400m_msg_to_dev(struct i2400m *, const void *, size_t); +extern void i2400m_msg_to_dev_cancel_wait(struct i2400m *, int); +extern void i2400m_msg_ack_hook(struct i2400m *, + const struct i2400m_l3l4_hdr *, size_t); +extern void i2400m_report_hook(struct i2400m *, + const struct i2400m_l3l4_hdr *, size_t); +extern int i2400m_cmd_enter_powersave(struct i2400m *); +extern int i2400m_cmd_get_state(struct i2400m *); +extern int i2400m_cmd_exit_idle(struct i2400m *); +extern struct sk_buff *i2400m_get_device_info(struct i2400m *); +extern int i2400m_firmware_check(struct i2400m *); +extern int i2400m_set_init_config(struct i2400m *, + const struct i2400m_tlv_hdr **, size_t); + +static inline +struct usb_endpoint_descriptor *usb_get_epd(struct usb_interface *iface, int ep) +{ + return &iface->cur_altsetting->endpoint[ep].desc; +} + +extern int i2400m_op_rfkill_sw_toggle(struct wimax_dev *, + enum wimax_rf_state); +extern void i2400m_report_tlv_rf_switches_status( + struct i2400m *, const struct i2400m_tlv_rf_switches_status *); + + +/* + * Do a millisecond-sleep for allowing wireshark to dump all the data + * packets. Used only for debugging. + */ +static inline +void __i2400m_msleep(unsigned ms) +{ +#if 1 +#else + msleep(ms); +#endif +} + +/* Module parameters */ + +extern int i2400m_idle_mode_disabled; + + +#endif /* #ifndef __I2400M_H__ */ diff --git a/drivers/net/wimax/i2400m/netdev.c b/drivers/net/wimax/i2400m/netdev.c new file mode 100644 index 000000000000..63fe708e8a31 --- /dev/null +++ b/drivers/net/wimax/i2400m/netdev.c @@ -0,0 +1,524 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Glue with the networking stack + * + * + * Copyright (C) 2007 Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.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. + * + * 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. + * + * + * This implements an ethernet device for the i2400m. + * + * We fake being an ethernet device to simplify the support from user + * space and from the other side. The world is (sadly) configured to + * take in only Ethernet devices... + * + * Because of this, currently there is an copy-each-rxed-packet + * overhead on the RX path. Each IP packet has to be reallocated to + * add an ethernet header (as there is no space in what we get from + * the device). This is a known drawback and coming versions of the + * device's firmware are being changed to add header space that can be + * used to insert the ethernet header without having to reallocate and + * copy. + * + * TX error handling is tricky; because we have to FIFO/queue the + * buffers for transmission (as the hardware likes it aggregated), we + * just give the skb to the TX subsystem and by the time it is + * transmitted, we have long forgotten about it. So we just don't care + * too much about it. + * + * Note that when the device is in idle mode with the basestation, we + * need to negotiate coming back up online. That involves negotiation + * and possible user space interaction. Thus, we defer to a workqueue + * to do all that. By default, we only queue a single packet and drop + * the rest, as potentially the time to go back from idle to normal is + * long. + * + * ROADMAP + * + * i2400m_open Called on ifconfig up + * i2400m_stop Called on ifconfig down + * + * i2400m_hard_start_xmit Called by the network stack to send a packet + * i2400m_net_wake_tx Wake up device from basestation-IDLE & TX + * i2400m_wake_tx_work + * i2400m_cmd_exit_idle + * i2400m_tx + * i2400m_net_tx TX a data frame + * i2400m_tx + * + * i2400m_change_mtu Called on ifconfig mtu XXX + * + * i2400m_tx_timeout Called when the device times out + * + * i2400m_net_rx Called by the RX code when a data frame is + * available. + * i2400m_netdev_setup Called to setup all the netdev stuff from + * alloc_netdev. + */ +#include <linux/if_arp.h> +#include <linux/netdevice.h> +#include "i2400m.h" + + +#define D_SUBMODULE netdev +#include "debug-levels.h" + +enum { +/* netdev interface */ + /* + * Out of NWG spec (R1_v1.2.2), 3.3.3 ASN Bearer Plane MTU Size + * + * The MTU is 1400 or less + */ + I2400M_MAX_MTU = 1400, + I2400M_TX_TIMEOUT = HZ, + I2400M_TX_QLEN = 5, +}; + + +static +int i2400m_open(struct net_device *net_dev) +{ + int result; + struct i2400m *i2400m = net_dev_to_i2400m(net_dev); + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(net_dev %p [i2400m %p])\n", net_dev, i2400m); + if (i2400m->ready == 0) { + dev_err(dev, "Device is still initializing\n"); + result = -EBUSY; + } else + result = 0; + d_fnend(3, dev, "(net_dev %p [i2400m %p]) = %d\n", + net_dev, i2400m, result); + return result; +} + + +/* + * + * On kernel versions where cancel_work_sync() didn't return anything, + * we rely on wake_tx_skb() being non-NULL. + */ +static +int i2400m_stop(struct net_device *net_dev) +{ + struct i2400m *i2400m = net_dev_to_i2400m(net_dev); + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(net_dev %p [i2400m %p])\n", net_dev, i2400m); + /* See i2400m_hard_start_xmit(), references are taken there + * and here we release them if the work was still + * pending. Note we can't differentiate work not pending vs + * never scheduled, so the NULL check does that. */ + if (cancel_work_sync(&i2400m->wake_tx_ws) == 0 + && i2400m->wake_tx_skb != NULL) { + unsigned long flags; + struct sk_buff *wake_tx_skb; + spin_lock_irqsave(&i2400m->tx_lock, flags); + wake_tx_skb = i2400m->wake_tx_skb; /* compat help */ + i2400m->wake_tx_skb = NULL; /* compat help */ + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + i2400m_put(i2400m); + kfree_skb(wake_tx_skb); + } + d_fnend(3, dev, "(net_dev %p [i2400m %p]) = 0\n", net_dev, i2400m); + return 0; +} + + +/* + * Wake up the device and transmit a held SKB, then restart the net queue + * + * When the device goes into basestation-idle mode, we need to tell it + * to exit that mode; it will negotiate with the base station, user + * space may have to intervene to rehandshake crypto and then tell us + * when it is ready to transmit the packet we have "queued". Still we + * need to give it sometime after it reports being ok. + * + * On error, there is not much we can do. If the error was on TX, we + * still wake the queue up to see if the next packet will be luckier. + * + * If _cmd_exit_idle() fails...well, it could be many things; most + * commonly it is that something else took the device out of IDLE mode + * (for example, the base station). In that case we get an -EILSEQ and + * we are just going to ignore that one. If the device is back to + * connected, then fine -- if it is someother state, the packet will + * be dropped anyway. + */ +void i2400m_wake_tx_work(struct work_struct *ws) +{ + int result; + struct i2400m *i2400m = container_of(ws, struct i2400m, wake_tx_ws); + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *skb = i2400m->wake_tx_skb; + unsigned long flags; + + spin_lock_irqsave(&i2400m->tx_lock, flags); + skb = i2400m->wake_tx_skb; + i2400m->wake_tx_skb = NULL; + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + + d_fnstart(3, dev, "(ws %p i2400m %p skb %p)\n", ws, i2400m, skb); + result = -EINVAL; + if (skb == NULL) { + dev_err(dev, "WAKE&TX: skb dissapeared!\n"); + goto out_put; + } + result = i2400m_cmd_exit_idle(i2400m); + if (result == -EILSEQ) + result = 0; + if (result < 0) { + dev_err(dev, "WAKE&TX: device didn't get out of idle: " + "%d\n", result); + goto error; + } + result = wait_event_timeout(i2400m->state_wq, + i2400m->state != I2400M_SS_IDLE, 5 * HZ); + if (result == 0) + result = -ETIMEDOUT; + if (result < 0) { + dev_err(dev, "WAKE&TX: error waiting for device to exit IDLE: " + "%d\n", result); + goto error; + } + msleep(20); /* device still needs some time or it drops it */ + result = i2400m_tx(i2400m, skb->data, skb->len, I2400M_PT_DATA); + netif_wake_queue(i2400m->wimax_dev.net_dev); +error: + kfree_skb(skb); /* refcount transferred by _hard_start_xmit() */ +out_put: + i2400m_put(i2400m); + d_fnend(3, dev, "(ws %p i2400m %p skb %p) = void [%d]\n", + ws, i2400m, skb, result); +} + + +/* + * Prepare the data payload TX header + * + * The i2400m expects a 4 byte header in front of a data packet. + * + * Because we pretend to be an ethernet device, this packet comes with + * an ethernet header. Pull it and push our header. + */ +static +void i2400m_tx_prep_header(struct sk_buff *skb) +{ + struct i2400m_pl_data_hdr *pl_hdr; + skb_pull(skb, ETH_HLEN); + pl_hdr = (struct i2400m_pl_data_hdr *) skb_push(skb, sizeof(*pl_hdr)); + pl_hdr->reserved = 0; +} + + +/* + * TX an skb to an idle device + * + * When the device is in basestation-idle mode, we need to wake it up + * and then TX. So we queue a work_struct for doing so. + * + * We need to get an extra ref for the skb (so it is not dropped), as + * well as be careful not to queue more than one request (won't help + * at all). If more than one request comes or there are errors, we + * just drop the packets (see i2400m_hard_start_xmit()). + */ +static +int i2400m_net_wake_tx(struct i2400m *i2400m, struct net_device *net_dev, + struct sk_buff *skb) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + unsigned long flags; + + d_fnstart(3, dev, "(skb %p net_dev %p)\n", skb, net_dev); + if (net_ratelimit()) { + d_printf(3, dev, "WAKE&NETTX: " + "skb %p sending %d bytes to radio\n", + skb, skb->len); + d_dump(4, dev, skb->data, skb->len); + } + /* We hold a ref count for i2400m and skb, so when + * stopping() the device, we need to cancel that work + * and if pending, release those resources. */ + result = 0; + spin_lock_irqsave(&i2400m->tx_lock, flags); + if (!work_pending(&i2400m->wake_tx_ws)) { + netif_stop_queue(net_dev); + i2400m_get(i2400m); + i2400m->wake_tx_skb = skb_get(skb); /* transfer ref count */ + i2400m_tx_prep_header(skb); + result = schedule_work(&i2400m->wake_tx_ws); + WARN_ON(result == 0); + } + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + if (result == 0) { + /* Yes, this happens even if we stopped the + * queue -- blame the queue disciplines that + * queue without looking -- I guess there is a reason + * for that. */ + if (net_ratelimit()) + d_printf(1, dev, "NETTX: device exiting idle, " + "dropping skb %p, queue running %d\n", + skb, netif_queue_stopped(net_dev)); + result = -EBUSY; + } + d_fnend(3, dev, "(skb %p net_dev %p) = %d\n", skb, net_dev, result); + return result; +} + + +/* + * Transmit a packet to the base station on behalf of the network stack. + * + * Returns: 0 if ok, < 0 errno code on error. + * + * We need to pull the ethernet header and add the hardware header, + * which is currently set to all zeroes and reserved. + */ +static +int i2400m_net_tx(struct i2400m *i2400m, struct net_device *net_dev, + struct sk_buff *skb) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p net_dev %p skb %p)\n", + i2400m, net_dev, skb); + /* FIXME: check eth hdr, only IPv4 is routed by the device as of now */ + net_dev->trans_start = jiffies; + i2400m_tx_prep_header(skb); + d_printf(3, dev, "NETTX: skb %p sending %d bytes to radio\n", + skb, skb->len); + d_dump(4, dev, skb->data, skb->len); + result = i2400m_tx(i2400m, skb->data, skb->len, I2400M_PT_DATA); + d_fnend(3, dev, "(i2400m %p net_dev %p skb %p) = %d\n", + i2400m, net_dev, skb, result); + return result; +} + + +/* + * Transmit a packet to the base station on behalf of the network stack + * + * + * Returns: NETDEV_TX_OK (always, even in case of error) + * + * In case of error, we just drop it. Reasons: + * + * - we add a hw header to each skb, and if the network stack + * retries, we have no way to know if that skb has it or not. + * + * - network protocols have their own drop-recovery mechanisms + * + * - there is not much else we can do + * + * If the device is idle, we need to wake it up; that is an operation + * that will sleep. See i2400m_net_wake_tx() for details. + */ +static +int i2400m_hard_start_xmit(struct sk_buff *skb, + struct net_device *net_dev) +{ + int result; + struct i2400m *i2400m = net_dev_to_i2400m(net_dev); + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(skb %p net_dev %p)\n", skb, net_dev); + if (i2400m->state == I2400M_SS_IDLE) + result = i2400m_net_wake_tx(i2400m, net_dev, skb); + else + result = i2400m_net_tx(i2400m, net_dev, skb); + if (result < 0) + net_dev->stats.tx_dropped++; + else { + net_dev->stats.tx_packets++; + net_dev->stats.tx_bytes += skb->len; + } + kfree_skb(skb); + result = NETDEV_TX_OK; + d_fnend(3, dev, "(skb %p net_dev %p) = %d\n", skb, net_dev, result); + return result; +} + + +static +int i2400m_change_mtu(struct net_device *net_dev, int new_mtu) +{ + int result; + struct i2400m *i2400m = net_dev_to_i2400m(net_dev); + struct device *dev = i2400m_dev(i2400m); + + if (new_mtu >= I2400M_MAX_MTU) { + dev_err(dev, "Cannot change MTU to %d (max is %d)\n", + new_mtu, I2400M_MAX_MTU); + result = -EINVAL; + } else { + net_dev->mtu = new_mtu; + result = 0; + } + return result; +} + + +static +void i2400m_tx_timeout(struct net_device *net_dev) +{ + /* + * We might want to kick the device + * + * There is not much we can do though, as the device requires + * that we send the data aggregated. By the time we receive + * this, there might be data pending to be sent or not... + */ + net_dev->stats.tx_errors++; + return; +} + + +/* + * Create a fake ethernet header + * + * For emulating an ethernet device, every received IP header has to + * be prefixed with an ethernet header. + * + * What we receive has (potentially) many IP packets concatenated with + * no ETH_HLEN bytes prefixed. Thus there is no space for an eth + * header. + * + * We would have to reallocate or do ugly fragment tricks in order to + * add it. + * + * But what we do is use the header space of the RX transaction + * (*msg_hdr) as we don't need it anymore; then we'll point all the + * data skbs there, as they share the same backing store. + * + * We only support IPv4 for v3 firmware. + */ +static +void i2400m_rx_fake_eth_header(struct net_device *net_dev, + void *_eth_hdr) +{ + struct ethhdr *eth_hdr = _eth_hdr; + + memcpy(eth_hdr->h_dest, net_dev->dev_addr, sizeof(eth_hdr->h_dest)); + memset(eth_hdr->h_source, 0, sizeof(eth_hdr->h_dest)); + eth_hdr->h_proto = __constant_cpu_to_be16(ETH_P_IP); +} + + +/* + * i2400m_net_rx - pass a network packet to the stack + * + * @i2400m: device instance + * @skb_rx: the skb where the buffer pointed to by @buf is + * @i: 1 if payload is the only one + * @buf: pointer to the buffer containing the data + * @len: buffer's length + * + * We just clone the skb and set it up so that it's skb->data pointer + * points to "buf" and it's length. + * + * Note that if the payload is the last (or the only one) in a + * multi-payload message, we don't clone the SKB but just reuse it. + * + * This function is normally run from a thread context. However, we + * still use netif_rx() instead of netif_receive_skb() as was + * recommended in the mailing list. Reason is in some stress tests + * when sending/receiving a lot of data we seem to hit a softlock in + * the kernel's TCP implementation [aroudn tcp_delay_timer()]. Using + * netif_rx() took care of the issue. + * + * This is, of course, still open to do more research on why running + * with netif_receive_skb() hits this softlock. FIXME. + * + * FIXME: currently we don't do any efforts at distinguishing if what + * we got was an IPv4 or IPv6 header, to setup the protocol field + * correctly. + */ +void i2400m_net_rx(struct i2400m *i2400m, struct sk_buff *skb_rx, + unsigned i, const void *buf, int buf_len) +{ + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *skb; + + d_fnstart(2, dev, "(i2400m %p buf %p buf_len %d)\n", + i2400m, buf, buf_len); + if (i) { + skb = skb_get(skb_rx); + d_printf(2, dev, "RX: reusing first payload skb %p\n", skb); + skb_pull(skb, buf - (void *) skb->data); + skb_trim(skb, (void *) skb_end_pointer(skb) - buf); + } else { + /* Yes, this is bad -- a lot of overhead -- see + * comments at the top of the file */ + skb = __netdev_alloc_skb(net_dev, buf_len, GFP_KERNEL); + if (skb == NULL) { + dev_err(dev, "NETRX: no memory to realloc skb\n"); + net_dev->stats.rx_dropped++; + goto error_skb_realloc; + } + memcpy(skb_put(skb, buf_len), buf, buf_len); + } + i2400m_rx_fake_eth_header(i2400m->wimax_dev.net_dev, + skb->data - ETH_HLEN); + skb_set_mac_header(skb, -ETH_HLEN); + skb->dev = i2400m->wimax_dev.net_dev; + skb->protocol = htons(ETH_P_IP); + net_dev->stats.rx_packets++; + net_dev->stats.rx_bytes += buf_len; + d_printf(3, dev, "NETRX: receiving %d bytes to network stack\n", + buf_len); + d_dump(4, dev, buf, buf_len); + netif_rx_ni(skb); /* see notes in function header */ +error_skb_realloc: + d_fnend(2, dev, "(i2400m %p buf %p buf_len %d) = void\n", + i2400m, buf, buf_len); +} + + +/** + * i2400m_netdev_setup - Setup setup @net_dev's i2400m private data + * + * Called by alloc_netdev() + */ +void i2400m_netdev_setup(struct net_device *net_dev) +{ + d_fnstart(3, NULL, "(net_dev %p)\n", net_dev); + ether_setup(net_dev); + net_dev->mtu = I2400M_MAX_MTU; + net_dev->tx_queue_len = I2400M_TX_QLEN; + net_dev->features = + NETIF_F_VLAN_CHALLENGED + | NETIF_F_HIGHDMA; + net_dev->flags = + IFF_NOARP /* i2400m is apure IP device */ + & (~IFF_BROADCAST /* i2400m is P2P */ + & ~IFF_MULTICAST); + net_dev->watchdog_timeo = I2400M_TX_TIMEOUT; + net_dev->open = i2400m_open; + net_dev->stop = i2400m_stop; + net_dev->hard_start_xmit = i2400m_hard_start_xmit; + net_dev->change_mtu = i2400m_change_mtu; + net_dev->tx_timeout = i2400m_tx_timeout; + d_fnend(3, NULL, "(net_dev %p) = void\n", net_dev); +} +EXPORT_SYMBOL_GPL(i2400m_netdev_setup); + diff --git a/drivers/net/wimax/i2400m/op-rfkill.c b/drivers/net/wimax/i2400m/op-rfkill.c new file mode 100644 index 000000000000..487ec58cea46 --- /dev/null +++ b/drivers/net/wimax/i2400m/op-rfkill.c @@ -0,0 +1,207 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Implement backend for the WiMAX stack rfkill support + * + * + * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.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. + * + * 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. + * + * + * The WiMAX kernel stack integrates into RF-Kill and keeps the + * switches's status. We just need to: + * + * - report changes in the HW RF Kill switch [with + * wimax_rfkill_{sw,hw}_report(), which happens when we detect those + * indications coming through hardware reports]. We also do it on + * initialization to let the stack know the intial HW state. + * + * - implement indications from the stack to change the SW RF Kill + * switch (coming from sysfs, the wimax stack or user space). + */ +#include "i2400m.h" +#include <linux/wimax/i2400m.h> + + + +#define D_SUBMODULE rfkill +#include "debug-levels.h" + +/* + * Return true if the i2400m radio is in the requested wimax_rf_state state + * + */ +static +int i2400m_radio_is(struct i2400m *i2400m, enum wimax_rf_state state) +{ + if (state == WIMAX_RF_OFF) + return i2400m->state == I2400M_SS_RF_OFF + || i2400m->state == I2400M_SS_RF_SHUTDOWN; + else if (state == WIMAX_RF_ON) + /* state == WIMAX_RF_ON */ + return i2400m->state != I2400M_SS_RF_OFF + && i2400m->state != I2400M_SS_RF_SHUTDOWN; + else + BUG(); +} + + +/* + * WiMAX stack operation: implement SW RFKill toggling + * + * @wimax_dev: device descriptor + * @skb: skb where the message has been received; skb->data is + * expected to point to the message payload. + * @genl_info: passed by the generic netlink layer + * + * Generic Netlink will call this function when a message is sent from + * userspace to change the software RF-Kill switch status. + * + * This function will set the device's sofware RF-Kill switch state to + * match what is requested. + * + * NOTE: the i2400m has a strict state machine; we can only set the + * RF-Kill switch when it is on, the HW RF-Kill is on and the + * device is initialized. So we ignore errors steaming from not + * being in the right state (-EILSEQ). + */ +int i2400m_op_rfkill_sw_toggle(struct wimax_dev *wimax_dev, + enum wimax_rf_state state) +{ + int result; + struct i2400m *i2400m = wimax_dev_to_i2400m(wimax_dev); + struct device *dev = i2400m_dev(i2400m); + struct sk_buff *ack_skb; + struct { + struct i2400m_l3l4_hdr hdr; + struct i2400m_tlv_rf_operation sw_rf; + } __attribute__((packed)) *cmd; + char strerr[32]; + + d_fnstart(4, dev, "(wimax_dev %p state %d)\n", wimax_dev, state); + + result = -ENOMEM; + cmd = kzalloc(sizeof(*cmd), GFP_KERNEL); + if (cmd == NULL) + goto error_alloc; + cmd->hdr.type = cpu_to_le16(I2400M_MT_CMD_RF_CONTROL); + cmd->hdr.length = sizeof(cmd->sw_rf); + cmd->hdr.version = cpu_to_le16(I2400M_L3L4_VERSION); + cmd->sw_rf.hdr.type = cpu_to_le16(I2400M_TLV_RF_OPERATION); + cmd->sw_rf.hdr.length = cpu_to_le16(sizeof(cmd->sw_rf.status)); + switch (state) { + case WIMAX_RF_OFF: /* RFKILL ON, radio OFF */ + cmd->sw_rf.status = cpu_to_le32(2); + break; + case WIMAX_RF_ON: /* RFKILL OFF, radio ON */ + cmd->sw_rf.status = cpu_to_le32(1); + break; + default: + BUG(); + } + + ack_skb = i2400m_msg_to_dev(i2400m, cmd, sizeof(*cmd)); + result = PTR_ERR(ack_skb); + if (IS_ERR(ack_skb)) { + dev_err(dev, "Failed to issue 'RF Control' command: %d\n", + result); + goto error_msg_to_dev; + } + result = i2400m_msg_check_status(wimax_msg_data(ack_skb), + strerr, sizeof(strerr)); + if (result < 0) { + dev_err(dev, "'RF Control' (0x%04x) command failed: %d - %s\n", + I2400M_MT_CMD_RF_CONTROL, result, strerr); + goto error_cmd; + } + + /* Now we wait for the state to change to RADIO_OFF or RADIO_ON */ + result = wait_event_timeout( + i2400m->state_wq, i2400m_radio_is(i2400m, state), + 5 * HZ); + if (result == 0) + result = -ETIMEDOUT; + if (result < 0) + dev_err(dev, "Error waiting for device to toggle RF state: " + "%d\n", result); + result = 0; +error_cmd: + kfree_skb(ack_skb); +error_msg_to_dev: +error_alloc: + d_fnend(4, dev, "(wimax_dev %p state %d) = %d\n", + wimax_dev, state, result); + return result; +} + + +/* + * Inform the WiMAX stack of changes in the RF Kill switches reported + * by the device + * + * @i2400m: device descriptor + * @rfss: TLV for RF Switches status; already validated + * + * NOTE: the reports on RF switch status cannot be trusted + * or used until the device is in a state of RADIO_OFF + * or greater. + */ +void i2400m_report_tlv_rf_switches_status( + struct i2400m *i2400m, + const struct i2400m_tlv_rf_switches_status *rfss) +{ + struct device *dev = i2400m_dev(i2400m); + enum i2400m_rf_switch_status hw, sw; + enum wimax_st wimax_state; + + sw = le32_to_cpu(rfss->sw_rf_switch); + hw = le32_to_cpu(rfss->hw_rf_switch); + + d_fnstart(3, dev, "(i2400m %p rfss %p [hw %u sw %u])\n", + i2400m, rfss, hw, sw); + /* We only process rw switch evens when the device has been + * fully initialized */ + wimax_state = wimax_state_get(&i2400m->wimax_dev); + if (wimax_state < WIMAX_ST_RADIO_OFF) { + d_printf(3, dev, "ignoring RF switches report, state %u\n", + wimax_state); + goto out; + } + switch (sw) { + case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ + wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_ON); + break; + case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ + wimax_report_rfkill_sw(&i2400m->wimax_dev, WIMAX_RF_OFF); + break; + default: + dev_err(dev, "HW BUG? Unknown RF SW state 0x%x\n", sw); + } + + switch (hw) { + case I2400M_RF_SWITCH_ON: /* RF Kill disabled (radio on) */ + wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_ON); + break; + case I2400M_RF_SWITCH_OFF: /* RF Kill enabled (radio off) */ + wimax_report_rfkill_hw(&i2400m->wimax_dev, WIMAX_RF_OFF); + break; + default: + dev_err(dev, "HW BUG? Unknown RF HW state 0x%x\n", hw); + } +out: + d_fnend(3, dev, "(i2400m %p rfss %p [hw %u sw %u]) = void\n", + i2400m, rfss, hw, sw); +} diff --git a/drivers/net/wimax/i2400m/rx.c b/drivers/net/wimax/i2400m/rx.c new file mode 100644 index 000000000000..6922022710ac --- /dev/null +++ b/drivers/net/wimax/i2400m/rx.c @@ -0,0 +1,534 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Handle incoming traffic and deliver it to the control or data planes + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * - Initial implementation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Use skb_clone(), break up processing in chunks + * - Split transport/device specific + * - Make buffer size dynamic to exert less memory pressure + * + * + * This handles the RX path. + * + * We receive an RX message from the bus-specific driver, which + * contains one or more payloads that have potentially different + * destinataries (data or control paths). + * + * So we just take that payload from the transport specific code in + * the form of an skb, break it up in chunks (a cloned skb each in the + * case of network packets) and pass it to netdev or to the + * command/ack handler (and from there to the WiMAX stack). + * + * PROTOCOL FORMAT + * + * The format of the buffer is: + * + * HEADER (struct i2400m_msg_hdr) + * PAYLOAD DESCRIPTOR 0 (struct i2400m_pld) + * PAYLOAD DESCRIPTOR 1 + * ... + * PAYLOAD DESCRIPTOR N + * PAYLOAD 0 (raw bytes) + * PAYLOAD 1 + * ... + * PAYLOAD N + * + * See tx.c for a deeper description on alignment requirements and + * other fun facts of it. + * + * ROADMAP + * + * i2400m_rx + * i2400m_rx_msg_hdr_check + * i2400m_rx_pl_descr_check + * i2400m_rx_payload + * i2400m_net_rx + * i2400m_rx_ctl + * i2400m_msg_size_check + * i2400m_report_hook_work [in a workqueue] + * i2400m_report_hook + * wimax_msg_to_user + * i2400m_rx_ctl_ack + * wimax_msg_to_user_alloc + * i2400m_rx_trace + * i2400m_msg_size_check + * wimax_msg + */ +#include <linux/kernel.h> +#include <linux/if_arp.h> +#include <linux/netdevice.h> +#include <linux/workqueue.h> +#include "i2400m.h" + + +#define D_SUBMODULE rx +#include "debug-levels.h" + +struct i2400m_report_hook_args { + struct sk_buff *skb_rx; + const struct i2400m_l3l4_hdr *l3l4_hdr; + size_t size; +}; + + +/* + * Execute i2400m_report_hook in a workqueue + * + * Unpacks arguments from the deferred call, executes it and then + * drops the references. + * + * Obvious NOTE: References are needed because we are a separate + * thread; otherwise the buffer changes under us because it is + * released by the original caller. + */ +static +void i2400m_report_hook_work(struct work_struct *ws) +{ + struct i2400m_work *iw = + container_of(ws, struct i2400m_work, ws); + struct i2400m_report_hook_args *args = (void *) iw->pl; + i2400m_report_hook(iw->i2400m, args->l3l4_hdr, args->size); + kfree_skb(args->skb_rx); + i2400m_put(iw->i2400m); + kfree(iw); +} + + +/* + * Process an ack to a command + * + * @i2400m: device descriptor + * @payload: pointer to message + * @size: size of the message + * + * Pass the acknodledgment (in an skb) to the thread that is waiting + * for it in i2400m->msg_completion. + * + * We need to coordinate properly with the thread waiting for the + * ack. Check if it is waiting or if it is gone. We loose the spinlock + * to avoid allocating on atomic contexts (yeah, could use GFP_ATOMIC, + * but this is not so speed critical). + */ +static +void i2400m_rx_ctl_ack(struct i2400m *i2400m, + const void *payload, size_t size) +{ + struct device *dev = i2400m_dev(i2400m); + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + unsigned long flags; + struct sk_buff *ack_skb; + + /* Anyone waiting for an answer? */ + spin_lock_irqsave(&i2400m->rx_lock, flags); + if (i2400m->ack_skb != ERR_PTR(-EINPROGRESS)) { + dev_err(dev, "Huh? reply to command with no waiters\n"); + goto error_no_waiter; + } + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + + ack_skb = wimax_msg_alloc(wimax_dev, NULL, payload, size, GFP_KERNEL); + + /* Check waiter didn't time out waiting for the answer... */ + spin_lock_irqsave(&i2400m->rx_lock, flags); + if (i2400m->ack_skb != ERR_PTR(-EINPROGRESS)) { + d_printf(1, dev, "Huh? waiter for command reply cancelled\n"); + goto error_waiter_cancelled; + } + if (ack_skb == NULL) { + dev_err(dev, "CMD/GET/SET ack: cannot allocate SKB\n"); + i2400m->ack_skb = ERR_PTR(-ENOMEM); + } else + i2400m->ack_skb = ack_skb; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + complete(&i2400m->msg_completion); + return; + +error_waiter_cancelled: + if (ack_skb) + kfree_skb(ack_skb); +error_no_waiter: + spin_unlock_irqrestore(&i2400m->rx_lock, flags); + return; +} + + +/* + * Receive and process a control payload + * + * @i2400m: device descriptor + * @skb_rx: skb that contains the payload (for reference counting) + * @payload: pointer to message + * @size: size of the message + * + * There are two types of control RX messages: reports (asynchronous, + * like your every day interrupts) and 'acks' (reponses to a command, + * get or set request). + * + * If it is a report, we run hooks on it (to extract information for + * things we need to do in the driver) and then pass it over to the + * WiMAX stack to send it to user space. + * + * NOTE: report processing is done in a workqueue specific to the + * generic driver, to avoid deadlocks in the system. + * + * If it is not a report, it is an ack to a previously executed + * command, set or get, so wake up whoever is waiting for it from + * i2400m_msg_to_dev(). i2400m_rx_ctl_ack() takes care of that. + * + * Note that the sizes we pass to other functions from here are the + * sizes of the _l3l4_hdr + payload, not full buffer sizes, as we have + * verified in _msg_size_check() that they are congruent. + * + * For reports: We can't clone the original skb where the data is + * because we need to send this up via netlink; netlink has to add + * headers and we can't overwrite what's preceeding the payload...as + * it is another message. So we just dup them. + */ +static +void i2400m_rx_ctl(struct i2400m *i2400m, struct sk_buff *skb_rx, + const void *payload, size_t size) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_l3l4_hdr *l3l4_hdr = payload; + unsigned msg_type; + + result = i2400m_msg_size_check(i2400m, l3l4_hdr, size); + if (result < 0) { + dev_err(dev, "HW BUG? device sent a bad message: %d\n", + result); + goto error_check; + } + msg_type = le16_to_cpu(l3l4_hdr->type); + d_printf(1, dev, "%s 0x%04x: %zu bytes\n", + msg_type & I2400M_MT_REPORT_MASK ? "REPORT" : "CMD/SET/GET", + msg_type, size); + d_dump(2, dev, l3l4_hdr, size); + if (msg_type & I2400M_MT_REPORT_MASK) { + /* These hooks have to be ran serialized; as well, the + * handling might force the execution of commands, and + * that might cause reentrancy issues with + * bus-specific subdrivers and workqueues. So we run + * it in a separate workqueue. */ + struct i2400m_report_hook_args args = { + .skb_rx = skb_rx, + .l3l4_hdr = l3l4_hdr, + .size = size + }; + if (unlikely(i2400m->ready == 0)) /* only send if up */ + return; + skb_get(skb_rx); + i2400m_queue_work(i2400m, i2400m_report_hook_work, + GFP_KERNEL, &args, sizeof(args)); + result = wimax_msg(&i2400m->wimax_dev, NULL, l3l4_hdr, size, + GFP_KERNEL); + if (result < 0) + dev_err(dev, "error sending report to userspace: %d\n", + result); + } else /* an ack to a CMD, GET or SET */ + i2400m_rx_ctl_ack(i2400m, payload, size); +error_check: + return; +} + + + + +/* + * Receive and send up a trace + * + * @i2400m: device descriptor + * @skb_rx: skb that contains the trace (for reference counting) + * @payload: pointer to trace message inside the skb + * @size: size of the message + * + * THe i2400m might produce trace information (diagnostics) and we + * send them through a different kernel-to-user pipe (to avoid + * clogging it). + * + * As in i2400m_rx_ctl(), we can't clone the original skb where the + * data is because we need to send this up via netlink; netlink has to + * add headers and we can't overwrite what's preceeding the + * payload...as it is another message. So we just dup them. + */ +static +void i2400m_rx_trace(struct i2400m *i2400m, + const void *payload, size_t size) +{ + int result; + struct device *dev = i2400m_dev(i2400m); + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + const struct i2400m_l3l4_hdr *l3l4_hdr = payload; + unsigned msg_type; + + result = i2400m_msg_size_check(i2400m, l3l4_hdr, size); + if (result < 0) { + dev_err(dev, "HW BUG? device sent a bad trace message: %d\n", + result); + goto error_check; + } + msg_type = le16_to_cpu(l3l4_hdr->type); + d_printf(1, dev, "Trace %s 0x%04x: %zu bytes\n", + msg_type & I2400M_MT_REPORT_MASK ? "REPORT" : "CMD/SET/GET", + msg_type, size); + d_dump(2, dev, l3l4_hdr, size); + if (unlikely(i2400m->ready == 0)) /* only send if up */ + return; + result = wimax_msg(wimax_dev, "trace", l3l4_hdr, size, GFP_KERNEL); + if (result < 0) + dev_err(dev, "error sending trace to userspace: %d\n", + result); +error_check: + return; +} + + +/* + * Act on a received payload + * + * @i2400m: device instance + * @skb_rx: skb where the transaction was received + * @single: 1 if there is only one payload, 0 otherwise + * @pld: payload descriptor + * @payload: payload data + * + * Upon reception of a payload, look at its guts in the payload + * descriptor and decide what to do with it. + */ +static +void i2400m_rx_payload(struct i2400m *i2400m, struct sk_buff *skb_rx, + unsigned single, const struct i2400m_pld *pld, + const void *payload) +{ + struct device *dev = i2400m_dev(i2400m); + size_t pl_size = i2400m_pld_size(pld); + enum i2400m_pt pl_type = i2400m_pld_type(pld); + + switch (pl_type) { + case I2400M_PT_DATA: + d_printf(3, dev, "RX: data payload %zu bytes\n", pl_size); + i2400m_net_rx(i2400m, skb_rx, single, payload, pl_size); + break; + case I2400M_PT_CTRL: + i2400m_rx_ctl(i2400m, skb_rx, payload, pl_size); + break; + case I2400M_PT_TRACE: + i2400m_rx_trace(i2400m, payload, pl_size); + break; + default: /* Anything else shouldn't come to the host */ + if (printk_ratelimit()) + dev_err(dev, "RX: HW BUG? unexpected payload type %u\n", + pl_type); + } +} + + +/* + * Check a received transaction's message header + * + * @i2400m: device descriptor + * @msg_hdr: message header + * @buf_size: size of the received buffer + * + * Check that the declarations done by a RX buffer message header are + * sane and consistent with the amount of data that was received. + */ +static +int i2400m_rx_msg_hdr_check(struct i2400m *i2400m, + const struct i2400m_msg_hdr *msg_hdr, + size_t buf_size) +{ + int result = -EIO; + struct device *dev = i2400m_dev(i2400m); + if (buf_size < sizeof(*msg_hdr)) { + dev_err(dev, "RX: HW BUG? message with short header (%zu " + "vs %zu bytes expected)\n", buf_size, sizeof(*msg_hdr)); + goto error; + } + if (msg_hdr->barker != cpu_to_le32(I2400M_D2H_MSG_BARKER)) { + dev_err(dev, "RX: HW BUG? message received with unknown " + "barker 0x%08x (buf_size %zu bytes)\n", + le32_to_cpu(msg_hdr->barker), buf_size); + goto error; + } + if (msg_hdr->num_pls == 0) { + dev_err(dev, "RX: HW BUG? zero payload packets in message\n"); + goto error; + } + if (le16_to_cpu(msg_hdr->num_pls) > I2400M_MAX_PLS_IN_MSG) { + dev_err(dev, "RX: HW BUG? message contains more payload " + "than maximum; ignoring.\n"); + goto error; + } + result = 0; +error: + return result; +} + + +/* + * Check a payload descriptor against the received data + * + * @i2400m: device descriptor + * @pld: payload descriptor + * @pl_itr: offset (in bytes) in the received buffer the payload is + * located + * @buf_size: size of the received buffer + * + * Given a payload descriptor (part of a RX buffer), check it is sane + * and that the data it declares fits in the buffer. + */ +static +int i2400m_rx_pl_descr_check(struct i2400m *i2400m, + const struct i2400m_pld *pld, + size_t pl_itr, size_t buf_size) +{ + int result = -EIO; + struct device *dev = i2400m_dev(i2400m); + size_t pl_size = i2400m_pld_size(pld); + enum i2400m_pt pl_type = i2400m_pld_type(pld); + + if (pl_size > i2400m->bus_pl_size_max) { + dev_err(dev, "RX: HW BUG? payload @%zu: size %zu is " + "bigger than maximum %zu; ignoring message\n", + pl_itr, pl_size, i2400m->bus_pl_size_max); + goto error; + } + if (pl_itr + pl_size > buf_size) { /* enough? */ + dev_err(dev, "RX: HW BUG? payload @%zu: size %zu " + "goes beyond the received buffer " + "size (%zu bytes); ignoring message\n", + pl_itr, pl_size, buf_size); + goto error; + } + if (pl_type >= I2400M_PT_ILLEGAL) { + dev_err(dev, "RX: HW BUG? illegal payload type %u; " + "ignoring message\n", pl_type); + goto error; + } + result = 0; +error: + return result; +} + + +/** + * i2400m_rx - Receive a buffer of data from the device + * + * @i2400m: device descriptor + * @skb: skbuff where the data has been received + * + * Parse in a buffer of data that contains an RX message sent from the + * device. See the file header for the format. Run all checks on the + * buffer header, then run over each payload's descriptors, verify + * their consistency and act on each payload's contents. If + * everything is succesful, update the device's statistics. + * + * Note: You need to set the skb to contain only the length of the + * received buffer; for that, use skb_trim(skb, RECEIVED_SIZE). + * + * Returns: + * + * 0 if ok, < 0 errno on error + * + * If ok, this function owns now the skb and the caller DOESN'T have + * to run kfree_skb() on it. However, on error, the caller still owns + * the skb and it is responsible for releasing it. + */ +int i2400m_rx(struct i2400m *i2400m, struct sk_buff *skb) +{ + int i, result; + struct device *dev = i2400m_dev(i2400m); + const struct i2400m_msg_hdr *msg_hdr; + size_t pl_itr, pl_size, skb_len; + unsigned long flags; + unsigned num_pls; + + skb_len = skb->len; + d_fnstart(4, dev, "(i2400m %p skb %p [size %zu])\n", + i2400m, skb, skb_len); + result = -EIO; + msg_hdr = (void *) skb->data; + result = i2400m_rx_msg_hdr_check(i2400m, msg_hdr, skb->len); + if (result < 0) + goto error_msg_hdr_check; + result = -EIO; + num_pls = le16_to_cpu(msg_hdr->num_pls); + pl_itr = sizeof(*msg_hdr) + /* Check payload descriptor(s) */ + num_pls * sizeof(msg_hdr->pld[0]); + pl_itr = ALIGN(pl_itr, I2400M_PL_PAD); + if (pl_itr > skb->len) { /* got all the payload descriptors? */ + dev_err(dev, "RX: HW BUG? message too short (%u bytes) for " + "%u payload descriptors (%zu each, total %zu)\n", + skb->len, num_pls, sizeof(msg_hdr->pld[0]), pl_itr); + goto error_pl_descr_short; + } + /* Walk each payload payload--check we really got it */ + for (i = 0; i < num_pls; i++) { + /* work around old gcc warnings */ + pl_size = i2400m_pld_size(&msg_hdr->pld[i]); + result = i2400m_rx_pl_descr_check(i2400m, &msg_hdr->pld[i], + pl_itr, skb->len); + if (result < 0) + goto error_pl_descr_check; + i2400m_rx_payload(i2400m, skb, num_pls == 1, &msg_hdr->pld[i], + skb->data + pl_itr); + pl_itr += ALIGN(pl_size, I2400M_PL_PAD); + cond_resched(); /* Don't monopolize */ + } + kfree_skb(skb); + /* Update device statistics */ + spin_lock_irqsave(&i2400m->rx_lock, flags); + i2400m->rx_pl_num += i; + if (i > i2400m->rx_pl_max) + i2400m->rx_pl_max = i; + if (i < i2400m->rx_pl_min) + i2400m->rx_pl_min = i; + i2400m->rx_num++; + i2400m->rx_size_acc += skb->len; + if (skb->len < i2400m->rx_size_min) + i2400m->rx_size_min = skb->len; + if (skb->len > i2400m->rx_size_max) + i2400m->rx_size_max = skb->len; + spin_unlock_irqrestore(&i2400m->rx_lock, flags); +error_pl_descr_check: +error_pl_descr_short: +error_msg_hdr_check: + d_fnend(4, dev, "(i2400m %p skb %p [size %zu]) = %d\n", + i2400m, skb, skb_len, result); + return result; +} +EXPORT_SYMBOL_GPL(i2400m_rx); diff --git a/drivers/net/wimax/i2400m/sdio-debug-levels.h b/drivers/net/wimax/i2400m/sdio-debug-levels.h new file mode 100644 index 000000000000..c51998741301 --- /dev/null +++ b/drivers/net/wimax/i2400m/sdio-debug-levels.h @@ -0,0 +1,22 @@ +/* + * debug levels control file for the i2400m module's + */ +#ifndef __debug_levels__h__ +#define __debug_levels__h__ + +/* Maximum compile and run time debug level for all submodules */ +#define D_MODULENAME i2400m_sdio +#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL + +#include <linux/wimax/debug.h> + +/* List of all the enabled modules */ +enum d_module { + D_SUBMODULE_DECLARE(main), + D_SUBMODULE_DECLARE(tx), + D_SUBMODULE_DECLARE(rx), + D_SUBMODULE_DECLARE(fw) +}; + + +#endif /* #ifndef __debug_levels__h__ */ diff --git a/drivers/net/wimax/i2400m/sdio-fw.c b/drivers/net/wimax/i2400m/sdio-fw.c new file mode 100644 index 000000000000..3487205d8f50 --- /dev/null +++ b/drivers/net/wimax/i2400m/sdio-fw.c @@ -0,0 +1,224 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Firmware uploader's SDIO specifics + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Initial implementation + * + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Bus generic/specific split for USB + * + * Dirk Brandewie <dirk.j.brandewie@intel.com> + * - Initial implementation for SDIO + * + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - SDIO rehash for changes in the bus-driver model + * + * THE PROCEDURE + * + * See fw.c for the generic description of this procedure. + * + * This file implements only the SDIO specifics. It boils down to how + * to send a command and waiting for an acknowledgement from the + * device. We do polled reads. + * + * COMMAND EXECUTION + * + * THe generic firmware upload code will call i2400m_bus_bm_cmd_send() + * to send commands. + * + * The SDIO devices expects things in 256 byte blocks, so it will pad + * it, compute the checksum (if needed) and pass it to SDIO. + * + * ACK RECEPTION + * + * This works in polling mode -- the fw loader says when to wait for + * data and for that it calls i2400ms_bus_bm_wait_for_ack(). + * + * This will poll the device for data until it is received. We need to + * receive at least as much bytes as where asked for (although it'll + * always be a multiple of 256 bytes). + */ +#include <linux/mmc/sdio_func.h> +#include "i2400m-sdio.h" + + +#define D_SUBMODULE fw +#include "sdio-debug-levels.h" + +/* + * Send a boot-mode command to the SDIO function + * + * We use a bounce buffer (i2400m->bm_cmd_buf) because we need to + * touch the header if the RAW flag is not set. + * + * @flags: pass thru from i2400m_bm_cmd() + * @return: cmd_size if ok, < 0 errno code on error. + * + * Note the command is padded to the SDIO block size for the device. + */ +ssize_t i2400ms_bus_bm_cmd_send(struct i2400m *i2400m, + const struct i2400m_bootrom_header *_cmd, + size_t cmd_size, int flags) +{ + ssize_t result; + struct device *dev = i2400m_dev(i2400m); + struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m); + int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd); + struct i2400m_bootrom_header *cmd; + /* SDIO restriction */ + size_t cmd_size_a = ALIGN(cmd_size, I2400MS_BLK_SIZE); + + d_fnstart(5, dev, "(i2400m %p cmd %p size %zu)\n", + i2400m, _cmd, cmd_size); + result = -E2BIG; + if (cmd_size > I2400M_BM_CMD_BUF_SIZE) + goto error_too_big; + + memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size); /* Prep command */ + cmd = i2400m->bm_cmd_buf; + if (cmd_size_a > cmd_size) /* Zero pad space */ + memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size); + if ((flags & I2400M_BM_CMD_RAW) == 0) { + if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0)) + dev_warn(dev, "SW BUG: response_required == 0\n"); + i2400m_bm_cmd_prepare(cmd); + } + d_printf(4, dev, "BM cmd %d: %zu bytes (%zu padded)\n", + opcode, cmd_size, cmd_size_a); + d_dump(5, dev, cmd, cmd_size); + + sdio_claim_host(i2400ms->func); /* Send & check */ + result = sdio_memcpy_toio(i2400ms->func, I2400MS_DATA_ADDR, + i2400m->bm_cmd_buf, cmd_size_a); + sdio_release_host(i2400ms->func); + if (result < 0) { + dev_err(dev, "BM cmd %d: cannot send: %ld\n", + opcode, (long) result); + goto error_cmd_send; + } + result = cmd_size; +error_cmd_send: +error_too_big: + d_fnend(5, dev, "(i2400m %p cmd %p size %zu) = %d\n", + i2400m, _cmd, cmd_size, (int) result); + return result; +} + + +/* + * Read an ack from the device's boot-mode (polling) + * + * @i2400m: + * @_ack: pointer to where to store the read data + * @ack_size: how many bytes we should read + * + * Returns: < 0 errno code on error; otherwise, amount of received bytes. + * + * The ACK for a BM command is always at least sizeof(*ack) bytes, so + * check for that. We don't need to check for device reboots + * + * NOTE: We do an artificial timeout of 1 sec over the SDIO timeout; + * this way we have control over it...there is no way that I know + * of setting an SDIO transaction timeout. + */ +ssize_t i2400ms_bus_bm_wait_for_ack(struct i2400m *i2400m, + struct i2400m_bootrom_header *ack, + size_t ack_size) +{ + int result; + ssize_t rx_size; + u64 timeout; + struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m); + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + + BUG_ON(sizeof(*ack) > ack_size); + + d_fnstart(5, dev, "(i2400m %p ack %p size %zu)\n", + i2400m, ack, ack_size); + + timeout = get_jiffies_64() + 2 * HZ; + sdio_claim_host(func); + while (1) { + if (time_after64(get_jiffies_64(), timeout)) { + rx_size = -ETIMEDOUT; + dev_err(dev, "timeout waiting for ack data\n"); + goto error_timedout; + } + + /* Find the RX size, check if it fits or not -- it if + * doesn't fit, fail, as we have no way to dispose of + * the extra data. */ + rx_size = __i2400ms_rx_get_size(i2400ms); + if (rx_size < 0) + goto error_rx_get_size; + result = -ENOSPC; /* Check it fits */ + if (rx_size < sizeof(*ack)) { + rx_size = -EIO; + dev_err(dev, "HW BUG? received is too small (%zu vs " + "%zu needed)\n", sizeof(*ack), rx_size); + goto error_too_small; + } + if (rx_size > I2400M_BM_ACK_BUF_SIZE) { + dev_err(dev, "SW BUG? BM_ACK_BUF is too small (%u vs " + "%zu needed)\n", I2400M_BM_ACK_BUF_SIZE, + rx_size); + goto error_too_small; + } + + /* Read it */ + result = sdio_memcpy_fromio(func, i2400m->bm_ack_buf, + I2400MS_DATA_ADDR, rx_size); + if (result == -ETIMEDOUT || result == -ETIME) + continue; + if (result < 0) { + dev_err(dev, "BM SDIO receive (%zu B) failed: %d\n", + rx_size, result); + goto error_read; + } else + break; + } + rx_size = min((ssize_t)ack_size, rx_size); + memcpy(ack, i2400m->bm_ack_buf, rx_size); +error_read: +error_too_small: +error_rx_get_size: +error_timedout: + sdio_release_host(func); + d_fnend(5, dev, "(i2400m %p ack %p size %zu) = %ld\n", + i2400m, ack, ack_size, (long) rx_size); + return rx_size; +} diff --git a/drivers/net/wimax/i2400m/sdio-rx.c b/drivers/net/wimax/i2400m/sdio-rx.c new file mode 100644 index 000000000000..a3008b904f7d --- /dev/null +++ b/drivers/net/wimax/i2400m/sdio-rx.c @@ -0,0 +1,255 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * SDIO RX handling + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Dirk Brandewie <dirk.j.brandewie@intel.com> + * - Initial implementation + * + * + * This handles the RX path on SDIO. + * + * The SDIO bus driver calls the "irq" routine when data is available. + * This is not a traditional interrupt routine since the SDIO bus + * driver calls us from its irq thread context. Because of this + * sleeping in the SDIO RX IRQ routine is okay. + * + * From there on, we obtain the size of the data that is available, + * allocate an skb, copy it and then pass it to the generic driver's + * RX routine [i2400m_rx()]. + * + * ROADMAP + * + * i2400ms_irq() + * i2400ms_rx() + * __i2400ms_rx_get_size() + * i2400m_rx() + * + * i2400ms_rx_setup() + * + * i2400ms_rx_release() + */ +#include <linux/workqueue.h> +#include <linux/wait.h> +#include <linux/skbuff.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> +#include "i2400m-sdio.h" + +#define D_SUBMODULE rx +#include "sdio-debug-levels.h" + + +/* + * Read and return the amount of bytes available for RX + * + * The RX size has to be read like this: byte reads of three + * sequential locations; then glue'em together. + * + * sdio_readl() doesn't work. + */ +ssize_t __i2400ms_rx_get_size(struct i2400ms *i2400ms) +{ + int ret, cnt, val; + ssize_t rx_size; + unsigned xfer_size_addr; + struct sdio_func *func = i2400ms->func; + struct device *dev = &i2400ms->func->dev; + + d_fnstart(7, dev, "(i2400ms %p)\n", i2400ms); + xfer_size_addr = I2400MS_INTR_GET_SIZE_ADDR; + rx_size = 0; + for (cnt = 0; cnt < 3; cnt++) { + val = sdio_readb(func, xfer_size_addr + cnt, &ret); + if (ret < 0) { + dev_err(dev, "RX: Can't read byte %d of RX size from " + "0x%08x: %d\n", cnt, xfer_size_addr + cnt, ret); + rx_size = ret; + goto error_read; + } + rx_size = rx_size << 8 | (val & 0xff); + } + d_printf(6, dev, "RX: rx_size is %ld\n", (long) rx_size); +error_read: + d_fnend(7, dev, "(i2400ms %p) = %ld\n", i2400ms, (long) rx_size); + return rx_size; +} + + +/* + * Read data from the device (when in normal) + * + * Allocate an SKB of the right size, read the data in and then + * deliver it to the generic layer. + * + * We also check for a reboot barker. That means the device died and + * we have to reboot it. + */ +static +void i2400ms_rx(struct i2400ms *i2400ms) +{ + int ret; + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + struct i2400m *i2400m = &i2400ms->i2400m; + struct sk_buff *skb; + ssize_t rx_size; + + d_fnstart(7, dev, "(i2400ms %p)\n", i2400ms); + rx_size = __i2400ms_rx_get_size(i2400ms); + if (rx_size < 0) { + ret = rx_size; + goto error_get_size; + } + ret = -ENOMEM; + skb = alloc_skb(rx_size, GFP_ATOMIC); + if (NULL == skb) { + dev_err(dev, "RX: unable to alloc skb\n"); + goto error_alloc_skb; + } + + ret = sdio_memcpy_fromio(func, skb->data, + I2400MS_DATA_ADDR, rx_size); + if (ret < 0) { + dev_err(dev, "RX: SDIO data read failed: %d\n", ret); + goto error_memcpy_fromio; + } + /* Check if device has reset */ + if (!memcmp(skb->data, i2400m_NBOOT_BARKER, + sizeof(i2400m_NBOOT_BARKER)) + || !memcmp(skb->data, i2400m_SBOOT_BARKER, + sizeof(i2400m_SBOOT_BARKER))) { + ret = i2400m_dev_reset_handle(i2400m); + kfree_skb(skb); + } else { + skb_put(skb, rx_size); + i2400m_rx(i2400m, skb); + } + d_fnend(7, dev, "(i2400ms %p) = void\n", i2400ms); + return; + +error_memcpy_fromio: + kfree_skb(skb); +error_alloc_skb: +error_get_size: + d_fnend(7, dev, "(i2400ms %p) = %d\n", i2400ms, ret); + return; +} + + +/* + * Process an interrupt from the SDIO card + * + * FIXME: need to process other events that are not just ready-to-read + * + * Checks there is data ready and then proceeds to read it. + */ +static +void i2400ms_irq(struct sdio_func *func) +{ + int ret; + struct i2400ms *i2400ms = sdio_get_drvdata(func); + struct i2400m *i2400m = &i2400ms->i2400m; + struct device *dev = &func->dev; + int val; + + d_fnstart(6, dev, "(i2400ms %p)\n", i2400ms); + val = sdio_readb(func, I2400MS_INTR_STATUS_ADDR, &ret); + if (ret < 0) { + dev_err(dev, "RX: Can't read interrupt status: %d\n", ret); + goto error_no_irq; + } + if (!val) { + dev_err(dev, "RX: BUG? got IRQ but no interrupt ready?\n"); + goto error_no_irq; + } + sdio_writeb(func, 1, I2400MS_INTR_CLEAR_ADDR, &ret); + if (WARN_ON(i2400m->boot_mode != 0)) + dev_err(dev, "RX: SW BUG? boot mode and IRQ is up?\n"); + else + i2400ms_rx(i2400ms); +error_no_irq: + d_fnend(6, dev, "(i2400ms %p) = void\n", i2400ms); + return; +} + + +/* + * Setup SDIO RX + * + * Hooks up the IRQ handler and then enables IRQs. + */ +int i2400ms_rx_setup(struct i2400ms *i2400ms) +{ + int result; + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + + d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms); + sdio_claim_host(func); + result = sdio_claim_irq(func, i2400ms_irq); + if (result < 0) { + dev_err(dev, "Cannot claim IRQ: %d\n", result); + goto error_irq_claim; + } + result = 0; + sdio_writeb(func, 1, I2400MS_INTR_ENABLE_ADDR, &result); + if (result < 0) { + sdio_release_irq(func); + dev_err(dev, "Failed to enable interrupts %d\n", result); + } +error_irq_claim: + sdio_release_host(func); + d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result); + return result; +} + + +/* + * Tear down SDIO RX + * + * Disables IRQs in the device and removes the IRQ handler. + */ +void i2400ms_rx_release(struct i2400ms *i2400ms) +{ + int result; + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + + d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms); + sdio_claim_host(func); + sdio_writeb(func, 0, I2400MS_INTR_ENABLE_ADDR, &result); + sdio_release_irq(func); + sdio_release_host(func); + d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result); +} diff --git a/drivers/net/wimax/i2400m/sdio-tx.c b/drivers/net/wimax/i2400m/sdio-tx.c new file mode 100644 index 000000000000..5105a5ebc44f --- /dev/null +++ b/drivers/net/wimax/i2400m/sdio-tx.c @@ -0,0 +1,153 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * SDIO TX transaction backends + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Dirk Brandewie <dirk.j.brandewie@intel.com> + * - Initial implementation + * + * + * Takes the TX messages in the i2400m's driver TX FIFO and sends them + * to the device until there are no more. + * + * If we fail sending the message, we just drop it. There isn't much + * we can do at this point. Most of the traffic is network, which has + * recovery methods for dropped packets. + * + * The SDIO functions are not atomic, so we can't run from the context + * where i2400m->bus_tx_kick() [i2400ms_bus_tx_kick()] is being called + * (some times atomic). Thus, the actual TX work is deferred to a + * workqueue. + * + * ROADMAP + * + * i2400ms_bus_tx_kick() + * i2400ms_tx_submit() [through workqueue] + * + * i2400m_tx_setup() + * + * i2400m_tx_release() + */ +#include <linux/mmc/sdio_func.h> +#include "i2400m-sdio.h" + +#define D_SUBMODULE tx +#include "sdio-debug-levels.h" + + +/* + * Pull TX transations from the TX FIFO and send them to the device + * until there are no more. + */ +static +void i2400ms_tx_submit(struct work_struct *ws) +{ + int result; + struct i2400ms *i2400ms = container_of(ws, struct i2400ms, tx_worker); + struct i2400m *i2400m = &i2400ms->i2400m; + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + struct i2400m_msg_hdr *tx_msg; + size_t tx_msg_size; + + d_fnstart(4, dev, "(i2400ms %p, i2400m %p)\n", i2400ms, i2400ms); + + while (NULL != (tx_msg = i2400m_tx_msg_get(i2400m, &tx_msg_size))) { + d_printf(2, dev, "TX: submitting %zu bytes\n", tx_msg_size); + d_dump(5, dev, tx_msg, tx_msg_size); + + sdio_claim_host(func); + result = sdio_memcpy_toio(func, 0, tx_msg, tx_msg_size); + sdio_release_host(func); + + i2400m_tx_msg_sent(i2400m); + + if (result < 0) { + dev_err(dev, "TX: cannot submit TX; tx_msg @%zu %zu B:" + " %d\n", (void *) tx_msg - i2400m->tx_buf, + tx_msg_size, result); + } + + d_printf(2, dev, "TX: %zub submitted\n", tx_msg_size); + } + + d_fnend(4, dev, "(i2400ms %p) = void\n", i2400ms); +} + + +/* + * The generic driver notifies us that there is data ready for TX + * + * Schedule a run of i2400ms_tx_submit() to handle it. + */ +void i2400ms_bus_tx_kick(struct i2400m *i2400m) +{ + struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m); + struct device *dev = &i2400ms->func->dev; + + d_fnstart(3, dev, "(i2400m %p) = void\n", i2400m); + + /* schedule tx work, this is because tx may block, therefore + * it has to run in a thread context. + */ + queue_work(i2400ms->tx_workqueue, &i2400ms->tx_worker); + + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); +} + +int i2400ms_tx_setup(struct i2400ms *i2400ms) +{ + int result; + struct device *dev = &i2400ms->func->dev; + struct i2400m *i2400m = &i2400ms->i2400m; + + d_fnstart(5, dev, "(i2400ms %p)\n", i2400ms); + + INIT_WORK(&i2400ms->tx_worker, i2400ms_tx_submit); + snprintf(i2400ms->tx_wq_name, sizeof(i2400ms->tx_wq_name), + "%s-tx", i2400m->wimax_dev.name); + i2400ms->tx_workqueue = + create_singlethread_workqueue(i2400ms->tx_wq_name); + if (NULL == i2400ms->tx_workqueue) { + dev_err(dev, "TX: failed to create workqueue\n"); + result = -ENOMEM; + } else + result = 0; + d_fnend(5, dev, "(i2400ms %p) = %d\n", i2400ms, result); + return result; +} + +void i2400ms_tx_release(struct i2400ms *i2400ms) +{ + destroy_workqueue(i2400ms->tx_workqueue); +} diff --git a/drivers/net/wimax/i2400m/sdio.c b/drivers/net/wimax/i2400m/sdio.c new file mode 100644 index 000000000000..1bfa283bbd8a --- /dev/null +++ b/drivers/net/wimax/i2400m/sdio.c @@ -0,0 +1,511 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Linux driver model glue for the SDIO device, reset & fw upload + * + * + * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> + * Dirk Brandewie <dirk.j.brandewie@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.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. + * + * 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. + * + * + * See i2400m-sdio.h for a general description of this driver. + * + * This file implements driver model glue, and hook ups for the + * generic driver to implement the bus-specific functions (device + * communication setup/tear down, firmware upload and resetting). + * + * ROADMAP + * + * i2400m_probe() + * alloc_netdev() + * i2400ms_netdev_setup() + * i2400ms_init() + * i2400m_netdev_setup() + * i2400ms_enable_function() + * i2400m_setup() + * + * i2400m_remove() + * i2400m_release() + * free_netdev(net_dev) + * + * i2400ms_bus_reset() Called by i2400m->bus_reset + * __i2400ms_reset() + * __i2400ms_send_barker() + * + * i2400ms_bus_dev_start() Called by i2400m_dev_start() [who is + * i2400ms_tx_setup() called by i2400m_setup()] + * i2400ms_rx_setup() + * + * i2400ms_bus_dev_stop() Called by i2400m_dev_stop() [who is + * i2400ms_rx_release() is called by i2400m_release()] + * i2400ms_tx_release() + * + */ + +#include <linux/debugfs.h> +#include <linux/mmc/sdio.h> +#include <linux/mmc/sdio_func.h> +#include "i2400m-sdio.h" +#include <linux/wimax/i2400m.h> + +#define D_SUBMODULE main +#include "sdio-debug-levels.h" + +/* IOE WiMAX function timeout in seconds */ +static int ioe_timeout = 2; +module_param(ioe_timeout, int, 0); + +/* Our firmware file name */ +#define I2400MS_FW_FILE_NAME "i2400m-fw-sdio-" I2400M_FW_VERSION ".sbcf" + +/* + * Enable the SDIO function + * + * Tries to enable the SDIO function; might fail if it is still not + * ready (in some hardware, the SDIO WiMAX function is only enabled + * when we ask it to explicitly doing). Tries until a timeout is + * reached. + * + * The reverse of this is...sdio_disable_function() + * + * Returns: 0 if the SDIO function was enabled, < 0 errno code on + * error (-ENODEV when it was unable to enable the function). + */ +static +int i2400ms_enable_function(struct sdio_func *func) +{ + u64 timeout; + int err; + struct device *dev = &func->dev; + + d_fnstart(3, dev, "(func %p)\n", func); + /* Setup timeout (FIXME: This needs to read the CIS table to + * get a real timeout) and then wait for the device to signal + * it is ready */ + timeout = get_jiffies_64() + ioe_timeout * HZ; + err = -ENODEV; + while (err != 0 && time_before64(get_jiffies_64(), timeout)) { + sdio_claim_host(func); + err = sdio_enable_func(func); + if (0 == err) { + sdio_release_host(func); + d_printf(2, dev, "SDIO function enabled\n"); + goto function_enabled; + } + d_printf(2, dev, "SDIO function failed to enable: %d\n", err); + sdio_disable_func(func); + sdio_release_host(func); + msleep(I2400MS_INIT_SLEEP_INTERVAL); + } + /* If timed out, device is not there yet -- get -ENODEV so + * the device driver core will retry later on. */ + if (err == -ETIME) { + dev_err(dev, "Can't enable WiMAX function; " + " has the function been enabled?\n"); + err = -ENODEV; + } +function_enabled: + d_fnend(3, dev, "(func %p) = %d\n", func, err); + return err; +} + + +/* + * Setup driver resources needed to communicate with the device + * + * The fw needs some time to settle, and it was just uploaded, + * so give it a break first. I'd prefer to just wait for the device to + * send something, but seems the poking we do to enable SDIO stuff + * interferes with it, so just give it a break before starting... + */ +static +int i2400ms_bus_dev_start(struct i2400m *i2400m) +{ + int result; + struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m); + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + msleep(200); + result = i2400ms_rx_setup(i2400ms); + if (result < 0) + goto error_rx_setup; + result = i2400ms_tx_setup(i2400ms); + if (result < 0) + goto error_tx_setup; + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; + + i2400ms_tx_release(i2400ms); +error_tx_setup: + i2400ms_rx_release(i2400ms); +error_rx_setup: + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); + return result; +} + + +static +void i2400ms_bus_dev_stop(struct i2400m *i2400m) +{ + struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m); + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + i2400ms_rx_release(i2400ms); + i2400ms_tx_release(i2400ms); + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); +} + + +/* + * Sends a barker buffer to the device + * + * This helper will allocate a kmalloced buffer and use it to transmit + * (then free it). Reason for this is that the SDIO host controller + * expects alignment (unknown exactly which) which the stack won't + * really provide and certain arches/host-controller combinations + * cannot use stack/vmalloc/text areas for DMA transfers. + */ +static +int __i2400ms_send_barker(struct i2400ms *i2400ms, + const __le32 *barker, size_t barker_size) +{ + int ret; + struct sdio_func *func = i2400ms->func; + struct device *dev = &func->dev; + void *buffer; + + ret = -ENOMEM; + buffer = kmalloc(I2400MS_BLK_SIZE, GFP_KERNEL); + if (buffer == NULL) + goto error_kzalloc; + + memcpy(buffer, barker, barker_size); + sdio_claim_host(func); + ret = sdio_memcpy_toio(func, 0, buffer, I2400MS_BLK_SIZE); + sdio_release_host(func); + + if (ret < 0) + d_printf(0, dev, "E: barker error: %d\n", ret); + + kfree(buffer); +error_kzalloc: + return ret; +} + + +/* + * Reset a device at different levels (warm, cold or bus) + * + * @i2400ms: device descriptor + * @reset_type: soft, warm or bus reset (I2400M_RT_WARM/SOFT/BUS) + * + * FIXME: not tested -- need to confirm expected effects + * + * Warm and cold resets get an SDIO reset if they fail (unimplemented) + * + * Warm reset: + * + * The device will be fully reset internally, but won't be + * disconnected from the USB bus (so no reenumeration will + * happen). Firmware upload will be neccessary. + * + * The device will send a reboot barker in the notification endpoint + * that will trigger the driver to reinitialize the state + * automatically from notif.c:i2400m_notification_grok() into + * i2400m_dev_bootstrap_delayed(). + * + * Cold and bus (USB) reset: + * + * The device will be fully reset internally, disconnected from the + * USB bus an a reenumeration will happen. Firmware upload will be + * neccessary. Thus, we don't do any locking or struct + * reinitialization, as we are going to be fully disconnected and + * reenumerated. + * + * Note we need to return -ENODEV if a warm reset was requested and we + * had to resort to a bus reset. See i2400m_op_reset(), wimax_reset() + * and wimax_dev->op_reset. + * + * WARNING: no driver state saved/fixed + */ +static +int i2400ms_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) +{ + int result; + struct i2400ms *i2400ms = + container_of(i2400m, struct i2400ms, i2400m); + struct device *dev = i2400m_dev(i2400m); + static const __le32 i2400m_WARM_BOOT_BARKER[4] = { + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + }; + static const __le32 i2400m_COLD_BOOT_BARKER[4] = { + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + }; + + if (rt == I2400M_RT_WARM) + result = __i2400ms_send_barker(i2400ms, i2400m_WARM_BOOT_BARKER, + sizeof(i2400m_WARM_BOOT_BARKER)); + else if (rt == I2400M_RT_COLD) + result = __i2400ms_send_barker(i2400ms, i2400m_COLD_BOOT_BARKER, + sizeof(i2400m_COLD_BOOT_BARKER)); + else if (rt == I2400M_RT_BUS) { +do_bus_reset: + dev_err(dev, "FIXME: SDIO bus reset not implemented\n"); + result = rt == I2400M_RT_WARM ? -ENODEV : -ENOSYS; + } else + BUG(); + if (result < 0 && rt != I2400M_RT_BUS) { + dev_err(dev, "%s reset failed (%d); trying SDIO reset\n", + rt == I2400M_RT_WARM ? "warm" : "cold", result); + rt = I2400M_RT_BUS; + goto do_bus_reset; + } + return result; +} + + +static +void i2400ms_netdev_setup(struct net_device *net_dev) +{ + struct i2400m *i2400m = net_dev_to_i2400m(net_dev); + struct i2400ms *i2400ms = container_of(i2400m, struct i2400ms, i2400m); + i2400ms_init(i2400ms); + i2400m_netdev_setup(net_dev); +} + + +/* + * Debug levels control; see debug.h + */ +struct d_level D_LEVEL[] = { + D_SUBMODULE_DEFINE(main), + D_SUBMODULE_DEFINE(tx), + D_SUBMODULE_DEFINE(rx), + D_SUBMODULE_DEFINE(fw), +}; +size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL); + + +#define __debugfs_register(prefix, name, parent) \ +do { \ + result = d_level_register_debugfs(prefix, name, parent); \ + if (result < 0) \ + goto error; \ +} while (0) + + +static +int i2400ms_debugfs_add(struct i2400ms *i2400ms) +{ + int result; + struct dentry *dentry = i2400ms->i2400m.wimax_dev.debugfs_dentry; + + dentry = debugfs_create_dir("i2400m-usb", dentry); + result = PTR_ERR(dentry); + if (IS_ERR(dentry)) { + if (result == -ENODEV) + result = 0; /* No debugfs support */ + goto error; + } + i2400ms->debugfs_dentry = dentry; + __debugfs_register("dl_", main, dentry); + __debugfs_register("dl_", tx, dentry); + __debugfs_register("dl_", rx, dentry); + __debugfs_register("dl_", fw, dentry); + + return 0; + +error: + debugfs_remove_recursive(i2400ms->debugfs_dentry); + return result; +} + + +/* + * Probe a i2400m interface and register it + * + * @func: SDIO function + * @id: SDIO device ID + * @returns: 0 if ok, < 0 errno code on error. + * + * Alloc a net device, initialize the bus-specific details and then + * calls the bus-generic initialization routine. That will register + * the wimax and netdev devices, upload the firmware [using + * _bus_bm_*()], call _bus_dev_start() to finalize the setup of the + * communication with the device and then will start to talk to it to + * finnish setting it up. + * + * Initialization is tricky; some instances of the hw are packed with + * others in a way that requires a third driver that enables the WiMAX + * function. In those cases, we can't enable the SDIO function and + * we'll return with -ENODEV. When the driver that enables the WiMAX + * function does its thing, it has to do a bus_rescan_devices() on the + * SDIO bus so this driver is called again to enumerate the WiMAX + * function. + */ +static +int i2400ms_probe(struct sdio_func *func, + const struct sdio_device_id *id) +{ + int result; + struct net_device *net_dev; + struct device *dev = &func->dev; + struct i2400m *i2400m; + struct i2400ms *i2400ms; + + /* Allocate instance [calls i2400m_netdev_setup() on it]. */ + result = -ENOMEM; + net_dev = alloc_netdev(sizeof(*i2400ms), "wmx%d", + i2400ms_netdev_setup); + if (net_dev == NULL) { + dev_err(dev, "no memory for network device instance\n"); + goto error_alloc_netdev; + } + SET_NETDEV_DEV(net_dev, dev); + i2400m = net_dev_to_i2400m(net_dev); + i2400ms = container_of(i2400m, struct i2400ms, i2400m); + i2400m->wimax_dev.net_dev = net_dev; + i2400ms->func = func; + sdio_set_drvdata(func, i2400ms); + + i2400m->bus_tx_block_size = I2400MS_BLK_SIZE; + i2400m->bus_pl_size_max = I2400MS_PL_SIZE_MAX; + i2400m->bus_dev_start = i2400ms_bus_dev_start; + i2400m->bus_dev_stop = i2400ms_bus_dev_stop; + i2400m->bus_tx_kick = i2400ms_bus_tx_kick; + i2400m->bus_reset = i2400ms_bus_reset; + i2400m->bus_bm_cmd_send = i2400ms_bus_bm_cmd_send; + i2400m->bus_bm_wait_for_ack = i2400ms_bus_bm_wait_for_ack; + i2400m->bus_fw_name = I2400MS_FW_FILE_NAME; + i2400m->bus_bm_mac_addr_impaired = 1; + + result = i2400ms_enable_function(i2400ms->func); + if (result < 0) { + dev_err(dev, "Cannot enable SDIO function: %d\n", result); + goto error_func_enable; + } + + sdio_claim_host(func); + result = sdio_set_block_size(func, I2400MS_BLK_SIZE); + if (result < 0) { + dev_err(dev, "Failed to set block size: %d\n", result); + goto error_set_blk_size; + } + sdio_release_host(func); + + result = i2400m_setup(i2400m, I2400M_BRI_NO_REBOOT); + if (result < 0) { + dev_err(dev, "cannot setup device: %d\n", result); + goto error_setup; + } + + result = i2400ms_debugfs_add(i2400ms); + if (result < 0) { + dev_err(dev, "cannot create SDIO debugfs: %d\n", + result); + goto error_debugfs_add; + } + return 0; + +error_debugfs_add: + i2400m_release(i2400m); +error_setup: + sdio_set_drvdata(func, NULL); + sdio_claim_host(func); +error_set_blk_size: + sdio_disable_func(func); + sdio_release_host(func); +error_func_enable: + free_netdev(net_dev); +error_alloc_netdev: + return result; +} + + +static +void i2400ms_remove(struct sdio_func *func) +{ + struct device *dev = &func->dev; + struct i2400ms *i2400ms = sdio_get_drvdata(func); + struct i2400m *i2400m = &i2400ms->i2400m; + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + + d_fnstart(3, dev, "SDIO func %p\n", func); + debugfs_remove_recursive(i2400ms->debugfs_dentry); + i2400m_release(i2400m); + sdio_set_drvdata(func, NULL); + sdio_claim_host(func); + sdio_disable_func(func); + sdio_release_host(func); + free_netdev(net_dev); + d_fnend(3, dev, "SDIO func %p\n", func); +} + +enum { + I2400MS_INTEL_VID = 0x89, +}; + +static +const struct sdio_device_id i2400ms_sdio_ids[] = { + /* Intel: i2400m WiMAX over SDIO */ + { SDIO_DEVICE(I2400MS_INTEL_VID, 0x1402) }, + { }, /* end: all zeroes */ +}; +MODULE_DEVICE_TABLE(sdio, i2400ms_sdio_ids); + + +static +struct sdio_driver i2400m_sdio_driver = { + .name = KBUILD_MODNAME, + .probe = i2400ms_probe, + .remove = i2400ms_remove, + .id_table = i2400ms_sdio_ids, +}; + + +static +int __init i2400ms_driver_init(void) +{ + return sdio_register_driver(&i2400m_sdio_driver); +} +module_init(i2400ms_driver_init); + + +static +void __exit i2400ms_driver_exit(void) +{ + flush_scheduled_work(); /* for the stuff we schedule */ + sdio_unregister_driver(&i2400m_sdio_driver); +} +module_exit(i2400ms_driver_exit); + + +MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>"); +MODULE_DESCRIPTION("Intel 2400M WiMAX networking for SDIO"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(I2400MS_FW_FILE_NAME); diff --git a/drivers/net/wimax/i2400m/tx.c b/drivers/net/wimax/i2400m/tx.c new file mode 100644 index 000000000000..613a88ffd651 --- /dev/null +++ b/drivers/net/wimax/i2400m/tx.c @@ -0,0 +1,817 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Generic (non-bus specific) TX handling + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * - Initial implementation + * + * Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Rewritten to use a single FIFO to lower the memory allocation + * pressure and optimize cache hits when copying to the queue, as + * well as splitting out bus-specific code. + * + * + * Implements data transmission to the device; this is done through a + * software FIFO, as data/control frames can be coalesced (while the + * device is reading the previous tx transaction, others accumulate). + * + * A FIFO is used because at the end it is resource-cheaper that trying + * to implement scatter/gather over USB. As well, most traffic is going + * to be download (vs upload). + * + * The format for sending/receiving data to/from the i2400m is + * described in detail in rx.c:PROTOCOL FORMAT. In here we implement + * the transmission of that. This is split between a bus-independent + * part that just prepares everything and a bus-specific part that + * does the actual transmission over the bus to the device (in the + * bus-specific driver). + * + * + * The general format of a device-host transaction is MSG-HDR, PLD1, + * PLD2...PLDN, PL1, PL2,...PLN, PADDING. + * + * Because we need the send payload descriptors and then payloads and + * because it is kind of expensive to do scatterlists in USB (one URB + * per node), it becomes cheaper to append all the data to a FIFO + * (copying to a FIFO potentially in cache is cheaper). + * + * Then the bus-specific code takes the parts of that FIFO that are + * written and passes them to the device. + * + * So the concepts to keep in mind there are: + * + * We use a FIFO to queue the data in a linear buffer. We first append + * a MSG-HDR, space for I2400M_TX_PLD_MAX payload descriptors and then + * go appending payloads until we run out of space or of payload + * descriptors. Then we append padding to make the whole transaction a + * multiple of i2400m->bus_tx_block_size (as defined by the bus layer). + * + * - A TX message: a combination of a message header, payload + * descriptors and payloads. + * + * Open: it is marked as active (i2400m->tx_msg is valid) and we + * can keep adding payloads to it. + * + * Closed: we are not appending more payloads to this TX message + * (exahusted space in the queue, too many payloads or + * whichever). We have appended padding so the whole message + * length is aligned to i2400m->bus_tx_block_size (as set by the + * bus/transport layer). + * + * - Most of the time we keep a TX message open to which we append + * payloads. + * + * - If we are going to append and there is no more space (we are at + * the end of the FIFO), we close the message, mark the rest of the + * FIFO space unusable (skip_tail), create a new message at the + * beginning of the FIFO (if there is space) and append the message + * there. + * + * This is because we need to give linear TX messages to the bus + * engine. So we don't write a message to the remaining FIFO space + * until the tail and continue at the head of it. + * + * - We overload one of the fields in the message header to use it as + * 'size' of the TX message, so we can iterate over them. It also + * contains a flag that indicates if we have to skip it or not. + * When we send the buffer, we update that to its real on-the-wire + * value. + * + * - The MSG-HDR PLD1...PLD2 stuff has to be a size multiple of 16. + * + * It follows that if MSG-HDR says we have N messages, the whole + * header + descriptors is 16 + 4*N; for those to be a multiple of + * 16, it follows that N can be 4, 8, 12, ... (32, 48, 64, 80... + * bytes). + * + * So if we have only 1 payload, we have to submit a header that in + * all truth has space for 4. + * + * The implication is that we reserve space for 12 (64 bytes); but + * if we fill up only (eg) 2, our header becomes 32 bytes only. So + * the TX engine has to shift those 32 bytes of msg header and 2 + * payloads and padding so that right after it the payloads start + * and the TX engine has to know about that. + * + * It is cheaper to move the header up than the whole payloads down. + * + * We do this in i2400m_tx_close(). See 'i2400m_msg_hdr->offset'. + * + * - Each payload has to be size-padded to 16 bytes; before appending + * it, we just do it. + * + * - The whole message has to be padded to i2400m->bus_tx_block_size; + * we do this at close time. Thus, when reserving space for the + * payload, we always make sure there is also free space for this + * padding that sooner or later will happen. + * + * When we append a message, we tell the bus specific code to kick in + * TXs. It will TX (in parallel) until the buffer is exhausted--hence + * the lockin we do. The TX code will only send a TX message at the + * time (which remember, might contain more than one payload). Of + * course, when the bus-specific driver attempts to TX a message that + * is still open, it gets closed first. + * + * Gee, this is messy; well a picture. In the example below we have a + * partially full FIFO, with a closed message ready to be delivered + * (with a moved message header to make sure it is size-aligned to + * 16), TAIL room that was unusable (and thus is marked with a message + * header that says 'skip this') and at the head of the buffer, an + * imcomplete message with a couple of payloads. + * + * N ___________________________________________________ + * | | + * | TAIL room | + * | | + * | msg_hdr to skip (size |= 0x80000) | + * |---------------------------------------------------|------- + * | | /|\ + * | | | + * | TX message padding | | + * | | | + * | | | + * |- - - - - - - - - - - - - - - - - - - - - - - - - -| | + * | | | + * | payload 1 | | + * | | N * tx_block_size + * | | | + * |- - - - - - - - - - - - - - - - - - - - - - - - - -| | + * | | | + * | payload 1 | | + * | | | + * | | | + * |- - - - - - - - - - - - - - - - - - - - - - - - - -|- -|- - - - + * | padding 3 /|\ | | /|\ + * | padding 2 | | | | + * | pld 1 32 bytes (2 * 16) | | | + * | pld 0 | | | | + * | moved msg_hdr \|/ | \|/ | + * |- - - - - - - - - - - - - - - - - - - - - - - - - -|- - - | + * | | _PLD_SIZE + * | unused | | + * | | | + * |- - - - - - - - - - - - - - - - - - - - - - - - - -| | + * | msg_hdr (size X) [this message is closed] | \|/ + * |===================================================|========== <=== OUT + * | | + * | | + * | | + * | Free rooom | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * | | + * |===================================================|========== <=== IN + * | | + * | | + * | | + * | | + * | payload 1 | + * | | + * | | + * |- - - - - - - - - - - - - - - - - - - - - - - - - -| + * | | + * | payload 0 | + * | | + * | | + * |- - - - - - - - - - - - - - - - - - - - - - - - - -| + * | pld 11 /|\ | + * | ... | | + * | pld 1 64 bytes (2 * 16) | + * | pld 0 | | + * | msg_hdr (size X) \|/ [message is open] | + * 0 --------------------------------------------------- + * + * + * ROADMAP + * + * i2400m_tx_setup() Called by i2400m_setup + * i2400m_tx_release() Called by i2400m_release() + * + * i2400m_tx() Called to send data or control frames + * i2400m_tx_fifo_push() Allocates append-space in the FIFO + * i2400m_tx_new() Opens a new message in the FIFO + * i2400m_tx_fits() Checks if a new payload fits in the message + * i2400m_tx_close() Closes an open message in the FIFO + * i2400m_tx_skip_tail() Marks unusable FIFO tail space + * i2400m->bus_tx_kick() + * + * Now i2400m->bus_tx_kick() is the the bus-specific driver backend + * implementation; that would do: + * + * i2400m->bus_tx_kick() + * i2400m_tx_msg_get() Gets first message ready to go + * ...sends it... + * i2400m_tx_msg_sent() Ack the message is sent; repeat from + * _tx_msg_get() until it returns NULL + * (FIFO empty). + */ +#include <linux/netdevice.h> +#include "i2400m.h" + + +#define D_SUBMODULE tx +#include "debug-levels.h" + +enum { + /** + * TX Buffer size + * + * Doc says maximum transaction is 16KiB. If we had 16KiB en + * route and 16KiB being queued, it boils down to needing + * 32KiB. + */ + I2400M_TX_BUF_SIZE = 32768, + /** + * Message header and payload descriptors have to be 16 + * aligned (16 + 4 * N = 16 * M). If we take that average sent + * packets are MTU size (~1400-~1500) it follows that we could + * fit at most 10-11 payloads in one transaction. To meet the + * alignment requirement, that means we need to leave space + * for 12 (64 bytes). To simplify, we leave space for that. If + * at the end there are less, we pad up to the nearest + * multiple of 16. + */ + I2400M_TX_PLD_MAX = 12, + I2400M_TX_PLD_SIZE = sizeof(struct i2400m_msg_hdr) + + I2400M_TX_PLD_MAX * sizeof(struct i2400m_pld), + I2400M_TX_SKIP = 0x80000000, +}; + +#define TAIL_FULL ((void *)~(unsigned long)NULL) + +/* + * Allocate @size bytes in the TX fifo, return a pointer to it + * + * @i2400m: device descriptor + * @size: size of the buffer we need to allocate + * @padding: ensure that there is at least this many bytes of free + * contiguous space in the fifo. This is needed because later on + * we might need to add padding. + * + * Returns: + * + * Pointer to the allocated space. NULL if there is no + * space. TAIL_FULL if there is no space at the tail but there is at + * the head (Case B below). + * + * These are the two basic cases we need to keep an eye for -- it is + * much better explained in linux/kernel/kfifo.c, but this code + * basically does the same. No rocket science here. + * + * Case A Case B + * N ___________ ___________ + * | tail room | | data | + * | | | | + * |<- IN ->| |<- OUT ->| + * | | | | + * | data | | room | + * | | | | + * |<- OUT ->| |<- IN ->| + * | | | | + * | head room | | data | + * 0 ----------- ----------- + * + * We allocate only *contiguous* space. + * + * We can allocate only from 'room'. In Case B, it is simple; in case + * A, we only try from the tail room; if it is not enough, we just + * fail and return TAIL_FULL and let the caller figure out if we wants to + * skip the tail room and try to allocate from the head. + * + * Note: + * + * Assumes i2400m->tx_lock is taken, and we use that as a barrier + * + * The indexes keep increasing and we reset them to zero when we + * pop data off the queue + */ +static +void *i2400m_tx_fifo_push(struct i2400m *i2400m, size_t size, size_t padding) +{ + struct device *dev = i2400m_dev(i2400m); + size_t room, tail_room, needed_size; + void *ptr; + + needed_size = size + padding; + room = I2400M_TX_BUF_SIZE - (i2400m->tx_in - i2400m->tx_out); + if (room < needed_size) { /* this takes care of Case B */ + d_printf(2, dev, "fifo push %zu/%zu: no space\n", + size, padding); + return NULL; + } + /* Is there space at the tail? */ + tail_room = I2400M_TX_BUF_SIZE - i2400m->tx_in % I2400M_TX_BUF_SIZE; + if (tail_room < needed_size) { + if (i2400m->tx_out % I2400M_TX_BUF_SIZE + < i2400m->tx_in % I2400M_TX_BUF_SIZE) { + d_printf(2, dev, "fifo push %zu/%zu: tail full\n", + size, padding); + return TAIL_FULL; /* There might be head space */ + } else { + d_printf(2, dev, "fifo push %zu/%zu: no head space\n", + size, padding); + return NULL; /* There is no space */ + } + } + ptr = i2400m->tx_buf + i2400m->tx_in % I2400M_TX_BUF_SIZE; + d_printf(2, dev, "fifo push %zu/%zu: at @%zu\n", size, padding, + i2400m->tx_in % I2400M_TX_BUF_SIZE); + i2400m->tx_in += size; + return ptr; +} + + +/* + * Mark the tail of the FIFO buffer as 'to-skip' + * + * We should never hit the BUG_ON() because all the sizes we push to + * the FIFO are padded to be a multiple of 16 -- the size of *msg + * (I2400M_PL_PAD for the payloads, I2400M_TX_PLD_SIZE for the + * header). + * + * Note: + * + * Assumes i2400m->tx_lock is taken, and we use that as a barrier + */ +static +void i2400m_tx_skip_tail(struct i2400m *i2400m) +{ + struct device *dev = i2400m_dev(i2400m); + size_t tx_in = i2400m->tx_in % I2400M_TX_BUF_SIZE; + size_t tail_room = I2400M_TX_BUF_SIZE - tx_in; + struct i2400m_msg_hdr *msg = i2400m->tx_buf + tx_in; + BUG_ON(tail_room < sizeof(*msg)); + msg->size = tail_room | I2400M_TX_SKIP; + d_printf(2, dev, "skip tail: skipping %zu bytes @%zu\n", + tail_room, tx_in); + i2400m->tx_in += tail_room; +} + + +/* + * Check if a skb will fit in the TX queue's current active TX + * message (if there are still descriptors left unused). + * + * Returns: + * 0 if the message won't fit, 1 if it will. + * + * Note: + * + * Assumes a TX message is active (i2400m->tx_msg). + * + * Assumes i2400m->tx_lock is taken, and we use that as a barrier + */ +static +unsigned i2400m_tx_fits(struct i2400m *i2400m) +{ + struct i2400m_msg_hdr *msg_hdr = i2400m->tx_msg; + return le16_to_cpu(msg_hdr->num_pls) < I2400M_TX_PLD_MAX; + +} + + +/* + * Start a new TX message header in the queue. + * + * Reserve memory from the base FIFO engine and then just initialize + * the message header. + * + * We allocate the biggest TX message header we might need (one that'd + * fit I2400M_TX_PLD_MAX payloads) -- when it is closed it will be + * 'ironed it out' and the unneeded parts removed. + * + * NOTE: + * + * Assumes that the previous message is CLOSED (eg: either + * there was none or 'i2400m_tx_close()' was called on it). + * + * Assumes i2400m->tx_lock is taken, and we use that as a barrier + */ +static +void i2400m_tx_new(struct i2400m *i2400m) +{ + struct device *dev = i2400m_dev(i2400m); + struct i2400m_msg_hdr *tx_msg; + BUG_ON(i2400m->tx_msg != NULL); +try_head: + tx_msg = i2400m_tx_fifo_push(i2400m, I2400M_TX_PLD_SIZE, 0); + if (tx_msg == NULL) + goto out; + else if (tx_msg == TAIL_FULL) { + i2400m_tx_skip_tail(i2400m); + d_printf(2, dev, "new TX message: tail full, trying head\n"); + goto try_head; + } + memset(tx_msg, 0, I2400M_TX_PLD_SIZE); + tx_msg->size = I2400M_TX_PLD_SIZE; +out: + i2400m->tx_msg = tx_msg; + d_printf(2, dev, "new TX message: %p @%zu\n", + tx_msg, (void *) tx_msg - i2400m->tx_buf); +} + + +/* + * Finalize the current TX message header + * + * Sets the message header to be at the proper location depending on + * how many descriptors we have (check documentation at the file's + * header for more info on that). + * + * Appends padding bytes to make sure the whole TX message (counting + * from the 'relocated' message header) is aligned to + * tx_block_size. We assume the _append() code has left enough space + * in the FIFO for that. If there are no payloads, just pass, as it + * won't be transferred. + * + * The amount of padding bytes depends on how many payloads are in the + * TX message, as the "msg header and payload descriptors" will be + * shifted up in the buffer. + */ +static +void i2400m_tx_close(struct i2400m *i2400m) +{ + struct device *dev = i2400m_dev(i2400m); + struct i2400m_msg_hdr *tx_msg = i2400m->tx_msg; + struct i2400m_msg_hdr *tx_msg_moved; + size_t aligned_size, padding, hdr_size; + void *pad_buf; + + if (tx_msg->size & I2400M_TX_SKIP) /* a skipper? nothing to do */ + goto out; + + /* Relocate the message header + * + * Find the current header size, align it to 16 and if we need + * to move it so the tail is next to the payloads, move it and + * set the offset. + * + * If it moved, this header is good only for transmission; the + * original one (it is kept if we moved) is still used to + * figure out where the next TX message starts (and where the + * offset to the moved header is). + */ + hdr_size = sizeof(*tx_msg) + + le16_to_cpu(tx_msg->num_pls) * sizeof(tx_msg->pld[0]); + hdr_size = ALIGN(hdr_size, I2400M_PL_PAD); + tx_msg->offset = I2400M_TX_PLD_SIZE - hdr_size; + tx_msg_moved = (void *) tx_msg + tx_msg->offset; + memmove(tx_msg_moved, tx_msg, hdr_size); + tx_msg_moved->size -= tx_msg->offset; + /* + * Now figure out how much we have to add to the (moved!) + * message so the size is a multiple of i2400m->bus_tx_block_size. + */ + aligned_size = ALIGN(tx_msg_moved->size, i2400m->bus_tx_block_size); + padding = aligned_size - tx_msg_moved->size; + if (padding > 0) { + pad_buf = i2400m_tx_fifo_push(i2400m, padding, 0); + if (unlikely(WARN_ON(pad_buf == NULL + || pad_buf == TAIL_FULL))) { + /* This should not happen -- append should verify + * there is always space left at least to append + * tx_block_size */ + dev_err(dev, + "SW BUG! Possible data leakage from memory the " + "device should not read for padding - " + "size %lu aligned_size %zu tx_buf %p in " + "%zu out %zu\n", + (unsigned long) tx_msg_moved->size, + aligned_size, i2400m->tx_buf, i2400m->tx_in, + i2400m->tx_out); + } else + memset(pad_buf, 0xad, padding); + } + tx_msg_moved->padding = cpu_to_le16(padding); + tx_msg_moved->size += padding; + if (tx_msg != tx_msg_moved) + tx_msg->size += padding; +out: + i2400m->tx_msg = NULL; +} + + +/** + * i2400m_tx - send the data in a buffer to the device + * + * @buf: pointer to the buffer to transmit + * + * @buf_len: buffer size + * + * @pl_type: type of the payload we are sending. + * + * Returns: + * 0 if ok, < 0 errno code on error (-ENOSPC, if there is no more + * room for the message in the queue). + * + * Appends the buffer to the TX FIFO and notifies the bus-specific + * part of the driver that there is new data ready to transmit. + * Once this function returns, the buffer has been copied, so it can + * be reused. + * + * The steps followed to append are explained in detail in the file + * header. + * + * Whenever we write to a message, we increase msg->size, so it + * reflects exactly how big the message is. This is needed so that if + * we concatenate two messages before they can be sent, the code that + * sends the messages can find the boundaries (and it will replace the + * size with the real barker before sending). + * + * Note: + * + * Cold and warm reset payloads need to be sent as a single + * payload, so we handle that. + */ +int i2400m_tx(struct i2400m *i2400m, const void *buf, size_t buf_len, + enum i2400m_pt pl_type) +{ + int result = -ENOSPC; + struct device *dev = i2400m_dev(i2400m); + unsigned long flags; + size_t padded_len; + void *ptr; + unsigned is_singleton = pl_type == I2400M_PT_RESET_WARM + || pl_type == I2400M_PT_RESET_COLD; + + d_fnstart(3, dev, "(i2400m %p skb %p [%zu bytes] pt %u)\n", + i2400m, buf, buf_len, pl_type); + padded_len = ALIGN(buf_len, I2400M_PL_PAD); + d_printf(5, dev, "padded_len %zd buf_len %zd\n", padded_len, buf_len); + /* If there is no current TX message, create one; if the + * current one is out of payload slots or we have a singleton, + * close it and start a new one */ + spin_lock_irqsave(&i2400m->tx_lock, flags); +try_new: + if (unlikely(i2400m->tx_msg == NULL)) + i2400m_tx_new(i2400m); + else if (unlikely(!i2400m_tx_fits(i2400m) + || (is_singleton && i2400m->tx_msg->num_pls != 0))) { + d_printf(2, dev, "closing TX message (fits %u singleton " + "%u num_pls %u)\n", i2400m_tx_fits(i2400m), + is_singleton, i2400m->tx_msg->num_pls); + i2400m_tx_close(i2400m); + i2400m_tx_new(i2400m); + } + if (i2400m->tx_msg->size + padded_len > I2400M_TX_BUF_SIZE / 2) { + d_printf(2, dev, "TX: message too big, going new\n"); + i2400m_tx_close(i2400m); + i2400m_tx_new(i2400m); + } + if (i2400m->tx_msg == NULL) + goto error_tx_new; + /* So we have a current message header; now append space for + * the message -- if there is not enough, try the head */ + ptr = i2400m_tx_fifo_push(i2400m, padded_len, + i2400m->bus_tx_block_size); + if (ptr == TAIL_FULL) { /* Tail is full, try head */ + d_printf(2, dev, "pl append: tail full\n"); + i2400m_tx_close(i2400m); + i2400m_tx_skip_tail(i2400m); + goto try_new; + } else if (ptr == NULL) { /* All full */ + result = -ENOSPC; + d_printf(2, dev, "pl append: all full\n"); + } else { /* Got space, copy it, set padding */ + struct i2400m_msg_hdr *tx_msg = i2400m->tx_msg; + unsigned num_pls = le16_to_cpu(tx_msg->num_pls); + memcpy(ptr, buf, buf_len); + memset(ptr + buf_len, 0xad, padded_len - buf_len); + i2400m_pld_set(&tx_msg->pld[num_pls], buf_len, pl_type); + d_printf(3, dev, "pld 0x%08x (type 0x%1x len 0x%04zx\n", + le32_to_cpu(tx_msg->pld[num_pls].val), + pl_type, buf_len); + tx_msg->num_pls = le16_to_cpu(num_pls+1); + tx_msg->size += padded_len; + d_printf(2, dev, "TX: appended %zu b (up to %u b) pl #%u \n", + padded_len, tx_msg->size, num_pls+1); + d_printf(2, dev, + "TX: appended hdr @%zu %zu b pl #%u @%zu %zu/%zu b\n", + (void *)tx_msg - i2400m->tx_buf, (size_t)tx_msg->size, + num_pls+1, ptr - i2400m->tx_buf, buf_len, padded_len); + result = 0; + if (is_singleton) + i2400m_tx_close(i2400m); + } +error_tx_new: + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + i2400m->bus_tx_kick(i2400m); /* always kick, might free up space */ + d_fnend(3, dev, "(i2400m %p skb %p [%zu bytes] pt %u) = %d\n", + i2400m, buf, buf_len, pl_type, result); + return result; +} +EXPORT_SYMBOL_GPL(i2400m_tx); + + +/** + * i2400m_tx_msg_get - Get the first TX message in the FIFO to start sending it + * + * @i2400m: device descriptors + * @bus_size: where to place the size of the TX message + * + * Called by the bus-specific driver to get the first TX message at + * the FIF that is ready for transmission. + * + * It sets the state in @i2400m to indicate the bus-specific driver is + * transfering that message (i2400m->tx_msg_size). + * + * Once the transfer is completed, call i2400m_tx_msg_sent(). + * + * Notes: + * + * The size of the TX message to be transmitted might be smaller than + * that of the TX message in the FIFO (in case the header was + * shorter). Hence, we copy it in @bus_size, for the bus layer to + * use. We keep the message's size in i2400m->tx_msg_size so that + * when the bus later is done transferring we know how much to + * advance the fifo. + * + * We collect statistics here as all the data is available and we + * assume it is going to work [see i2400m_tx_msg_sent()]. + */ +struct i2400m_msg_hdr *i2400m_tx_msg_get(struct i2400m *i2400m, + size_t *bus_size) +{ + struct device *dev = i2400m_dev(i2400m); + struct i2400m_msg_hdr *tx_msg, *tx_msg_moved; + unsigned long flags, pls; + + d_fnstart(3, dev, "(i2400m %p bus_size %p)\n", i2400m, bus_size); + spin_lock_irqsave(&i2400m->tx_lock, flags); +skip: + tx_msg_moved = NULL; + if (i2400m->tx_in == i2400m->tx_out) { /* Empty FIFO? */ + i2400m->tx_in = 0; + i2400m->tx_out = 0; + d_printf(2, dev, "TX: FIFO empty: resetting\n"); + goto out_unlock; + } + tx_msg = i2400m->tx_buf + i2400m->tx_out % I2400M_TX_BUF_SIZE; + if (tx_msg->size & I2400M_TX_SKIP) { /* skip? */ + d_printf(2, dev, "TX: skip: msg @%zu (%zu b)\n", + i2400m->tx_out % I2400M_TX_BUF_SIZE, + (size_t) tx_msg->size & ~I2400M_TX_SKIP); + i2400m->tx_out += tx_msg->size & ~I2400M_TX_SKIP; + goto skip; + } + + if (tx_msg->num_pls == 0) { /* No payloads? */ + if (tx_msg == i2400m->tx_msg) { /* open, we are done */ + d_printf(2, dev, + "TX: FIFO empty: open msg w/o payloads @%zu\n", + (void *) tx_msg - i2400m->tx_buf); + tx_msg = NULL; + goto out_unlock; + } else { /* closed, skip it */ + d_printf(2, dev, + "TX: skip msg w/o payloads @%zu (%zu b)\n", + (void *) tx_msg - i2400m->tx_buf, + (size_t) tx_msg->size); + i2400m->tx_out += tx_msg->size & ~I2400M_TX_SKIP; + goto skip; + } + } + if (tx_msg == i2400m->tx_msg) /* open msg? */ + i2400m_tx_close(i2400m); + + /* Now we have a valid TX message (with payloads) to TX */ + tx_msg_moved = (void *) tx_msg + tx_msg->offset; + i2400m->tx_msg_size = tx_msg->size; + *bus_size = tx_msg_moved->size; + d_printf(2, dev, "TX: pid %d msg hdr at @%zu offset +@%zu " + "size %zu bus_size %zu\n", + current->pid, (void *) tx_msg - i2400m->tx_buf, + (size_t) tx_msg->offset, (size_t) tx_msg->size, + (size_t) tx_msg_moved->size); + tx_msg_moved->barker = le32_to_cpu(I2400M_H2D_PREVIEW_BARKER); + tx_msg_moved->sequence = le32_to_cpu(i2400m->tx_sequence++); + + pls = le32_to_cpu(tx_msg_moved->num_pls); + i2400m->tx_pl_num += pls; /* Update stats */ + if (pls > i2400m->tx_pl_max) + i2400m->tx_pl_max = pls; + if (pls < i2400m->tx_pl_min) + i2400m->tx_pl_min = pls; + i2400m->tx_num++; + i2400m->tx_size_acc += *bus_size; + if (*bus_size < i2400m->tx_size_min) + i2400m->tx_size_min = *bus_size; + if (*bus_size > i2400m->tx_size_max) + i2400m->tx_size_max = *bus_size; +out_unlock: + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + d_fnstart(3, dev, "(i2400m %p bus_size %p [%zu]) = %p\n", + i2400m, bus_size, *bus_size, tx_msg_moved); + return tx_msg_moved; +} +EXPORT_SYMBOL_GPL(i2400m_tx_msg_get); + + +/** + * i2400m_tx_msg_sent - indicate the transmission of a TX message + * + * @i2400m: device descriptor + * + * Called by the bus-specific driver when a message has been sent; + * this pops it from the FIFO; and as there is space, start the queue + * in case it was stopped. + * + * Should be called even if the message send failed and we are + * dropping this TX message. + */ +void i2400m_tx_msg_sent(struct i2400m *i2400m) +{ + unsigned n; + unsigned long flags; + struct device *dev = i2400m_dev(i2400m); + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + spin_lock_irqsave(&i2400m->tx_lock, flags); + i2400m->tx_out += i2400m->tx_msg_size; + d_printf(2, dev, "TX: sent %zu b\n", (size_t) i2400m->tx_msg_size); + i2400m->tx_msg_size = 0; + BUG_ON(i2400m->tx_out > i2400m->tx_in); + /* level them FIFO markers off */ + n = i2400m->tx_out / I2400M_TX_BUF_SIZE; + i2400m->tx_out %= I2400M_TX_BUF_SIZE; + i2400m->tx_in -= n * I2400M_TX_BUF_SIZE; + netif_start_queue(i2400m->wimax_dev.net_dev); + spin_unlock_irqrestore(&i2400m->tx_lock, flags); + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); +} +EXPORT_SYMBOL_GPL(i2400m_tx_msg_sent); + + +/** + * i2400m_tx_setup - Initialize the TX queue and infrastructure + * + * Make sure we reset the TX sequence to zero, as when this function + * is called, the firmware has been just restarted. + */ +int i2400m_tx_setup(struct i2400m *i2400m) +{ + int result; + + /* Do this here only once -- can't do on + * i2400m_hard_start_xmit() as we'll cause race conditions if + * the WS was scheduled on another CPU */ + INIT_WORK(&i2400m->wake_tx_ws, i2400m_wake_tx_work); + + i2400m->tx_sequence = 0; + i2400m->tx_buf = kmalloc(I2400M_TX_BUF_SIZE, GFP_KERNEL); + if (i2400m->tx_buf == NULL) + result = -ENOMEM; + else + result = 0; + /* Huh? the bus layer has to define this... */ + BUG_ON(i2400m->bus_tx_block_size == 0); + return result; + +} + + +/** + * i2400m_tx_release - Tear down the TX queue and infrastructure + */ +void i2400m_tx_release(struct i2400m *i2400m) +{ + kfree(i2400m->tx_buf); +} diff --git a/drivers/net/wimax/i2400m/usb-debug-levels.h b/drivers/net/wimax/i2400m/usb-debug-levels.h new file mode 100644 index 000000000000..e4358bd880be --- /dev/null +++ b/drivers/net/wimax/i2400m/usb-debug-levels.h @@ -0,0 +1,42 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Debug levels control file for the i2400m-usb module + * + * + * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.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. + * + * 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. + */ +#ifndef __debug_levels__h__ +#define __debug_levels__h__ + +/* Maximum compile and run time debug level for all submodules */ +#define D_MODULENAME i2400m_usb +#define D_MASTER CONFIG_WIMAX_I2400M_DEBUG_LEVEL + +#include <linux/wimax/debug.h> + +/* List of all the enabled modules */ +enum d_module { + D_SUBMODULE_DECLARE(usb), + D_SUBMODULE_DECLARE(fw), + D_SUBMODULE_DECLARE(notif), + D_SUBMODULE_DECLARE(rx), + D_SUBMODULE_DECLARE(tx), +}; + + +#endif /* #ifndef __debug_levels__h__ */ diff --git a/drivers/net/wimax/i2400m/usb-fw.c b/drivers/net/wimax/i2400m/usb-fw.c new file mode 100644 index 000000000000..5ad287c228b8 --- /dev/null +++ b/drivers/net/wimax/i2400m/usb-fw.c @@ -0,0 +1,340 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Firmware uploader's USB specifics + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Initial implementation + * + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - bus generic/specific split + * + * THE PROCEDURE + * + * See fw.c for the generic description of this procedure. + * + * This file implements only the USB specifics. It boils down to how + * to send a command and waiting for an acknowledgement from the + * device. + * + * This code (and process) is single threaded. It assumes it is the + * only thread poking around (guaranteed by fw.c). + * + * COMMAND EXECUTION + * + * A write URB is posted with the buffer to the bulk output endpoint. + * + * ACK RECEPTION + * + * We just post a URB to the notification endpoint and wait for + * data. We repeat until we get all the data we expect (as indicated + * by the call from the bus generic code). + * + * The data is not read from the bulk in endpoint for boot mode. + * + * ROADMAP + * + * i2400mu_bus_bm_cmd_send + * i2400m_bm_cmd_prepare... + * i2400mu_tx_bulk_out + * + * i2400mu_bus_bm_wait_for_ack + * i2400m_notif_submit + */ +#include <linux/usb.h> +#include "i2400m-usb.h" + + +#define D_SUBMODULE fw +#include "usb-debug-levels.h" + + +/* + * Synchronous write to the device + * + * Takes care of updating EDC counts and thus, handle device errors. + */ +static +ssize_t i2400mu_tx_bulk_out(struct i2400mu *i2400mu, void *buf, size_t buf_size) +{ + int result; + struct device *dev = &i2400mu->usb_iface->dev; + int len; + struct usb_endpoint_descriptor *epd; + int pipe, do_autopm = 1; + + result = usb_autopm_get_interface(i2400mu->usb_iface); + if (result < 0) { + dev_err(dev, "BM-CMD: can't get autopm: %d\n", result); + do_autopm = 0; + } + epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_OUT); + pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); +retry: + result = usb_bulk_msg(i2400mu->usb_dev, pipe, buf, buf_size, &len, HZ); + switch (result) { + case 0: + if (len != buf_size) { + dev_err(dev, "BM-CMD: short write (%u B vs %zu " + "expected)\n", len, buf_size); + result = -EIO; + break; + } + result = len; + break; + case -EINVAL: /* while removing driver */ + case -ENODEV: /* dev disconnect ... */ + case -ENOENT: /* just ignore it */ + case -ESHUTDOWN: /* and exit */ + case -ECONNRESET: + result = -ESHUTDOWN; + break; + case -ETIMEDOUT: /* bah... */ + break; + default: /* any other? */ + if (edc_inc(&i2400mu->urb_edc, + EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "BM-CMD: maximum errors in " + "URB exceeded; resetting device\n"); + usb_queue_reset_device(i2400mu->usb_iface); + result = -ENODEV; + break; + } + dev_err(dev, "BM-CMD: URB error %d, retrying\n", + result); + goto retry; + } + result = len; + if (do_autopm) + usb_autopm_put_interface(i2400mu->usb_iface); + return result; +} + + +/* + * Send a boot-mode command over the bulk-out pipe + * + * Command can be a raw command, which requires no preparation (and + * which might not even be following the command format). Checks that + * the right amount of data was transfered. + * + * To satisfy USB requirements (no onstack, vmalloc or in data segment + * buffers), we copy the command to i2400m->bm_cmd_buf and send it from + * there. + * + * @flags: pass thru from i2400m_bm_cmd() + * @return: cmd_size if ok, < 0 errno code on error. + */ +ssize_t i2400mu_bus_bm_cmd_send(struct i2400m *i2400m, + const struct i2400m_bootrom_header *_cmd, + size_t cmd_size, int flags) +{ + ssize_t result; + struct device *dev = i2400m_dev(i2400m); + struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m); + int opcode = _cmd == NULL ? -1 : i2400m_brh_get_opcode(_cmd); + struct i2400m_bootrom_header *cmd; + size_t cmd_size_a = ALIGN(cmd_size, 16); /* USB restriction */ + + d_fnstart(8, dev, "(i2400m %p cmd %p size %zu)\n", + i2400m, _cmd, cmd_size); + result = -E2BIG; + if (cmd_size > I2400M_BM_CMD_BUF_SIZE) + goto error_too_big; + memcpy(i2400m->bm_cmd_buf, _cmd, cmd_size); + cmd = i2400m->bm_cmd_buf; + if (cmd_size_a > cmd_size) /* Zero pad space */ + memset(i2400m->bm_cmd_buf + cmd_size, 0, cmd_size_a - cmd_size); + if ((flags & I2400M_BM_CMD_RAW) == 0) { + if (WARN_ON(i2400m_brh_get_response_required(cmd) == 0)) + dev_warn(dev, "SW BUG: response_required == 0\n"); + i2400m_bm_cmd_prepare(cmd); + } + result = i2400mu_tx_bulk_out(i2400mu, i2400m->bm_cmd_buf, cmd_size); + if (result < 0) { + dev_err(dev, "boot-mode cmd %d: cannot send: %zd\n", + opcode, result); + goto error_cmd_send; + } + if (result != cmd_size) { /* all was transferred? */ + dev_err(dev, "boot-mode cmd %d: incomplete transfer " + "(%zu vs %zu submitted)\n", opcode, result, cmd_size); + result = -EIO; + goto error_cmd_size; + } +error_cmd_size: +error_cmd_send: +error_too_big: + d_fnend(8, dev, "(i2400m %p cmd %p size %zu) = %zd\n", + i2400m, _cmd, cmd_size, result); + return result; +} + + +static +void __i2400mu_bm_notif_cb(struct urb *urb) +{ + complete(urb->context); +} + + +/* + * submit a read to the notification endpoint + * + * @i2400m: device descriptor + * @urb: urb to use + * @completion: completion varible to complete when done + * + * Data is always read to i2400m->bm_ack_buf + */ +static +int i2400mu_notif_submit(struct i2400mu *i2400mu, struct urb *urb, + struct completion *completion) +{ + struct i2400m *i2400m = &i2400mu->i2400m; + struct usb_endpoint_descriptor *epd; + int pipe; + + epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION); + pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress); + usb_fill_int_urb(urb, i2400mu->usb_dev, pipe, + i2400m->bm_ack_buf, I2400M_BM_ACK_BUF_SIZE, + __i2400mu_bm_notif_cb, completion, + epd->bInterval); + return usb_submit_urb(urb, GFP_KERNEL); +} + + +/* + * Read an ack from the notification endpoint + * + * @i2400m: + * @_ack: pointer to where to store the read data + * @ack_size: how many bytes we should read + * + * Returns: < 0 errno code on error; otherwise, amount of received bytes. + * + * Submits a notification read, appends the read data to the given ack + * buffer and then repeats (until @ack_size bytes have been + * received). + */ +ssize_t i2400mu_bus_bm_wait_for_ack(struct i2400m *i2400m, + struct i2400m_bootrom_header *_ack, + size_t ack_size) +{ + ssize_t result = -ENOMEM; + struct device *dev = i2400m_dev(i2400m); + struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m); + struct urb notif_urb; + void *ack = _ack; + size_t offset, len; + long val; + int do_autopm = 1; + DECLARE_COMPLETION_ONSTACK(notif_completion); + + d_fnstart(8, dev, "(i2400m %p ack %p size %zu)\n", + i2400m, ack, ack_size); + BUG_ON(_ack == i2400m->bm_ack_buf); + result = usb_autopm_get_interface(i2400mu->usb_iface); + if (result < 0) { + dev_err(dev, "BM-ACK: can't get autopm: %d\n", (int) result); + do_autopm = 0; + } + usb_init_urb(¬if_urb); /* ready notifications */ + usb_get_urb(¬if_urb); + offset = 0; + while (offset < ack_size) { + init_completion(¬if_completion); + result = i2400mu_notif_submit(i2400mu, ¬if_urb, + ¬if_completion); + if (result < 0) + goto error_notif_urb_submit; + val = wait_for_completion_interruptible_timeout( + ¬if_completion, HZ); + if (val == 0) { + result = -ETIMEDOUT; + usb_kill_urb(¬if_urb); /* Timedout */ + goto error_notif_wait; + } + if (val == -ERESTARTSYS) { + result = -EINTR; /* Interrupted */ + usb_kill_urb(¬if_urb); + goto error_notif_wait; + } + result = notif_urb.status; /* How was the ack? */ + switch (result) { + case 0: + break; + case -EINVAL: /* while removing driver */ + case -ENODEV: /* dev disconnect ... */ + case -ENOENT: /* just ignore it */ + case -ESHUTDOWN: /* and exit */ + case -ECONNRESET: + result = -ESHUTDOWN; + goto error_dev_gone; + default: /* any other? */ + usb_kill_urb(¬if_urb); /* Timedout */ + if (edc_inc(&i2400mu->urb_edc, + EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) + goto error_exceeded; + dev_err(dev, "BM-ACK: URB error %d, " + "retrying\n", notif_urb.status); + continue; /* retry */ + } + if (notif_urb.actual_length == 0) { + d_printf(6, dev, "ZLP received, retrying\n"); + continue; + } + /* Got data, append it to the buffer */ + len = min(ack_size - offset, (size_t) notif_urb.actual_length); + memcpy(ack + offset, i2400m->bm_ack_buf, len); + offset += len; + } + result = offset; +error_notif_urb_submit: +error_notif_wait: +error_dev_gone: +out: + if (do_autopm) + usb_autopm_put_interface(i2400mu->usb_iface); + d_fnend(8, dev, "(i2400m %p ack %p size %zu) = %zd\n", + i2400m, ack, ack_size, result); + return result; + +error_exceeded: + dev_err(dev, "bm: maximum errors in notification URB exceeded; " + "resetting device\n"); + usb_queue_reset_device(i2400mu->usb_iface); + goto out; +} diff --git a/drivers/net/wimax/i2400m/usb-notif.c b/drivers/net/wimax/i2400m/usb-notif.c new file mode 100644 index 000000000000..9702c22b2497 --- /dev/null +++ b/drivers/net/wimax/i2400m/usb-notif.c @@ -0,0 +1,269 @@ +/* + * Intel Wireless WiMAX Connection 2400m over USB + * Notification handling + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Initial implementation + * + * + * The notification endpoint is active when the device is not in boot + * mode; in here we just read and get notifications; based on those, + * we act to either reinitialize the device after a reboot or to + * submit a RX request. + * + * ROADMAP + * + * i2400mu_usb_notification_setup() + * + * i2400mu_usb_notification_release() + * + * i2400mu_usb_notification_cb() Called when a URB is ready + * i2400mu_notif_grok() + * i2400m_dev_reset_handle() + * i2400mu_rx_kick() + */ +#include <linux/usb.h> +#include "i2400m-usb.h" + + +#define D_SUBMODULE notif +#include "usb-debug-levels.h" + + +static const +__le32 i2400m_ZERO_BARKER[4] = { 0, 0, 0, 0 }; + + +/* + * Process a received notification + * + * In normal operation mode, we can only receive two types of payloads + * on the notification endpoint: + * + * - a reboot barker, we do a bootstrap (the device has reseted). + * + * - a block of zeroes: there is pending data in the IN endpoint + */ +static +int i2400mu_notification_grok(struct i2400mu *i2400mu, const void *buf, + size_t buf_len) +{ + int ret; + struct device *dev = &i2400mu->usb_iface->dev; + struct i2400m *i2400m = &i2400mu->i2400m; + + d_fnstart(4, dev, "(i2400m %p buf %p buf_len %zu)\n", + i2400mu, buf, buf_len); + ret = -EIO; + if (buf_len < sizeof(i2400m_NBOOT_BARKER)) + /* Not a bug, just ignore */ + goto error_bad_size; + if (!memcmp(i2400m_NBOOT_BARKER, buf, sizeof(i2400m_NBOOT_BARKER)) + || !memcmp(i2400m_SBOOT_BARKER, buf, sizeof(i2400m_SBOOT_BARKER))) + ret = i2400m_dev_reset_handle(i2400m); + else if (!memcmp(i2400m_ZERO_BARKER, buf, sizeof(i2400m_ZERO_BARKER))) { + i2400mu_rx_kick(i2400mu); + ret = 0; + } else { /* Unknown or unexpected data in the notif message */ + char prefix[64]; + ret = -EIO; + dev_err(dev, "HW BUG? Unknown/unexpected data in notification " + "message (%zu bytes)\n", buf_len); + snprintf(prefix, sizeof(prefix), "%s %s: ", + dev_driver_string(dev) , dev->bus_id); + if (buf_len > 64) { + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, + 8, 4, buf, 64, 0); + printk(KERN_ERR "%s... (only first 64 bytes " + "dumped)\n", prefix); + } else + print_hex_dump(KERN_ERR, prefix, DUMP_PREFIX_OFFSET, + 8, 4, buf, buf_len, 0); + } +error_bad_size: + d_fnend(4, dev, "(i2400m %p buf %p buf_len %zu) = %d\n", + i2400mu, buf, buf_len, ret); + return ret; +} + + +/* + * URB callback for the notification endpoint + * + * @urb: the urb received from the notification endpoint + * + * This function will just process the USB side of the transaction, + * checking everything is fine, pass the processing to + * i2400m_notification_grok() and resubmit the URB. + */ +static +void i2400mu_notification_cb(struct urb *urb) +{ + int ret; + struct i2400mu *i2400mu = urb->context; + struct device *dev = &i2400mu->usb_iface->dev; + + d_fnstart(4, dev, "(urb %p status %d actual_length %d)\n", + urb, urb->status, urb->actual_length); + ret = urb->status; + switch (ret) { + case 0: + ret = i2400mu_notification_grok(i2400mu, urb->transfer_buffer, + urb->actual_length); + if (ret == -EIO && edc_inc(&i2400mu->urb_edc, EDC_MAX_ERRORS, + EDC_ERROR_TIMEFRAME)) + goto error_exceeded; + if (ret == -ENOMEM) /* uff...power cycle? shutdown? */ + goto error_exceeded; + break; + case -EINVAL: /* while removing driver */ + case -ENODEV: /* dev disconnect ... */ + case -ENOENT: /* ditto */ + case -ESHUTDOWN: /* URB killed */ + case -ECONNRESET: /* disconnection */ + goto out; /* Notify around */ + default: /* Some error? */ + if (edc_inc(&i2400mu->urb_edc, + EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) + goto error_exceeded; + dev_err(dev, "notification: URB error %d, retrying\n", + urb->status); + } + usb_mark_last_busy(i2400mu->usb_dev); + ret = usb_submit_urb(i2400mu->notif_urb, GFP_ATOMIC); + switch (ret) { + case 0: + case -EINVAL: /* while removing driver */ + case -ENODEV: /* dev disconnect ... */ + case -ENOENT: /* ditto */ + case -ESHUTDOWN: /* URB killed */ + case -ECONNRESET: /* disconnection */ + break; /* just ignore */ + default: /* Some error? */ + dev_err(dev, "notification: cannot submit URB: %d\n", ret); + goto error_submit; + } + d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n", + urb, urb->status, urb->actual_length); + return; + +error_exceeded: + dev_err(dev, "maximum errors in notification URB exceeded; " + "resetting device\n"); +error_submit: + usb_queue_reset_device(i2400mu->usb_iface); +out: + d_fnend(4, dev, "(urb %p status %d actual_length %d) = void\n", + urb, urb->status, urb->actual_length); + return; +} + + +/* + * setup the notification endpoint + * + * @i2400m: device descriptor + * + * This procedure prepares the notification urb and handler for receiving + * unsolicited barkers from the device. + */ +int i2400mu_notification_setup(struct i2400mu *i2400mu) +{ + struct device *dev = &i2400mu->usb_iface->dev; + int usb_pipe, ret = 0; + struct usb_endpoint_descriptor *epd; + char *buf; + + d_fnstart(4, dev, "(i2400m %p)\n", i2400mu); + buf = kmalloc(I2400MU_MAX_NOTIFICATION_LEN, GFP_KERNEL | GFP_DMA); + if (buf == NULL) { + dev_err(dev, "notification: buffer allocation failed\n"); + ret = -ENOMEM; + goto error_buf_alloc; + } + + i2400mu->notif_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!i2400mu->notif_urb) { + ret = -ENOMEM; + dev_err(dev, "notification: cannot allocate URB\n"); + goto error_alloc_urb; + } + epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_NOTIFICATION); + usb_pipe = usb_rcvintpipe(i2400mu->usb_dev, epd->bEndpointAddress); + usb_fill_int_urb(i2400mu->notif_urb, i2400mu->usb_dev, usb_pipe, + buf, I2400MU_MAX_NOTIFICATION_LEN, + i2400mu_notification_cb, i2400mu, epd->bInterval); + ret = usb_submit_urb(i2400mu->notif_urb, GFP_KERNEL); + if (ret != 0) { + dev_err(dev, "notification: cannot submit URB: %d\n", ret); + goto error_submit; + } + d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret); + return ret; + +error_submit: + usb_free_urb(i2400mu->notif_urb); +error_alloc_urb: + kfree(buf); +error_buf_alloc: + d_fnend(4, dev, "(i2400m %p) = %d\n", i2400mu, ret); + return ret; +} + + +/* + * Tear down of the notification mechanism + * + * @i2400m: device descriptor + * + * Kill the interrupt endpoint urb, free any allocated resources. + * + * We need to check if we have done it before as for example, + * _suspend() call this; if after a suspend() we get a _disconnect() + * (as the case is when hibernating), nothing bad happens. + */ +void i2400mu_notification_release(struct i2400mu *i2400mu) +{ + struct device *dev = &i2400mu->usb_iface->dev; + + d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); + if (i2400mu->notif_urb != NULL) { + usb_kill_urb(i2400mu->notif_urb); + kfree(i2400mu->notif_urb->transfer_buffer); + usb_free_urb(i2400mu->notif_urb); + i2400mu->notif_urb = NULL; + } + d_fnend(4, dev, "(i2400mu %p)\n", i2400mu); +} diff --git a/drivers/net/wimax/i2400m/usb-rx.c b/drivers/net/wimax/i2400m/usb-rx.c new file mode 100644 index 000000000000..074cc1f89853 --- /dev/null +++ b/drivers/net/wimax/i2400m/usb-rx.c @@ -0,0 +1,417 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * USB RX handling + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * - Initial implementation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Use skb_clone(), break up processing in chunks + * - Split transport/device specific + * - Make buffer size dynamic to exert less memory pressure + * + * + * This handles the RX path on USB. + * + * When a notification is received that says 'there is RX data ready', + * we call i2400mu_rx_kick(); that wakes up the RX kthread, which + * reads a buffer from USB and passes it to i2400m_rx() in the generic + * handling code. The RX buffer has an specific format that is + * described in rx.c. + * + * We use a kernel thread in a loop because: + * + * - we want to be able to call the USB power management get/put + * functions (blocking) before each transaction. + * + * - We might get a lot of notifications and we don't want to submit + * a zillion reads; by serializing, we are throttling. + * + * - RX data processing can get heavy enough so that it is not + * appropiate for doing it in the USB callback; thus we run it in a + * process context. + * + * We provide a read buffer of an arbitrary size (short of a page); if + * the callback reports -EOVERFLOW, it means it was too small, so we + * just double the size and retry (being careful to append, as + * sometimes the device provided some data). Every now and then we + * check if the average packet size is smaller than the current packet + * size and if so, we halve it. At the end, the size of the + * preallocated buffer should be following the average received + * transaction size, adapting dynamically to it. + * + * ROADMAP + * + * i2400mu_rx_kick() Called from notif.c when we get a + * 'data ready' notification + * i2400mu_rxd() Kernel RX daemon + * i2400mu_rx() Receive USB data + * i2400m_rx() Send data to generic i2400m RX handling + * + * i2400mu_rx_setup() called from i2400mu_bus_dev_start() + * + * i2400mu_rx_release() called from i2400mu_bus_dev_stop() + */ +#include <linux/workqueue.h> +#include <linux/usb.h> +#include "i2400m-usb.h" + + +#define D_SUBMODULE rx +#include "usb-debug-levels.h" + +/* + * Dynamic RX size + * + * We can't let the rx_size be a multiple of 512 bytes (the RX + * endpoint's max packet size). On some USB host controllers (we + * haven't been able to fully characterize which), if the device is + * about to send (for example) X bytes and we only post a buffer to + * receive n*512, it will fail to mark that as babble (so that + * i2400mu_rx() [case -EOVERFLOW] can resize the buffer and get the + * rest). + * + * So on growing or shrinking, if it is a multiple of the + * maxpacketsize, we remove some (instead of incresing some, so in a + * buddy allocator we try to waste less space). + * + * Note we also need a hook for this on i2400mu_rx() -- when we do the + * first read, we are sure we won't hit this spot because + * i240mm->rx_size has been set properly. However, if we have to + * double because of -EOVERFLOW, when we launch the read to get the + * rest of the data, we *have* to make sure that also is not a + * multiple of the max_pkt_size. + */ + +static +size_t i2400mu_rx_size_grow(struct i2400mu *i2400mu) +{ + struct device *dev = &i2400mu->usb_iface->dev; + size_t rx_size; + const size_t max_pkt_size = 512; + + rx_size = 2 * i2400mu->rx_size; + if (rx_size % max_pkt_size == 0) { + rx_size -= 8; + d_printf(1, dev, + "RX: expected size grew to %zu [adjusted -8] " + "from %zu\n", + rx_size, i2400mu->rx_size); + } else + d_printf(1, dev, + "RX: expected size grew to %zu from %zu\n", + rx_size, i2400mu->rx_size); + return rx_size; +} + + +static +void i2400mu_rx_size_maybe_shrink(struct i2400mu *i2400mu) +{ + const size_t max_pkt_size = 512; + struct device *dev = &i2400mu->usb_iface->dev; + + if (unlikely(i2400mu->rx_size_cnt >= 100 + && i2400mu->rx_size_auto_shrink)) { + size_t avg_rx_size = + i2400mu->rx_size_acc / i2400mu->rx_size_cnt; + size_t new_rx_size = i2400mu->rx_size / 2; + if (avg_rx_size < new_rx_size) { + if (new_rx_size % max_pkt_size == 0) { + new_rx_size -= 8; + d_printf(1, dev, + "RX: expected size shrank to %zu " + "[adjusted -8] from %zu\n", + new_rx_size, i2400mu->rx_size); + } else + d_printf(1, dev, + "RX: expected size shrank to %zu " + "from %zu\n", + new_rx_size, i2400mu->rx_size); + i2400mu->rx_size = new_rx_size; + i2400mu->rx_size_cnt = 0; + i2400mu->rx_size_acc = i2400mu->rx_size; + } + } +} + +/* + * Receive a message with payloads from the USB bus into an skb + * + * @i2400mu: USB device descriptor + * @rx_skb: skb where to place the received message + * + * Deals with all the USB-specifics of receiving, dynamically + * increasing the buffer size if so needed. Returns the payload in the + * skb, ready to process. On a zero-length packet, we retry. + * + * On soft USB errors, we retry (until they become too frequent and + * then are promoted to hard); on hard USB errors, we reset the + * device. On other errors (skb realloacation, we just drop it and + * hope for the next invocation to solve it). + * + * Returns: pointer to the skb if ok, ERR_PTR on error. + * NOTE: this function might realloc the skb (if it is too small), + * so always update with the one returned. + * ERR_PTR() is < 0 on error. + */ +static +struct sk_buff *i2400mu_rx(struct i2400mu *i2400mu, struct sk_buff *rx_skb) +{ + int result = 0; + struct device *dev = &i2400mu->usb_iface->dev; + int usb_pipe, read_size, rx_size, do_autopm; + struct usb_endpoint_descriptor *epd; + const size_t max_pkt_size = 512; + + d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); + do_autopm = atomic_read(&i2400mu->do_autopm); + result = do_autopm ? + usb_autopm_get_interface(i2400mu->usb_iface) : 0; + if (result < 0) { + dev_err(dev, "RX: can't get autopm: %d\n", result); + do_autopm = 0; + } + epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_IN); + usb_pipe = usb_rcvbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); +retry: + rx_size = skb_end_pointer(rx_skb) - rx_skb->data - rx_skb->len; + if (unlikely(rx_size % max_pkt_size == 0)) { + rx_size -= 8; + d_printf(1, dev, "RX: rx_size adapted to %d [-8]\n", rx_size); + } + result = usb_bulk_msg( + i2400mu->usb_dev, usb_pipe, rx_skb->data + rx_skb->len, + rx_size, &read_size, HZ); + usb_mark_last_busy(i2400mu->usb_dev); + switch (result) { + case 0: + if (read_size == 0) + goto retry; /* ZLP, just resubmit */ + skb_put(rx_skb, read_size); + break; + case -EINVAL: /* while removing driver */ + case -ENODEV: /* dev disconnect ... */ + case -ENOENT: /* just ignore it */ + case -ESHUTDOWN: + case -ECONNRESET: + break; + case -EOVERFLOW: { /* too small, reallocate */ + struct sk_buff *new_skb; + rx_size = i2400mu_rx_size_grow(i2400mu); + if (rx_size <= (1 << 16)) /* cap it */ + i2400mu->rx_size = rx_size; + else if (printk_ratelimit()) { + dev_err(dev, "BUG? rx_size up to %d\n", rx_size); + result = -EINVAL; + goto out; + } + skb_put(rx_skb, read_size); + new_skb = skb_copy_expand(rx_skb, 0, rx_size - rx_skb->len, + GFP_KERNEL); + if (new_skb == NULL) { + if (printk_ratelimit()) + dev_err(dev, "RX: Can't reallocate skb to %d; " + "RX dropped\n", rx_size); + kfree(rx_skb); + result = 0; + goto out; /* drop it...*/ + } + kfree_skb(rx_skb); + rx_skb = new_skb; + i2400mu->rx_size_cnt = 0; + i2400mu->rx_size_acc = i2400mu->rx_size; + d_printf(1, dev, "RX: size changed to %d, received %d, " + "copied %d, capacity %ld\n", + rx_size, read_size, rx_skb->len, + (long) (skb_end_pointer(new_skb) - new_skb->head)); + goto retry; + } + /* In most cases, it happens due to the hardware scheduling a + * read when there was no data - unfortunately, we have no way + * to tell this timeout from a USB timeout. So we just ignore + * it. */ + case -ETIMEDOUT: + dev_err(dev, "RX: timeout: %d\n", result); + result = 0; + break; + default: /* Any error */ + if (edc_inc(&i2400mu->urb_edc, + EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) + goto error_reset; + dev_err(dev, "RX: error receiving URB: %d, retrying\n", result); + goto retry; + } +out: + if (do_autopm) + usb_autopm_put_interface(i2400mu->usb_iface); + d_fnend(4, dev, "(i2400mu %p) = %p\n", i2400mu, rx_skb); + return rx_skb; + +error_reset: + dev_err(dev, "RX: maximum errors in URB exceeded; " + "resetting device\n"); + usb_queue_reset_device(i2400mu->usb_iface); + rx_skb = ERR_PTR(result); + goto out; +} + + +/* + * Kernel thread for USB reception of data + * + * This thread waits for a kick; once kicked, it will allocate an skb + * and receive a single message to it from USB (using + * i2400mu_rx()). Once received, it is passed to the generic i2400m RX + * code for processing. + * + * When done processing, it runs some dirty statistics to verify if + * the last 100 messages received were smaller than half of the + * current RX buffer size. In that case, the RX buffer size is + * halved. This will helps lowering the pressure on the memory + * allocator. + * + * Hard errors force the thread to exit. + */ +static +int i2400mu_rxd(void *_i2400mu) +{ + int result = 0; + struct i2400mu *i2400mu = _i2400mu; + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = &i2400mu->usb_iface->dev; + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + size_t pending; + int rx_size; + struct sk_buff *rx_skb; + + d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); + while (1) { + d_printf(2, dev, "TX: waiting for messages\n"); + pending = 0; + wait_event_interruptible( + i2400mu->rx_wq, + (kthread_should_stop() /* check this first! */ + || (pending = atomic_read(&i2400mu->rx_pending_count))) + ); + if (kthread_should_stop()) + break; + if (pending == 0) + continue; + rx_size = i2400mu->rx_size; + d_printf(2, dev, "RX: reading up to %d bytes\n", rx_size); + rx_skb = __netdev_alloc_skb(net_dev, rx_size, GFP_KERNEL); + if (rx_skb == NULL) { + dev_err(dev, "RX: can't allocate skb [%d bytes]\n", + rx_size); + msleep(50); /* give it some time? */ + continue; + } + + /* Receive the message with the payloads */ + rx_skb = i2400mu_rx(i2400mu, rx_skb); + result = PTR_ERR(rx_skb); + if (IS_ERR(rx_skb)) + goto out; + atomic_dec(&i2400mu->rx_pending_count); + if (rx_skb->len == 0) { /* some ignorable condition */ + kfree_skb(rx_skb); + continue; + } + + /* Deliver the message to the generic i2400m code */ + i2400mu->rx_size_cnt++; + i2400mu->rx_size_acc += rx_skb->len; + result = i2400m_rx(i2400m, rx_skb); + if (result == -EIO + && edc_inc(&i2400mu->urb_edc, + EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + goto error_reset; + } + + /* Maybe adjust RX buffer size */ + i2400mu_rx_size_maybe_shrink(i2400mu); + } + result = 0; +out: + d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result); + return result; + +error_reset: + dev_err(dev, "RX: maximum errors in received buffer exceeded; " + "resetting device\n"); + usb_queue_reset_device(i2400mu->usb_iface); + goto out; +} + + +/* + * Start reading from the device + * + * @i2400m: device instance + * + * Notify the RX thread that there is data pending. + */ +void i2400mu_rx_kick(struct i2400mu *i2400mu) +{ + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = &i2400mu->usb_iface->dev; + + d_fnstart(3, dev, "(i2400mu %p)\n", i2400m); + atomic_inc(&i2400mu->rx_pending_count); + wake_up_all(&i2400mu->rx_wq); + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); +} + + +int i2400mu_rx_setup(struct i2400mu *i2400mu) +{ + int result = 0; + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = &i2400mu->usb_iface->dev; + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + + i2400mu->rx_kthread = kthread_run(i2400mu_rxd, i2400mu, "%s-rx", + wimax_dev->name); + if (IS_ERR(i2400mu->rx_kthread)) { + result = PTR_ERR(i2400mu->rx_kthread); + dev_err(dev, "RX: cannot start thread: %d\n", result); + } + return result; +} + +void i2400mu_rx_release(struct i2400mu *i2400mu) +{ + kthread_stop(i2400mu->rx_kthread); +} + diff --git a/drivers/net/wimax/i2400m/usb-tx.c b/drivers/net/wimax/i2400m/usb-tx.c new file mode 100644 index 000000000000..dfd893356f49 --- /dev/null +++ b/drivers/net/wimax/i2400m/usb-tx.c @@ -0,0 +1,229 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * USB specific TX handling + * + * + * Copyright (C) 2007-2008 Intel Corporation. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * + * Intel Corporation <linux-wimax@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.com> + * - Initial implementation + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * - Split transport/device specific + * + * + * Takes the TX messages in the i2400m's driver TX FIFO and sends them + * to the device until there are no more. + * + * If we fail sending the message, we just drop it. There isn't much + * we can do at this point. We could also retry, but the USB stack has + * already retried and still failed, so there is not much of a + * point. As well, most of the traffic is network, which has recovery + * methods for dropped packets. + * + * For sending we just obtain a FIFO buffer to send, send it to the + * USB bulk out, tell the TX FIFO code we have sent it; query for + * another one, etc... until done. + * + * We use a thread so we can call usb_autopm_enable() and + * usb_autopm_disable() for each transaction; this way when the device + * goes idle, it will suspend. It also has less overhead than a + * dedicated workqueue, as it is being used for a single task. + * + * ROADMAP + * + * i2400mu_tx_setup() + * i2400mu_tx_release() + * + * i2400mu_bus_tx_kick() - Called by the tx.c code when there + * is new data in the FIFO. + * i2400mu_txd() + * i2400m_tx_msg_get() + * i2400m_tx_msg_sent() + */ +#include "i2400m-usb.h" + + +#define D_SUBMODULE tx +#include "usb-debug-levels.h" + + +/* + * Get the next TX message in the TX FIFO and send it to the device + * + * Note that any iteration consumes a message to be sent, no matter if + * it succeeds or fails (we have no real way to retry or complain). + * + * Return: 0 if ok, < 0 errno code on hard error. + */ +static +int i2400mu_tx(struct i2400mu *i2400mu, struct i2400m_msg_hdr *tx_msg, + size_t tx_msg_size) +{ + int result = 0; + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = &i2400mu->usb_iface->dev; + int usb_pipe, sent_size, do_autopm; + struct usb_endpoint_descriptor *epd; + + d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); + do_autopm = atomic_read(&i2400mu->do_autopm); + result = do_autopm ? + usb_autopm_get_interface(i2400mu->usb_iface) : 0; + if (result < 0) { + dev_err(dev, "TX: can't get autopm: %d\n", result); + do_autopm = 0; + } + epd = usb_get_epd(i2400mu->usb_iface, I2400MU_EP_BULK_OUT); + usb_pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); +retry: + result = usb_bulk_msg(i2400mu->usb_dev, usb_pipe, + tx_msg, tx_msg_size, &sent_size, HZ); + usb_mark_last_busy(i2400mu->usb_dev); + switch (result) { + case 0: + if (sent_size != tx_msg_size) { /* Too short? drop it */ + dev_err(dev, "TX: short write (%d B vs %zu " + "expected)\n", sent_size, tx_msg_size); + result = -EIO; + } + break; + case -EINVAL: /* while removing driver */ + case -ENODEV: /* dev disconnect ... */ + case -ENOENT: /* just ignore it */ + case -ESHUTDOWN: /* and exit */ + case -ECONNRESET: + result = -ESHUTDOWN; + break; + default: /* Some error? */ + if (edc_inc(&i2400mu->urb_edc, + EDC_MAX_ERRORS, EDC_ERROR_TIMEFRAME)) { + dev_err(dev, "TX: maximum errors in URB " + "exceeded; resetting device\n"); + usb_queue_reset_device(i2400mu->usb_iface); + } else { + dev_err(dev, "TX: cannot send URB; retrying. " + "tx_msg @%zu %zu B [%d sent]: %d\n", + (void *) tx_msg - i2400m->tx_buf, + tx_msg_size, sent_size, result); + goto retry; + } + } + if (do_autopm) + usb_autopm_put_interface(i2400mu->usb_iface); + d_fnend(4, dev, "(i2400mu %p) = result\n", i2400mu); + return result; +} + + +/* + * Get the next TX message in the TX FIFO and send it to the device + * + * Note we exit the loop if i2400mu_tx() fails; that funtion only + * fails on hard error (failing to tx a buffer not being one of them, + * see its doc). + * + * Return: 0 + */ +static +int i2400mu_txd(void *_i2400mu) +{ + int result = 0; + struct i2400mu *i2400mu = _i2400mu; + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = &i2400mu->usb_iface->dev; + struct i2400m_msg_hdr *tx_msg; + size_t tx_msg_size; + + d_fnstart(4, dev, "(i2400mu %p)\n", i2400mu); + + while (1) { + d_printf(2, dev, "TX: waiting for messages\n"); + tx_msg = NULL; + wait_event_interruptible( + i2400mu->tx_wq, + (kthread_should_stop() /* check this first! */ + || (tx_msg = i2400m_tx_msg_get(i2400m, &tx_msg_size))) + ); + if (kthread_should_stop()) + break; + WARN_ON(tx_msg == NULL); /* should not happen...*/ + d_printf(2, dev, "TX: submitting %zu bytes\n", tx_msg_size); + d_dump(5, dev, tx_msg, tx_msg_size); + /* Yeah, we ignore errors ... not much we can do */ + i2400mu_tx(i2400mu, tx_msg, tx_msg_size); + i2400m_tx_msg_sent(i2400m); /* ack it, advance the FIFO */ + if (result < 0) + break; + } + d_fnend(4, dev, "(i2400mu %p) = %d\n", i2400mu, result); + return result; +} + + +/* + * i2400m TX engine notifies us that there is data in the FIFO ready + * for TX + * + * If there is a URB in flight, don't do anything; when it finishes, + * it will see there is data in the FIFO and send it. Else, just + * submit a write. + */ +void i2400mu_bus_tx_kick(struct i2400m *i2400m) +{ + struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m); + struct device *dev = &i2400mu->usb_iface->dev; + + d_fnstart(3, dev, "(i2400m %p) = void\n", i2400m); + wake_up_all(&i2400mu->tx_wq); + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); +} + + +int i2400mu_tx_setup(struct i2400mu *i2400mu) +{ + int result = 0; + struct i2400m *i2400m = &i2400mu->i2400m; + struct device *dev = &i2400mu->usb_iface->dev; + struct wimax_dev *wimax_dev = &i2400m->wimax_dev; + + i2400mu->tx_kthread = kthread_run(i2400mu_txd, i2400mu, "%s-tx", + wimax_dev->name); + if (IS_ERR(i2400mu->tx_kthread)) { + result = PTR_ERR(i2400mu->tx_kthread); + dev_err(dev, "TX: cannot start thread: %d\n", result); + } + return result; +} + +void i2400mu_tx_release(struct i2400mu *i2400mu) +{ + kthread_stop(i2400mu->tx_kthread); +} diff --git a/drivers/net/wimax/i2400m/usb.c b/drivers/net/wimax/i2400m/usb.c new file mode 100644 index 000000000000..6d4b65fd9c17 --- /dev/null +++ b/drivers/net/wimax/i2400m/usb.c @@ -0,0 +1,591 @@ +/* + * Intel Wireless WiMAX Connection 2400m + * Linux driver model glue for USB device, reset & fw upload + * + * + * Copyright (C) 2007-2008 Intel Corporation <linux-wimax@intel.com> + * Inaky Perez-Gonzalez <inaky.perez-gonzalez@intel.com> + * Yanir Lubetkin <yanirx.lubetkin@intel.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. + * + * 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. + * + * + * See i2400m-usb.h for a general description of this driver. + * + * This file implements driver model glue, and hook ups for the + * generic driver to implement the bus-specific functions (device + * communication setup/tear down, firmware upload and resetting). + * + * ROADMAP + * + * i2400mu_probe() + * alloc_netdev()... + * i2400mu_netdev_setup() + * i2400mu_init() + * i2400m_netdev_setup() + * i2400m_setup()... + * + * i2400mu_disconnect + * i2400m_release() + * free_netdev() + * + * i2400mu_suspend() + * i2400m_cmd_enter_powersave() + * i2400mu_notification_release() + * + * i2400mu_resume() + * i2400mu_notification_setup() + * + * i2400mu_bus_dev_start() Called by i2400m_dev_start() [who is + * i2400mu_tx_setup() called by i2400m_setup()] + * i2400mu_rx_setup() + * i2400mu_notification_setup() + * + * i2400mu_bus_dev_stop() Called by i2400m_dev_stop() [who is + * i2400mu_notification_release() called by i2400m_release()] + * i2400mu_rx_release() + * i2400mu_tx_release() + * + * i2400mu_bus_reset() Called by i2400m->bus_reset + * __i2400mu_reset() + * __i2400mu_send_barker() + * usb_reset_device() + */ +#include "i2400m-usb.h" +#include <linux/wimax/i2400m.h> +#include <linux/debugfs.h> + + +#define D_SUBMODULE usb +#include "usb-debug-levels.h" + + +/* Our firmware file name */ +#define I2400MU_FW_FILE_NAME "i2400m-fw-usb-" I2400M_FW_VERSION ".sbcf" + +static +int i2400mu_bus_dev_start(struct i2400m *i2400m) +{ + int result; + struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m); + struct device *dev = &i2400mu->usb_iface->dev; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + result = i2400mu_tx_setup(i2400mu); + if (result < 0) + goto error_usb_tx_setup; + result = i2400mu_rx_setup(i2400mu); + if (result < 0) + goto error_usb_rx_setup; + result = i2400mu_notification_setup(i2400mu); + if (result < 0) + goto error_notif_setup; + d_fnend(3, dev, "(i2400m %p) = %d\n", i2400m, result); + return result; + +error_notif_setup: + i2400mu_rx_release(i2400mu); +error_usb_rx_setup: + i2400mu_tx_release(i2400mu); +error_usb_tx_setup: + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); + return result; +} + + +static +void i2400mu_bus_dev_stop(struct i2400m *i2400m) +{ + struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m); + struct device *dev = &i2400mu->usb_iface->dev; + + d_fnstart(3, dev, "(i2400m %p)\n", i2400m); + i2400mu_notification_release(i2400mu); + i2400mu_rx_release(i2400mu); + i2400mu_tx_release(i2400mu); + d_fnend(3, dev, "(i2400m %p) = void\n", i2400m); +} + + +/* + * Sends a barker buffer to the device + * + * This helper will allocate a kmalloced buffer and use it to transmit + * (then free it). Reason for this is that other arches cannot use + * stack/vmalloc/text areas for DMA transfers. + * + * Error recovery here is simpler: anything is considered a hard error + * and will move the reset code to use a last-resort bus-based reset. + */ +static +int __i2400mu_send_barker(struct i2400mu *i2400mu, + const __le32 *barker, + size_t barker_size, + unsigned endpoint) +{ + struct usb_endpoint_descriptor *epd = NULL; + int pipe, actual_len, ret; + struct device *dev = &i2400mu->usb_iface->dev; + void *buffer; + int do_autopm = 1; + + ret = usb_autopm_get_interface(i2400mu->usb_iface); + if (ret < 0) { + dev_err(dev, "RESET: can't get autopm: %d\n", ret); + do_autopm = 0; + } + ret = -ENOMEM; + buffer = kmalloc(barker_size, GFP_KERNEL); + if (buffer == NULL) + goto error_kzalloc; + epd = usb_get_epd(i2400mu->usb_iface, endpoint); + pipe = usb_sndbulkpipe(i2400mu->usb_dev, epd->bEndpointAddress); + memcpy(buffer, barker, barker_size); + ret = usb_bulk_msg(i2400mu->usb_dev, pipe, buffer, barker_size, + &actual_len, HZ); + if (ret < 0) { + if (ret != -EINVAL) + dev_err(dev, "E: barker error: %d\n", ret); + } else if (actual_len != barker_size) { + dev_err(dev, "E: only %d bytes transmitted\n", actual_len); + ret = -EIO; + } + kfree(buffer); +error_kzalloc: + if (do_autopm) + usb_autopm_put_interface(i2400mu->usb_iface); + return ret; +} + + +/* + * Reset a device at different levels (warm, cold or bus) + * + * @i2400m: device descriptor + * @reset_type: soft, warm or bus reset (I2400M_RT_WARM/SOFT/BUS) + * + * Warm and cold resets get a USB reset if they fail. + * + * Warm reset: + * + * The device will be fully reset internally, but won't be + * disconnected from the USB bus (so no reenumeration will + * happen). Firmware upload will be neccessary. + * + * The device will send a reboot barker in the notification endpoint + * that will trigger the driver to reinitialize the state + * automatically from notif.c:i2400m_notification_grok() into + * i2400m_dev_bootstrap_delayed(). + * + * Cold and bus (USB) reset: + * + * The device will be fully reset internally, disconnected from the + * USB bus an a reenumeration will happen. Firmware upload will be + * neccessary. Thus, we don't do any locking or struct + * reinitialization, as we are going to be fully disconnected and + * reenumerated. + * + * Note we need to return -ENODEV if a warm reset was requested and we + * had to resort to a bus reset. See i2400m_op_reset(), wimax_reset() + * and wimax_dev->op_reset. + * + * WARNING: no driver state saved/fixed + */ +static +int i2400mu_bus_reset(struct i2400m *i2400m, enum i2400m_reset_type rt) +{ + int result; + struct i2400mu *i2400mu = + container_of(i2400m, struct i2400mu, i2400m); + struct device *dev = i2400m_dev(i2400m); + static const __le32 i2400m_WARM_BOOT_BARKER[4] = { + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + __constant_cpu_to_le32(I2400M_WARM_RESET_BARKER), + }; + static const __le32 i2400m_COLD_BOOT_BARKER[4] = { + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + __constant_cpu_to_le32(I2400M_COLD_RESET_BARKER), + }; + + d_fnstart(3, dev, "(i2400m %p rt %u)\n", i2400m, rt); + if (rt == I2400M_RT_WARM) + result = __i2400mu_send_barker(i2400mu, i2400m_WARM_BOOT_BARKER, + sizeof(i2400m_WARM_BOOT_BARKER), + I2400MU_EP_BULK_OUT); + else if (rt == I2400M_RT_COLD) + result = __i2400mu_send_barker(i2400mu, i2400m_COLD_BOOT_BARKER, + sizeof(i2400m_COLD_BOOT_BARKER), + I2400MU_EP_RESET_COLD); + else if (rt == I2400M_RT_BUS) { +do_bus_reset: + result = usb_reset_device(i2400mu->usb_dev); + switch (result) { + case 0: + case -EINVAL: /* device is gone */ + case -ENODEV: + case -ENOENT: + case -ESHUTDOWN: + result = rt == I2400M_RT_WARM ? -ENODEV : 0; + break; /* We assume the device is disconnected */ + default: + dev_err(dev, "USB reset failed (%d), giving up!\n", + result); + } + } else + BUG(); + if (result < 0 + && result != -EINVAL /* device is gone */ + && rt != I2400M_RT_BUS) { + dev_err(dev, "%s reset failed (%d); trying USB reset\n", + rt == I2400M_RT_WARM ? "warm" : "cold", result); + rt = I2400M_RT_BUS; + goto do_bus_reset; + } + d_fnend(3, dev, "(i2400m %p rt %u) = %d\n", i2400m, rt, result); + return result; +} + + +static +void i2400mu_netdev_setup(struct net_device *net_dev) +{ + struct i2400m *i2400m = net_dev_to_i2400m(net_dev); + struct i2400mu *i2400mu = container_of(i2400m, struct i2400mu, i2400m); + i2400mu_init(i2400mu); + i2400m_netdev_setup(net_dev); +} + + +/* + * Debug levels control; see debug.h + */ +struct d_level D_LEVEL[] = { + D_SUBMODULE_DEFINE(usb), + D_SUBMODULE_DEFINE(fw), + D_SUBMODULE_DEFINE(notif), + D_SUBMODULE_DEFINE(rx), + D_SUBMODULE_DEFINE(tx), +}; +size_t D_LEVEL_SIZE = ARRAY_SIZE(D_LEVEL); + + +#define __debugfs_register(prefix, name, parent) \ +do { \ + result = d_level_register_debugfs(prefix, name, parent); \ + if (result < 0) \ + goto error; \ +} while (0) + + +static +int i2400mu_debugfs_add(struct i2400mu *i2400mu) +{ + int result; + struct device *dev = &i2400mu->usb_iface->dev; + struct dentry *dentry = i2400mu->i2400m.wimax_dev.debugfs_dentry; + struct dentry *fd; + + dentry = debugfs_create_dir("i2400m-usb", dentry); + result = PTR_ERR(dentry); + if (IS_ERR(dentry)) { + if (result == -ENODEV) + result = 0; /* No debugfs support */ + goto error; + } + i2400mu->debugfs_dentry = dentry; + __debugfs_register("dl_", usb, dentry); + __debugfs_register("dl_", fw, dentry); + __debugfs_register("dl_", notif, dentry); + __debugfs_register("dl_", rx, dentry); + __debugfs_register("dl_", tx, dentry); + + /* Don't touch these if you don't know what you are doing */ + fd = debugfs_create_u8("rx_size_auto_shrink", 0600, dentry, + &i2400mu->rx_size_auto_shrink); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "rx_size_auto_shrink: %d\n", result); + goto error; + } + + fd = debugfs_create_size_t("rx_size", 0600, dentry, + &i2400mu->rx_size); + result = PTR_ERR(fd); + if (IS_ERR(fd) && result != -ENODEV) { + dev_err(dev, "Can't create debugfs entry " + "rx_size: %d\n", result); + goto error; + } + + return 0; + +error: + debugfs_remove_recursive(i2400mu->debugfs_dentry); + return result; +} + + +/* + * Probe a i2400m interface and register it + * + * @iface: USB interface to link to + * @id: USB class/subclass/protocol id + * @returns: 0 if ok, < 0 errno code on error. + * + * Alloc a net device, initialize the bus-specific details and then + * calls the bus-generic initialization routine. That will register + * the wimax and netdev devices, upload the firmware [using + * _bus_bm_*()], call _bus_dev_start() to finalize the setup of the + * communication with the device and then will start to talk to it to + * finnish setting it up. + */ +static +int i2400mu_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + int result; + struct net_device *net_dev; + struct device *dev = &iface->dev; + struct i2400m *i2400m; + struct i2400mu *i2400mu; + struct usb_device *usb_dev = interface_to_usbdev(iface); + + if (usb_dev->speed != USB_SPEED_HIGH) + dev_err(dev, "device not connected as high speed\n"); + + /* Allocate instance [calls i2400m_netdev_setup() on it]. */ + result = -ENOMEM; + net_dev = alloc_netdev(sizeof(*i2400mu), "wmx%d", + i2400mu_netdev_setup); + if (net_dev == NULL) { + dev_err(dev, "no memory for network device instance\n"); + goto error_alloc_netdev; + } + SET_NETDEV_DEV(net_dev, dev); + i2400m = net_dev_to_i2400m(net_dev); + i2400mu = container_of(i2400m, struct i2400mu, i2400m); + i2400m->wimax_dev.net_dev = net_dev; + i2400mu->usb_dev = usb_get_dev(usb_dev); + i2400mu->usb_iface = iface; + usb_set_intfdata(iface, i2400mu); + + i2400m->bus_tx_block_size = I2400MU_BLK_SIZE; + i2400m->bus_pl_size_max = I2400MU_PL_SIZE_MAX; + i2400m->bus_dev_start = i2400mu_bus_dev_start; + i2400m->bus_dev_stop = i2400mu_bus_dev_stop; + i2400m->bus_tx_kick = i2400mu_bus_tx_kick; + i2400m->bus_reset = i2400mu_bus_reset; + i2400m->bus_bm_cmd_send = i2400mu_bus_bm_cmd_send; + i2400m->bus_bm_wait_for_ack = i2400mu_bus_bm_wait_for_ack; + i2400m->bus_fw_name = I2400MU_FW_FILE_NAME; + i2400m->bus_bm_mac_addr_impaired = 0; + + iface->needs_remote_wakeup = 1; /* autosuspend (15s delay) */ + device_init_wakeup(dev, 1); + usb_autopm_enable(i2400mu->usb_iface); + usb_dev->autosuspend_delay = 15 * HZ; + usb_dev->autosuspend_disabled = 0; + + result = i2400m_setup(i2400m, I2400M_BRI_MAC_REINIT); + if (result < 0) { + dev_err(dev, "cannot setup device: %d\n", result); + goto error_setup; + } + result = i2400mu_debugfs_add(i2400mu); + if (result < 0) { + dev_err(dev, "Can't register i2400mu's debugfs: %d\n", result); + goto error_debugfs_add; + } + return 0; + +error_debugfs_add: + i2400m_release(i2400m); +error_setup: + usb_set_intfdata(iface, NULL); + usb_put_dev(i2400mu->usb_dev); + free_netdev(net_dev); +error_alloc_netdev: + return result; +} + + +/* + * Disconect a i2400m from the system. + * + * i2400m_stop() has been called before, so al the rx and tx contexts + * have been taken down already. Make sure the queue is stopped, + * unregister netdev and i2400m, free and kill. + */ +static +void i2400mu_disconnect(struct usb_interface *iface) +{ + struct i2400mu *i2400mu = usb_get_intfdata(iface); + struct i2400m *i2400m = &i2400mu->i2400m; + struct net_device *net_dev = i2400m->wimax_dev.net_dev; + struct device *dev = &iface->dev; + + d_fnstart(3, dev, "(iface %p i2400m %p)\n", iface, i2400m); + + debugfs_remove_recursive(i2400mu->debugfs_dentry); + i2400m_release(i2400m); + usb_set_intfdata(iface, NULL); + usb_put_dev(i2400mu->usb_dev); + free_netdev(net_dev); + d_fnend(3, dev, "(iface %p i2400m %p) = void\n", iface, i2400m); +} + + +/* + * Get the device ready for USB port or system standby and hibernation + * + * USB port and system standby are handled the same. + * + * When the system hibernates, the USB device is powered down and then + * up, so we don't really have to do much here, as it will be seen as + * a reconnect. Still for simplicity we consider this case the same as + * suspend, so that the device has a chance to do notify the base + * station (if connected). + * + * So at the end, the three cases require common handling. + * + * If at the time of this call the device's firmware is not loaded, + * nothing has to be done. + * + * If the firmware is loaded, we need to: + * + * - tell the device to go into host interface power save mode, wait + * for it to ack + * + * This is quite more interesting than it is; we need to execute a + * command, but this time, we don't want the code in usb-{tx,rx}.c + * to call the usb_autopm_get/put_interface() barriers as it'd + * deadlock, so we need to decrement i2400mu->do_autopm, that acts + * as a poor man's semaphore. Ugly, but it works. + * + * As well, the device might refuse going to sleep for whichever + * reason. In this case we just fail. For system suspend/hibernate, + * we *can't* fail. We look at usb_dev->auto_pm to see if the + * suspend call comes from the USB stack or from the system and act + * in consequence. + * + * - stop the notification endpoint polling + */ +static +int i2400mu_suspend(struct usb_interface *iface, pm_message_t pm_msg) +{ + int result = 0; + struct device *dev = &iface->dev; + struct i2400mu *i2400mu = usb_get_intfdata(iface); + struct usb_device *usb_dev = i2400mu->usb_dev; + struct i2400m *i2400m = &i2400mu->i2400m; + + d_fnstart(3, dev, "(iface %p pm_msg %u)\n", iface, pm_msg.event); + if (i2400m->updown == 0) + goto no_firmware; + d_printf(1, dev, "fw up, requesting standby\n"); + atomic_dec(&i2400mu->do_autopm); + result = i2400m_cmd_enter_powersave(i2400m); + atomic_inc(&i2400mu->do_autopm); + if (result < 0 && usb_dev->auto_pm == 0) { + /* System suspend, can't fail */ + dev_err(dev, "failed to suspend, will reset on resume\n"); + result = 0; + } + if (result < 0) + goto error_enter_powersave; + i2400mu_notification_release(i2400mu); + d_printf(1, dev, "fw up, got standby\n"); +error_enter_powersave: +no_firmware: + d_fnend(3, dev, "(iface %p pm_msg %u) = %d\n", + iface, pm_msg.event, result); + return result; +} + + +static +int i2400mu_resume(struct usb_interface *iface) +{ + int ret = 0; + struct device *dev = &iface->dev; + struct i2400mu *i2400mu = usb_get_intfdata(iface); + struct i2400m *i2400m = &i2400mu->i2400m; + + d_fnstart(3, dev, "(iface %p)\n", iface); + if (i2400m->updown == 0) { + d_printf(1, dev, "fw was down, no resume neeed\n"); + goto out; + } + d_printf(1, dev, "fw was up, resuming\n"); + i2400mu_notification_setup(i2400mu); + /* USB has flow control, so we don't need to give it time to + * come back; otherwise, we'd use something like a get-state + * command... */ +out: + d_fnend(3, dev, "(iface %p) = %d\n", iface, ret); + return ret; +} + + +static +struct usb_device_id i2400mu_id_table[] = { + { USB_DEVICE(0x8086, 0x0181) }, + { USB_DEVICE(0x8086, 0x1403) }, + { USB_DEVICE(0x8086, 0x1405) }, + { USB_DEVICE(0x8086, 0x0180) }, + { USB_DEVICE(0x8086, 0x0182) }, + { USB_DEVICE(0x8086, 0x1406) }, + { USB_DEVICE(0x8086, 0x1403) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, i2400mu_id_table); + + +static +struct usb_driver i2400mu_driver = { + .name = KBUILD_MODNAME, + .suspend = i2400mu_suspend, + .resume = i2400mu_resume, + .probe = i2400mu_probe, + .disconnect = i2400mu_disconnect, + .id_table = i2400mu_id_table, + .supports_autosuspend = 1, +}; + +static +int __init i2400mu_driver_init(void) +{ + return usb_register(&i2400mu_driver); +} +module_init(i2400mu_driver_init); + + +static +void __exit i2400mu_driver_exit(void) +{ + flush_scheduled_work(); /* for the stuff we schedule from sysfs.c */ + usb_deregister(&i2400mu_driver); +} +module_exit(i2400mu_driver_exit); + +MODULE_AUTHOR("Intel Corporation <linux-wimax@intel.com>"); +MODULE_DESCRIPTION("Intel 2400M WiMAX networking for USB"); +MODULE_LICENSE("GPL"); +MODULE_FIRMWARE(I2400MU_FW_FILE_NAME); diff --git a/drivers/net/wireless/atmel.c b/drivers/net/wireless/atmel.c index 350157fcd080..4223672c4432 100644 --- a/drivers/net/wireless/atmel.c +++ b/drivers/net/wireless/atmel.c @@ -3836,7 +3836,7 @@ static int reset_atmel_card(struct net_device *dev) This routine is also responsible for initialising some hardware-specific fields in the atmel_private structure, including a copy of the firmware's hostinfo stucture - which is the route into the rest of the firmare datastructures. */ + which is the route into the rest of the firmware datastructures. */ struct atmel_private *priv = netdev_priv(dev); u8 configuration; diff --git a/drivers/net/wireless/ipw2x00/ipw2100.c b/drivers/net/wireless/ipw2x00/ipw2100.c index 1667065b86a7..823c2bf5e31e 100644 --- a/drivers/net/wireless/ipw2x00/ipw2100.c +++ b/drivers/net/wireless/ipw2x00/ipw2100.c @@ -1332,7 +1332,7 @@ static int ipw2100_power_cycle_adapter(struct ipw2100_priv *priv) IPW_AUX_HOST_RESET_REG_STOP_MASTER); /* Step 2. Wait for stop Master Assert - * (not more then 50us, otherwise ret error */ + * (not more than 50us, otherwise ret error */ i = 5; do { udelay(IPW_WAIT_RESET_MASTER_ASSERT_COMPLETE_DELAY); @@ -1830,7 +1830,7 @@ static void ipw2100_down(struct ipw2100_priv *priv) cancel_delayed_work(&priv->rf_kill); } - /* Kill the firmare hang check timer */ + /* Kill the firmware hang check timer */ if (!priv->stop_hang_check) { priv->stop_hang_check = 1; cancel_delayed_work(&priv->hang_check); diff --git a/drivers/net/wireless/rt2x00/rt2x00crypto.c b/drivers/net/wireless/rt2x00/rt2x00crypto.c index 37ad0d2fb64c..aee9cba13eb3 100644 --- a/drivers/net/wireless/rt2x00/rt2x00crypto.c +++ b/drivers/net/wireless/rt2x00/rt2x00crypto.c @@ -184,8 +184,8 @@ void rt2x00crypto_rx_insert_iv(struct sk_buff *skb, unsigned int align, * Make room for new data, note that we increase both * headsize and tailsize when required. The tailsize is * only needed when ICV data needs to be inserted and - * the padding is smaller then the ICV data. - * When alignment requirements is greater then the + * the padding is smaller than the ICV data. + * When alignment requirements is greater than the * ICV data we must trim the skb to the correct size * because we need to remove the extra bytes. */ diff --git a/drivers/net/wireless/strip.c b/drivers/net/wireless/strip.c index dd0de3a9ed4e..7015f2480550 100644 --- a/drivers/net/wireless/strip.c +++ b/drivers/net/wireless/strip.c @@ -236,7 +236,7 @@ struct strip { unsigned long tx_errors; /* Planned stuff */ unsigned long rx_dropped; /* No memory for skb */ unsigned long tx_dropped; /* When MTU change */ - unsigned long rx_over_errors; /* Frame bigger then STRIP buf. */ + unsigned long rx_over_errors; /* Frame bigger than STRIP buf. */ unsigned long pps_timer; /* Timer to determine pps */ unsigned long rx_pps_count; /* Counter to determine pps */ diff --git a/drivers/rtc/rtc-ds1511.c b/drivers/rtc/rtc-ds1511.c index 23a07fe15a2c..0b6b7730c716 100644 --- a/drivers/rtc/rtc-ds1511.c +++ b/drivers/rtc/rtc-ds1511.c @@ -630,7 +630,7 @@ ds1511_rtc_init(void) static void __exit ds1511_rtc_exit(void) { - return platform_driver_unregister(&ds1511_rtc_driver); + platform_driver_unregister(&ds1511_rtc_driver); } module_init(ds1511_rtc_init); diff --git a/drivers/rtc/rtc-stk17ta8.c b/drivers/rtc/rtc-stk17ta8.c index dc0b6224ad9b..7d1547b0070e 100644 --- a/drivers/rtc/rtc-stk17ta8.c +++ b/drivers/rtc/rtc-stk17ta8.c @@ -399,7 +399,7 @@ static __init int stk17ta8_init(void) static __exit void stk17ta8_exit(void) { - return platform_driver_unregister(&stk17ta8_rtc_driver); + platform_driver_unregister(&stk17ta8_rtc_driver); } module_init(stk17ta8_init); diff --git a/drivers/s390/block/dasd_eer.c b/drivers/s390/block/dasd_eer.c index 892e2878d61b..f8e05ce98621 100644 --- a/drivers/s390/block/dasd_eer.c +++ b/drivers/s390/block/dasd_eer.c @@ -535,8 +535,8 @@ static int dasd_eer_open(struct inode *inp, struct file *filp) eerb->buffer_page_count > INT_MAX / PAGE_SIZE) { kfree(eerb); MESSAGE(KERN_WARNING, "can't open device since module " - "parameter eer_pages is smaller then 1 or" - " bigger then %d", (int)(INT_MAX / PAGE_SIZE)); + "parameter eer_pages is smaller than 1 or" + " bigger than %d", (int)(INT_MAX / PAGE_SIZE)); unlock_kernel(); return -EINVAL; } diff --git a/drivers/s390/char/vmlogrdr.c b/drivers/s390/char/vmlogrdr.c index aabbeb909cc6..d8a2289fcb69 100644 --- a/drivers/s390/char/vmlogrdr.c +++ b/drivers/s390/char/vmlogrdr.c @@ -427,7 +427,7 @@ static int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv) buffer = priv->buffer + sizeof(int); } /* - * If the record is bigger then our buffer, we receive only + * If the record is bigger than our buffer, we receive only * a part of it. We can get the rest later. */ if (iucv_data_count > NET_BUFFER_SIZE) @@ -437,7 +437,7 @@ static int vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv) 0, buffer, iucv_data_count, &priv->residual_length); spin_unlock_bh(&priv->priv_lock); - /* An rc of 5 indicates that the record was bigger then + /* An rc of 5 indicates that the record was bigger than * the buffer, which is OK for us. A 9 indicates that the * record was purged befor we could receive it. */ diff --git a/drivers/scsi/a100u2w.c b/drivers/scsi/a100u2w.c index 3c298c7253ee..964769f66eac 100644 --- a/drivers/scsi/a100u2w.c +++ b/drivers/scsi/a100u2w.c @@ -633,7 +633,7 @@ static int orc_device_reset(struct orc_host * host, struct scsi_cmnd *cmd, unsig return FAILED; } - /* Reset device is handled by the firmare, we fill in an SCB and + /* Reset device is handled by the firmware, we fill in an SCB and fire it at the controller, it does the rest */ scb->opcode = ORC_BUSDEVRST; scb->target = target; diff --git a/drivers/scsi/lpfc/lpfc_hbadisc.c b/drivers/scsi/lpfc/lpfc_hbadisc.c index 8c64494444bf..311ed6dea726 100644 --- a/drivers/scsi/lpfc/lpfc_hbadisc.c +++ b/drivers/scsi/lpfc/lpfc_hbadisc.c @@ -1964,10 +1964,10 @@ lpfc_set_disctmo(struct lpfc_vport *vport) uint32_t tmo; if (vport->port_state == LPFC_LOCAL_CFG_LINK) { - /* For FAN, timeout should be greater then edtov */ + /* For FAN, timeout should be greater than edtov */ tmo = (((phba->fc_edtov + 999) / 1000) + 1); } else { - /* Normal discovery timeout should be > then ELS/CT timeout + /* Normal discovery timeout should be > than ELS/CT timeout * FC spec states we need 3 * ratov for CT requests */ tmo = ((phba->fc_ratov * 3) + 3); diff --git a/drivers/scsi/lpfc/lpfc_sli.c b/drivers/scsi/lpfc/lpfc_sli.c index 01dfdc8696f8..a36a120561e2 100644 --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -420,7 +420,7 @@ lpfc_sli_next_iocb_slot (struct lpfc_hba *phba, struct lpfc_sli_ring *pring) if (unlikely(pring->local_getidx >= max_cmd_idx)) { lpfc_printf_log(phba, KERN_ERR, LOG_SLI, "0315 Ring %d issue: portCmdGet %d " - "is bigger then cmd ring %d\n", + "is bigger than cmd ring %d\n", pring->ringno, pring->local_getidx, max_cmd_idx); @@ -1628,12 +1628,12 @@ lpfc_sli_rsp_pointers_error(struct lpfc_hba *phba, struct lpfc_sli_ring *pring) { struct lpfc_pgp *pgp = &phba->port_gp[pring->ringno]; /* - * Ring <ringno> handler: portRspPut <portRspPut> is bigger then + * Ring <ringno> handler: portRspPut <portRspPut> is bigger than * rsp ring <portRspMax> */ lpfc_printf_log(phba, KERN_ERR, LOG_SLI, "0312 Ring %d handler: portRspPut %d " - "is bigger then rsp ring %d\n", + "is bigger than rsp ring %d\n", pring->ringno, le32_to_cpu(pgp->rspPutInx), pring->numRiocb); @@ -2083,12 +2083,12 @@ lpfc_sli_handle_slow_ring_event(struct lpfc_hba *phba, portRspPut = le32_to_cpu(pgp->rspPutInx); if (portRspPut >= portRspMax) { /* - * Ring <ringno> handler: portRspPut <portRspPut> is bigger then + * Ring <ringno> handler: portRspPut <portRspPut> is bigger than * rsp ring <portRspMax> */ lpfc_printf_log(phba, KERN_ERR, LOG_SLI, "0303 Ring %d handler: portRspPut %d " - "is bigger then rsp ring %d\n", + "is bigger than rsp ring %d\n", pring->ringno, portRspPut, portRspMax); phba->link_state = LPFC_HBA_ERROR; diff --git a/drivers/scsi/megaraid.c b/drivers/scsi/megaraid.c index 7dc62deb4087..9fdcd60c5493 100644 --- a/drivers/scsi/megaraid.c +++ b/drivers/scsi/megaraid.c @@ -1967,8 +1967,8 @@ megaraid_abort_and_reset(adapter_t *adapter, Scsi_Cmnd *cmd, int aor) scb->state |= aor; /* - * Check if this command has firmare owenership. If - * yes, we cannot reset this command. Whenever, f/w + * Check if this command has firmware ownership. If + * yes, we cannot reset this command. Whenever f/w * completes this command, we will return appropriate * status from ISR. */ diff --git a/drivers/scsi/qla1280.c b/drivers/scsi/qla1280.c index 8cb9240596ab..df09820e8916 100644 --- a/drivers/scsi/qla1280.c +++ b/drivers/scsi/qla1280.c @@ -128,7 +128,7 @@ - Integrate ql12160_set_target_parameters() with 1280 version - Make qla1280_setup() non static - Do not call qla1280_check_for_dead_scsi_bus() on every I/O request - sent to the card - this command pauses the firmare!!! + sent to the card - this command pauses the firmware!!! Rev 3.23.15 Beta March 19, 2002, Jes Sorensen - Clean up qla1280.h - remove obsolete QL_DEBUG_LEVEL_x definitions - Remove a pile of pointless and confusing (srb_t **) and @@ -659,7 +659,7 @@ static int qla1280_read_nvram(struct scsi_qla_host *ha) /* The firmware interface is, um, interesting, in that the * actual firmware image on the chip is little endian, thus, * the process of taking that image to the CPU would end up - * little endian. However, the firmare interface requires it + * little endian. However, the firmware interface requires it * to be read a word (two bytes) at a time. * * The net result of this would be that the word (and diff --git a/drivers/scsi/qla4xxx/ql4_mbx.c b/drivers/scsi/qla4xxx/ql4_mbx.c index c577d79bd7e8..051b0f5e8c8e 100644 --- a/drivers/scsi/qla4xxx/ql4_mbx.c +++ b/drivers/scsi/qla4xxx/ql4_mbx.c @@ -392,7 +392,7 @@ int qla4xxx_get_firmware_status(struct scsi_qla_host * ha) ha->iocb_hiwat -= IOCB_HIWAT_CUSHION; else dev_info(&ha->pdev->dev, "WARNING!!! You have less than %d " - "firmare IOCBs available (%d).\n", + "firmware IOCBs available (%d).\n", IOCB_HIWAT_CUSHION, ha->iocb_hiwat); return QLA_SUCCESS; diff --git a/drivers/scsi/scsi_error.c b/drivers/scsi/scsi_error.c index 381838ebd460..d86ebea9350a 100644 --- a/drivers/scsi/scsi_error.c +++ b/drivers/scsi/scsi_error.c @@ -1650,7 +1650,7 @@ int scsi_error_handler(void *data) * We use TASK_INTERRUPTIBLE so that the thread is not * counted against the load average as a running process. * We never actually get interrupted because kthread_run - * disables singal delivery for the created thread. + * disables signal delivery for the created thread. */ set_current_state(TASK_INTERRUPTIBLE); while (!kthread_should_stop()) { diff --git a/drivers/scsi/scsi_scan.c b/drivers/scsi/scsi_scan.c index 18486b51668d..17914a346f71 100644 --- a/drivers/scsi/scsi_scan.c +++ b/drivers/scsi/scsi_scan.c @@ -32,6 +32,7 @@ #include <linux/delay.h> #include <linux/kthread.h> #include <linux/spinlock.h> +#include <linux/async.h> #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> @@ -179,6 +180,8 @@ int scsi_complete_async_scans(void) spin_unlock(&async_scan_lock); kfree(data); + /* Synchronize async operations globally */ + async_synchronize_full(); return 0; } diff --git a/drivers/scsi/sd.c b/drivers/scsi/sd.c index 62b28d58e65e..e035c1114010 100644 --- a/drivers/scsi/sd.c +++ b/drivers/scsi/sd.c @@ -48,6 +48,7 @@ #include <linux/delay.h> #include <linux/mutex.h> #include <linux/string_helpers.h> +#include <linux/async.h> #include <asm/uaccess.h> #include <scsi/scsi.h> @@ -1802,6 +1803,71 @@ static int sd_format_disk_name(char *prefix, int index, char *buf, int buflen) return 0; } +/* + * The asynchronous part of sd_probe + */ +static void sd_probe_async(void *data, async_cookie_t cookie) +{ + struct scsi_disk *sdkp = data; + struct scsi_device *sdp; + struct gendisk *gd; + u32 index; + struct device *dev; + + sdp = sdkp->device; + gd = sdkp->disk; + index = sdkp->index; + dev = &sdp->sdev_gendev; + + if (!sdp->request_queue->rq_timeout) { + if (sdp->type != TYPE_MOD) + blk_queue_rq_timeout(sdp->request_queue, SD_TIMEOUT); + else + blk_queue_rq_timeout(sdp->request_queue, + SD_MOD_TIMEOUT); + } + + device_initialize(&sdkp->dev); + sdkp->dev.parent = &sdp->sdev_gendev; + sdkp->dev.class = &sd_disk_class; + strncpy(sdkp->dev.bus_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE); + + if (device_add(&sdkp->dev)) + goto out_free_index; + + get_device(&sdp->sdev_gendev); + + if (index < SD_MAX_DISKS) { + gd->major = sd_major((index & 0xf0) >> 4); + gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00); + gd->minors = SD_MINORS; + } + gd->fops = &sd_fops; + gd->private_data = &sdkp->driver; + gd->queue = sdkp->device->request_queue; + + sd_revalidate_disk(gd); + + blk_queue_prep_rq(sdp->request_queue, sd_prep_fn); + + gd->driverfs_dev = &sdp->sdev_gendev; + gd->flags = GENHD_FL_EXT_DEVT | GENHD_FL_DRIVERFS; + if (sdp->removable) + gd->flags |= GENHD_FL_REMOVABLE; + + dev_set_drvdata(dev, sdkp); + add_disk(gd); + sd_dif_config_host(sdkp); + + sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n", + sdp->removable ? "removable " : ""); + + return; + + out_free_index: + ida_remove(&sd_index_ida, index); +} + /** * sd_probe - called during driver initialization and whenever a * new scsi device is attached to the system. It is called once @@ -1865,48 +1931,7 @@ static int sd_probe(struct device *dev) sdkp->openers = 0; sdkp->previous_state = 1; - if (!sdp->request_queue->rq_timeout) { - if (sdp->type != TYPE_MOD) - blk_queue_rq_timeout(sdp->request_queue, SD_TIMEOUT); - else - blk_queue_rq_timeout(sdp->request_queue, - SD_MOD_TIMEOUT); - } - - device_initialize(&sdkp->dev); - sdkp->dev.parent = &sdp->sdev_gendev; - sdkp->dev.class = &sd_disk_class; - strncpy(sdkp->dev.bus_id, sdp->sdev_gendev.bus_id, BUS_ID_SIZE); - - if (device_add(&sdkp->dev)) - goto out_free_index; - - get_device(&sdp->sdev_gendev); - - if (index < SD_MAX_DISKS) { - gd->major = sd_major((index & 0xf0) >> 4); - gd->first_minor = ((index & 0xf) << 4) | (index & 0xfff00); - gd->minors = SD_MINORS; - } - gd->fops = &sd_fops; - gd->private_data = &sdkp->driver; - gd->queue = sdkp->device->request_queue; - - sd_revalidate_disk(gd); - - blk_queue_prep_rq(sdp->request_queue, sd_prep_fn); - - gd->driverfs_dev = &sdp->sdev_gendev; - gd->flags = GENHD_FL_EXT_DEVT | GENHD_FL_DRIVERFS; - if (sdp->removable) - gd->flags |= GENHD_FL_REMOVABLE; - - dev_set_drvdata(dev, sdkp); - add_disk(gd); - sd_dif_config_host(sdkp); - - sd_printk(KERN_NOTICE, sdkp, "Attached SCSI %sdisk\n", - sdp->removable ? "removable " : ""); + async_schedule(sd_probe_async, sdkp); return 0; diff --git a/drivers/serial/8250.c b/drivers/serial/8250.c index daa00567bc44..1889a63ebc22 100644 --- a/drivers/serial/8250.c +++ b/drivers/serial/8250.c @@ -3123,7 +3123,7 @@ static int __init serial8250_init(void) if (nr_uarts > UART_NR) nr_uarts = UART_NR; - printk(KERN_INFO "Serial: 8250/16550 driver" + printk(KERN_INFO "Serial: 8250/16550 driver, " "%d ports, IRQ sharing %sabled\n", nr_uarts, share_irqs ? "en" : "dis"); diff --git a/drivers/serial/crisv10.c b/drivers/serial/crisv10.c index 8b2c619a09f2..e642c22c80e2 100644 --- a/drivers/serial/crisv10.c +++ b/drivers/serial/crisv10.c @@ -1203,7 +1203,7 @@ static void e100_disable_txdma_channel(struct e100_serial *info) unsigned long flags; /* Disable output DMA channel for the serial port in question - * ( set to something other then serialX) + * ( set to something other than serialX) */ local_irq_save(flags); DFLOW(DEBUG_LOG(info->line, "disable_txdma_channel %i\n", info->line)); @@ -1266,7 +1266,7 @@ static void e100_disable_rxdma_channel(struct e100_serial *info) unsigned long flags; /* Disable input DMA channel for the serial port in question - * ( set to something other then serialX) + * ( set to something other than serialX) */ local_irq_save(flags); if (info->line == 0) { diff --git a/drivers/spi/spi_lm70llp.c b/drivers/spi/spi_lm70llp.c index af6526767e2a..568c781ad91c 100644 --- a/drivers/spi/spi_lm70llp.c +++ b/drivers/spi/spi_lm70llp.c @@ -1,5 +1,5 @@ /* - * spi_lm70llp.c - driver for lm70llp eval board for the LM70 sensor + * spi_lm70llp.c - driver for LM70EVAL-LLP board for the LM70 sensor * * Copyright (C) 2006 Kaiwan N Billimoria <kaiwan@designergraphix.com> * @@ -40,8 +40,12 @@ * master controller driver. The hwmon/lm70 driver is a "SPI protocol * driver", layered on top of this one and usable without the lm70llp. * + * Datasheet and Schematic: * The LM70 is a temperature sensor chip from National Semiconductor; its * datasheet is available at http://www.national.com/pf/LM/LM70.html + * The schematic for this particular board (the LM70EVAL-LLP) is + * available (on page 4) here: + * http://www.national.com/appinfo/tempsensors/files/LM70LLPEVALmanual.pdf * * Also see Documentation/spi/spi-lm70llp. The SPI<->parport code here is * (heavily) based on spi-butterfly by David Brownell. @@ -64,7 +68,7 @@ * * Note that parport pin 13 actually gets inverted by the transistor * arrangement which lets either the parport or the LM70 drive the - * SI/SO signal. + * SI/SO signal (see the schematic for details). */ #define DRVNAME "spi-lm70llp" @@ -106,12 +110,16 @@ static inline struct spi_lm70llp *spidev_to_pp(struct spi_device *spi) static inline void deassertCS(struct spi_lm70llp *pp) { u8 data = parport_read_data(pp->port); + + data &= ~0x80; /* pull D7/SI-out low while de-asserted */ parport_write_data(pp->port, data | nCS); } static inline void assertCS(struct spi_lm70llp *pp) { u8 data = parport_read_data(pp->port); + + data |= 0x80; /* pull D7/SI-out high so lm70 drives SO-in */ parport_write_data(pp->port, data & ~nCS); } @@ -184,22 +192,7 @@ static void lm70_chipselect(struct spi_device *spi, int value) */ static u32 lm70_txrx(struct spi_device *spi, unsigned nsecs, u32 word, u8 bits) { - static u32 sio=0; - static int first_time=1; - - /* First time: perform SPI bitbang and return the LSB of - * the result of the SPI call. - */ - if (first_time) { - sio = bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits); - first_time=0; - return (sio & 0x00ff); - } - /* Return the MSB of the result of the SPI call */ - else { - first_time=1; - return (sio >> 8); - } + return bitbang_txrx_be_cpha0(spi, nsecs, 0, word, bits); } static void spi_lm70llp_attach(struct parport *p) @@ -293,10 +286,9 @@ static void spi_lm70llp_attach(struct parport *p) status = -ENODEV; goto out_bitbang_stop; } - pp->spidev_lm70->bits_per_word = 16; + pp->spidev_lm70->bits_per_word = 8; lm70llp = pp; - return; out_bitbang_stop: @@ -326,7 +318,6 @@ static void spi_lm70llp_detach(struct parport *p) /* power down */ parport_write_data(pp->port, 0); - msleep(10); parport_release(pp->pd); parport_unregister_device(pp->pd); diff --git a/drivers/usb/Kconfig b/drivers/usb/Kconfig index 289d81adfb9c..83babb0a1df7 100644 --- a/drivers/usb/Kconfig +++ b/drivers/usb/Kconfig @@ -150,4 +150,6 @@ source "drivers/usb/atm/Kconfig" source "drivers/usb/gadget/Kconfig" +source "drivers/usb/otg/Kconfig" + endif # USB_SUPPORT diff --git a/drivers/usb/class/cdc-acm.c b/drivers/usb/class/cdc-acm.c index d50a99f70aee..00b47ea24f86 100644 --- a/drivers/usb/class/cdc-acm.c +++ b/drivers/usb/class/cdc-acm.c @@ -1275,7 +1275,7 @@ static int acm_suspend(struct usb_interface *intf, pm_message_t message) struct acm *acm = usb_get_intfdata(intf); int cnt; - if (acm->dev->auto_pm) { + if (message.event & PM_EVENT_AUTO) { int b; spin_lock_irq(&acm->read_lock); diff --git a/drivers/usb/class/cdc-wdm.c b/drivers/usb/class/cdc-wdm.c index 5a8ecc045e3f..3771d6e6d0cc 100644 --- a/drivers/usb/class/cdc-wdm.c +++ b/drivers/usb/class/cdc-wdm.c @@ -764,7 +764,8 @@ static int wdm_suspend(struct usb_interface *intf, pm_message_t message) mutex_lock(&desc->plock); #ifdef CONFIG_PM - if (interface_to_usbdev(desc->intf)->auto_pm && test_bit(WDM_IN_USE, &desc->flags)) { + if ((message.event & PM_EVENT_AUTO) && + test_bit(WDM_IN_USE, &desc->flags)) { rv = -EBUSY; } else { #endif diff --git a/drivers/usb/class/usbtmc.c b/drivers/usb/class/usbtmc.c index 43a863c5cc43..0f5c05f6f9df 100644 --- a/drivers/usb/class/usbtmc.c +++ b/drivers/usb/class/usbtmc.c @@ -21,6 +21,7 @@ #include <linux/init.h> #include <linux/module.h> +#include <linux/kernel.h> #include <linux/fs.h> #include <linux/uaccess.h> #include <linux/kref.h> @@ -482,7 +483,6 @@ static ssize_t usbtmc_write(struct file *filp, const char __user *buf, int retval; int actual; unsigned long int n_bytes; - int n; int remaining; int done; int this_part; @@ -526,11 +526,8 @@ static ssize_t usbtmc_write(struct file *filp, const char __user *buf, goto exit; } - n_bytes = 12 + this_part; - if (this_part % 4) - n_bytes += 4 - this_part % 4; - for (n = 12 + this_part; n < n_bytes; n++) - buffer[n] = 0; + n_bytes = roundup(12 + this_part, 4); + memset(buffer + 12 + this_part, 0, n_bytes - (12 + this_part)); retval = usb_bulk_msg(data->usb_dev, usb_sndbulkpipe(data->usb_dev, diff --git a/drivers/usb/core/devio.c b/drivers/usb/core/devio.c index aa79280df15d..26fece124e0e 100644 --- a/drivers/usb/core/devio.c +++ b/drivers/usb/core/devio.c @@ -981,9 +981,6 @@ static int proc_do_submiturb(struct dev_state *ps, struct usbdevfs_urb *uurb, return -EINVAL; if (!uurb->buffer) return -EINVAL; - if (uurb->signr != 0 && (uurb->signr < SIGRTMIN || - uurb->signr > SIGRTMAX)) - return -EINVAL; if (!(uurb->type == USBDEVFS_URB_TYPE_CONTROL && (uurb->endpoint & ~USB_ENDPOINT_DIR_MASK) == 0)) { ifnum = findintfep(ps->dev, uurb->endpoint); @@ -1320,7 +1317,7 @@ static int get_urb32(struct usbdevfs_urb *kurb, if (__get_user(uptr, &uurb->buffer)) return -EFAULT; kurb->buffer = compat_ptr(uptr); - if (__get_user(uptr, &uurb->buffer)) + if (__get_user(uptr, &uurb->usercontext)) return -EFAULT; kurb->usercontext = compat_ptr(uptr); @@ -1401,8 +1398,6 @@ static int proc_disconnectsignal(struct dev_state *ps, void __user *arg) if (copy_from_user(&ds, arg, sizeof(ds))) return -EFAULT; - if (ds.signr != 0 && (ds.signr < SIGRTMIN || ds.signr > SIGRTMAX)) - return -EINVAL; ps->discsignr = ds.signr; ps->disccontext = ds.context; return 0; diff --git a/drivers/usb/core/driver.c b/drivers/usb/core/driver.c index 8c081308b0e2..98760553bc95 100644 --- a/drivers/usb/core/driver.c +++ b/drivers/usb/core/driver.c @@ -184,6 +184,20 @@ static int usb_unbind_device(struct device *dev) return 0; } +/* + * Cancel any pending scheduled resets + * + * [see usb_queue_reset_device()] + * + * Called after unconfiguring / when releasing interfaces. See + * comments in __usb_queue_reset_device() regarding + * udev->reset_running. + */ +static void usb_cancel_queued_reset(struct usb_interface *iface) +{ + if (iface->reset_running == 0) + cancel_work_sync(&iface->reset_ws); +} /* called from driver core with dev locked */ static int usb_probe_interface(struct device *dev) @@ -242,6 +256,7 @@ static int usb_probe_interface(struct device *dev) mark_quiesced(intf); intf->needs_remote_wakeup = 0; intf->condition = USB_INTERFACE_UNBOUND; + usb_cancel_queued_reset(intf); } else intf->condition = USB_INTERFACE_BOUND; @@ -272,6 +287,7 @@ static int usb_unbind_interface(struct device *dev) usb_disable_interface(udev, intf); driver->disconnect(intf); + usb_cancel_queued_reset(intf); /* Reset other interface state. * We cannot do a Set-Interface if the device is suspended or @@ -279,9 +295,12 @@ static int usb_unbind_interface(struct device *dev) * altsetting means creating new endpoint device entries). * When either of these happens, defer the Set-Interface. */ - if (intf->cur_altsetting->desc.bAlternateSetting == 0) - ; /* Already in altsetting 0 so skip Set-Interface */ - else if (!error && intf->dev.power.status == DPM_ON) + if (intf->cur_altsetting->desc.bAlternateSetting == 0) { + /* Already in altsetting 0 so skip Set-Interface. + * Just re-enable it without affecting the endpoint toggles. + */ + usb_enable_interface(udev, intf, false); + } else if (!error && intf->dev.power.status == DPM_ON) usb_set_interface(udev, intf->altsetting[0]. desc.bInterfaceNumber, 0); else @@ -380,8 +399,10 @@ void usb_driver_release_interface(struct usb_driver *driver, if (device_is_registered(dev)) { iface->condition = USB_INTERFACE_UNBINDING; device_release_driver(dev); + } else { + iface->condition = USB_INTERFACE_UNBOUND; + usb_cancel_queued_reset(iface); } - dev->driver = NULL; usb_set_intfdata(iface, NULL); @@ -904,7 +925,7 @@ static int usb_suspend_device(struct usb_device *udev, pm_message_t msg) } /* Caller has locked udev's pm_mutex */ -static int usb_resume_device(struct usb_device *udev) +static int usb_resume_device(struct usb_device *udev, pm_message_t msg) { struct usb_device_driver *udriver; int status = 0; @@ -922,7 +943,7 @@ static int usb_resume_device(struct usb_device *udev) udev->reset_resume = 1; udriver = to_usb_device_driver(udev->dev.driver); - status = udriver->resume(udev); + status = udriver->resume(udev, msg); done: dev_vdbg(&udev->dev, "%s: status %d\n", __func__, status); @@ -942,7 +963,8 @@ static int usb_suspend_interface(struct usb_device *udev, if (udev->state == USB_STATE_NOTATTACHED || !is_active(intf)) goto done; - if (intf->condition == USB_INTERFACE_UNBOUND) /* This can't happen */ + /* This can happen; see usb_driver_release_interface() */ + if (intf->condition == USB_INTERFACE_UNBOUND) goto done; driver = to_usb_driver(intf->dev.driver); @@ -950,7 +972,7 @@ static int usb_suspend_interface(struct usb_device *udev, status = driver->suspend(intf, msg); if (status == 0) mark_quiesced(intf); - else if (!udev->auto_pm) + else if (!(msg.event & PM_EVENT_AUTO)) dev_err(&intf->dev, "%s error %d\n", "suspend", status); } else { @@ -968,7 +990,7 @@ static int usb_suspend_interface(struct usb_device *udev, /* Caller has locked intf's usb_device's pm_mutex */ static int usb_resume_interface(struct usb_device *udev, - struct usb_interface *intf, int reset_resume) + struct usb_interface *intf, pm_message_t msg, int reset_resume) { struct usb_driver *driver; int status = 0; @@ -1092,7 +1114,7 @@ static int autosuspend_check(struct usb_device *udev, int reschedule) if (reschedule) { if (!timer_pending(&udev->autosuspend.timer)) { queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend, - round_jiffies_relative(suspend_time - j)); + round_jiffies_up_relative(suspend_time - j)); } return -EAGAIN; } @@ -1119,10 +1141,9 @@ static inline int autosuspend_check(struct usb_device *udev, int reschedule) * all the interfaces which were suspended are resumed so that they remain * in the same state as the device. * - * If an autosuspend is in progress (@udev->auto_pm is set), the routine - * checks first to make sure that neither the device itself or any of its - * active interfaces is in use (pm_usage_cnt is greater than 0). If they - * are, the autosuspend fails. + * If an autosuspend is in progress the routine checks first to make sure + * that neither the device itself or any of its active interfaces is in use + * (pm_usage_cnt is greater than 0). If they are, the autosuspend fails. * * If the suspend succeeds, the routine recursively queues an autosuspend * request for @udev's parent device, thereby propagating the change up @@ -1157,7 +1178,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) udev->do_remote_wakeup = device_may_wakeup(&udev->dev); - if (udev->auto_pm) { + if (msg.event & PM_EVENT_AUTO) { status = autosuspend_check(udev, 0); if (status < 0) goto done; @@ -1177,13 +1198,16 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) /* If the suspend failed, resume interfaces that did get suspended */ if (status != 0) { + pm_message_t msg2; + + msg2.event = msg.event ^ (PM_EVENT_SUSPEND | PM_EVENT_RESUME); while (--i >= 0) { intf = udev->actconfig->interface[i]; - usb_resume_interface(udev, intf, 0); + usb_resume_interface(udev, intf, msg2, 0); } /* Try another autosuspend when the interfaces aren't busy */ - if (udev->auto_pm) + if (msg.event & PM_EVENT_AUTO) autosuspend_check(udev, status == -EBUSY); /* If the suspend succeeded then prevent any more URB submissions, @@ -1213,6 +1237,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) /** * usb_resume_both - resume a USB device and its interfaces * @udev: the usb_device to resume + * @msg: Power Management message describing this state transition * * This is the central routine for resuming USB devices. It calls the * the resume method for @udev and then calls the resume methods for all @@ -1238,7 +1263,7 @@ static int usb_suspend_both(struct usb_device *udev, pm_message_t msg) * * This routine can run only in process context. */ -static int usb_resume_both(struct usb_device *udev) +static int usb_resume_both(struct usb_device *udev, pm_message_t msg) { int status = 0; int i; @@ -1254,14 +1279,15 @@ static int usb_resume_both(struct usb_device *udev) /* Propagate the resume up the tree, if necessary */ if (udev->state == USB_STATE_SUSPENDED) { - if (udev->auto_pm && udev->autoresume_disabled) { + if ((msg.event & PM_EVENT_AUTO) && + udev->autoresume_disabled) { status = -EPERM; goto done; } if (parent) { status = usb_autoresume_device(parent); if (status == 0) { - status = usb_resume_device(udev); + status = usb_resume_device(udev, msg); if (status || udev->state == USB_STATE_NOTATTACHED) { usb_autosuspend_device(parent); @@ -1284,15 +1310,16 @@ static int usb_resume_both(struct usb_device *udev) /* We can't progagate beyond the USB subsystem, * so if a root hub's controller is suspended * then we're stuck. */ - status = usb_resume_device(udev); + status = usb_resume_device(udev, msg); } } else if (udev->reset_resume) - status = usb_resume_device(udev); + status = usb_resume_device(udev, msg); if (status == 0 && udev->actconfig) { for (i = 0; i < udev->actconfig->desc.bNumInterfaces; i++) { intf = udev->actconfig->interface[i]; - usb_resume_interface(udev, intf, udev->reset_resume); + usb_resume_interface(udev, intf, msg, + udev->reset_resume); } } @@ -1320,13 +1347,13 @@ static int usb_autopm_do_device(struct usb_device *udev, int inc_usage_cnt) udev->last_busy = jiffies; if (inc_usage_cnt >= 0 && udev->pm_usage_cnt > 0) { if (udev->state == USB_STATE_SUSPENDED) - status = usb_resume_both(udev); + status = usb_resume_both(udev, PMSG_AUTO_RESUME); if (status != 0) udev->pm_usage_cnt -= inc_usage_cnt; else if (inc_usage_cnt) udev->last_busy = jiffies; } else if (inc_usage_cnt <= 0 && udev->pm_usage_cnt <= 0) { - status = usb_suspend_both(udev, PMSG_SUSPEND); + status = usb_suspend_both(udev, PMSG_AUTO_SUSPEND); } usb_pm_unlock(udev); return status; @@ -1341,6 +1368,19 @@ void usb_autosuspend_work(struct work_struct *work) usb_autopm_do_device(udev, 0); } +/* usb_autoresume_work - callback routine to autoresume a USB device */ +void usb_autoresume_work(struct work_struct *work) +{ + struct usb_device *udev = + container_of(work, struct usb_device, autoresume); + + /* Wake it up, let the drivers do their thing, and then put it + * back to sleep. + */ + if (usb_autopm_do_device(udev, 1) == 0) + usb_autopm_do_device(udev, -1); +} + /** * usb_autosuspend_device - delayed autosuspend of a USB device and its interfaces * @udev: the usb_device to autosuspend @@ -1437,13 +1477,14 @@ static int usb_autopm_do_interface(struct usb_interface *intf, udev->last_busy = jiffies; if (inc_usage_cnt >= 0 && intf->pm_usage_cnt > 0) { if (udev->state == USB_STATE_SUSPENDED) - status = usb_resume_both(udev); + status = usb_resume_both(udev, + PMSG_AUTO_RESUME); if (status != 0) intf->pm_usage_cnt -= inc_usage_cnt; else udev->last_busy = jiffies; } else if (inc_usage_cnt <= 0 && intf->pm_usage_cnt <= 0) { - status = usb_suspend_both(udev, PMSG_SUSPEND); + status = usb_suspend_both(udev, PMSG_AUTO_SUSPEND); } } usb_pm_unlock(udev); @@ -1492,6 +1533,45 @@ void usb_autopm_put_interface(struct usb_interface *intf) EXPORT_SYMBOL_GPL(usb_autopm_put_interface); /** + * usb_autopm_put_interface_async - decrement a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be decremented + * + * This routine does essentially the same thing as + * usb_autopm_put_interface(): it decrements @intf's usage counter and + * queues a delayed autosuspend request if the counter is <= 0. The + * difference is that it does not acquire the device's pm_mutex; + * callers must handle all synchronization issues themselves. + * + * Typically a driver would call this routine during an URB's completion + * handler, if no more URBs were pending. + * + * This routine can run in atomic context. + */ +void usb_autopm_put_interface_async(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + int status = 0; + + if (intf->condition == USB_INTERFACE_UNBOUND) { + status = -ENODEV; + } else { + udev->last_busy = jiffies; + --intf->pm_usage_cnt; + if (udev->autosuspend_disabled || udev->autosuspend_delay < 0) + status = -EPERM; + else if (intf->pm_usage_cnt <= 0 && + !timer_pending(&udev->autosuspend.timer)) { + queue_delayed_work(ksuspend_usb_wq, &udev->autosuspend, + round_jiffies_up_relative( + udev->autosuspend_delay)); + } + } + dev_vdbg(&intf->dev, "%s: status %d cnt %d\n", + __func__, status, intf->pm_usage_cnt); +} +EXPORT_SYMBOL_GPL(usb_autopm_put_interface_async); + +/** * usb_autopm_get_interface - increment a USB interface's PM-usage counter * @intf: the usb_interface whose counter should be incremented * @@ -1537,6 +1617,37 @@ int usb_autopm_get_interface(struct usb_interface *intf) EXPORT_SYMBOL_GPL(usb_autopm_get_interface); /** + * usb_autopm_get_interface_async - increment a USB interface's PM-usage counter + * @intf: the usb_interface whose counter should be incremented + * + * This routine does much the same thing as + * usb_autopm_get_interface(): it increments @intf's usage counter and + * queues an autoresume request if the result is > 0. The differences + * are that it does not acquire the device's pm_mutex (callers must + * handle all synchronization issues themselves), and it does not + * autoresume the device directly (it only queues a request). After a + * successful call, the device will generally not yet be resumed. + * + * This routine can run in atomic context. + */ +int usb_autopm_get_interface_async(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + int status = 0; + + if (intf->condition == USB_INTERFACE_UNBOUND) + status = -ENODEV; + else if (udev->autoresume_disabled) + status = -EPERM; + else if (++intf->pm_usage_cnt > 0 && udev->state == USB_STATE_SUSPENDED) + queue_work(ksuspend_usb_wq, &udev->autoresume); + dev_vdbg(&intf->dev, "%s: status %d cnt %d\n", + __func__, status, intf->pm_usage_cnt); + return status; +} +EXPORT_SYMBOL_GPL(usb_autopm_get_interface_async); + +/** * usb_autopm_set_interface - set a USB interface's autosuspend state * @intf: the usb_interface whose state should be set * @@ -1563,6 +1674,9 @@ EXPORT_SYMBOL_GPL(usb_autopm_set_interface); void usb_autosuspend_work(struct work_struct *work) {} +void usb_autoresume_work(struct work_struct *work) +{} + #endif /* CONFIG_USB_SUSPEND */ /** @@ -1595,6 +1709,7 @@ int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg) /** * usb_external_resume_device - external resume of a USB device and its interfaces * @udev: the usb_device to resume + * @msg: Power Management message describing this state transition * * This routine handles external resume requests: ones not generated * internally by a USB driver (autoresume) but rather coming from the user @@ -1603,13 +1718,13 @@ int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg) * * The caller must hold @udev's device lock. */ -int usb_external_resume_device(struct usb_device *udev) +int usb_external_resume_device(struct usb_device *udev, pm_message_t msg) { int status; usb_pm_lock(udev); udev->auto_pm = 0; - status = usb_resume_both(udev); + status = usb_resume_both(udev, msg); udev->last_busy = jiffies; usb_pm_unlock(udev); if (status == 0) @@ -1622,7 +1737,7 @@ int usb_external_resume_device(struct usb_device *udev) return status; } -int usb_suspend(struct device *dev, pm_message_t message) +int usb_suspend(struct device *dev, pm_message_t msg) { struct usb_device *udev; @@ -1641,10 +1756,10 @@ int usb_suspend(struct device *dev, pm_message_t message) } udev->skip_sys_resume = 0; - return usb_external_suspend_device(udev, message); + return usb_external_suspend_device(udev, msg); } -int usb_resume(struct device *dev) +int usb_resume(struct device *dev, pm_message_t msg) { struct usb_device *udev; @@ -1656,7 +1771,7 @@ int usb_resume(struct device *dev) */ if (udev->skip_sys_resume) return 0; - return usb_external_resume_device(udev); + return usb_external_resume_device(udev, msg); } #endif /* CONFIG_PM */ diff --git a/drivers/usb/core/endpoint.c b/drivers/usb/core/endpoint.c index 946fae43d622..e1710f260b4f 100644 --- a/drivers/usb/core/endpoint.c +++ b/drivers/usb/core/endpoint.c @@ -276,7 +276,7 @@ static void ep_device_release(struct device *dev) kfree(ep_dev); } -int usb_create_ep_files(struct device *parent, +int usb_create_ep_devs(struct device *parent, struct usb_host_endpoint *endpoint, struct usb_device *udev) { @@ -340,7 +340,7 @@ exit: return retval; } -void usb_remove_ep_files(struct usb_host_endpoint *endpoint) +void usb_remove_ep_devs(struct usb_host_endpoint *endpoint) { struct ep_device *ep_dev = endpoint->ep_dev; diff --git a/drivers/usb/core/generic.c b/drivers/usb/core/generic.c index 7e912f21fd36..30ecac3af15a 100644 --- a/drivers/usb/core/generic.c +++ b/drivers/usb/core/generic.c @@ -200,18 +200,18 @@ static int generic_suspend(struct usb_device *udev, pm_message_t msg) * interfaces manually by doing a bus (or "global") suspend. */ if (!udev->parent) - rc = hcd_bus_suspend(udev); + rc = hcd_bus_suspend(udev, msg); /* Non-root devices don't need to do anything for FREEZE or PRETHAW */ else if (msg.event == PM_EVENT_FREEZE || msg.event == PM_EVENT_PRETHAW) rc = 0; else - rc = usb_port_suspend(udev); + rc = usb_port_suspend(udev, msg); return rc; } -static int generic_resume(struct usb_device *udev) +static int generic_resume(struct usb_device *udev, pm_message_t msg) { int rc; @@ -221,9 +221,9 @@ static int generic_resume(struct usb_device *udev) * interfaces manually by doing a bus (or "global") resume. */ if (!udev->parent) - rc = hcd_bus_resume(udev); + rc = hcd_bus_resume(udev, msg); else - rc = usb_port_resume(udev); + rc = usb_port_resume(udev, msg); return rc; } diff --git a/drivers/usb/core/hcd-pci.c b/drivers/usb/core/hcd-pci.c index 5b87ae7f0a6a..507741ed4482 100644 --- a/drivers/usb/core/hcd-pci.c +++ b/drivers/usb/core/hcd-pci.c @@ -128,6 +128,7 @@ int usb_hcd_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) } pci_set_master(dev); + device_set_wakeup_enable(&dev->dev, 1); retval = usb_add_hcd(hcd, dev->irq, IRQF_DISABLED | IRQF_SHARED); if (retval != 0) @@ -191,17 +192,15 @@ EXPORT_SYMBOL_GPL(usb_hcd_pci_remove); /** * usb_hcd_pci_suspend - power management suspend of a PCI-based HCD * @dev: USB Host Controller being suspended - * @message: semantics in flux + * @message: Power Management message describing this state transition * - * Store this function in the HCD's struct pci_driver as suspend(). + * Store this function in the HCD's struct pci_driver as .suspend. */ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) { - struct usb_hcd *hcd; + struct usb_hcd *hcd = pci_get_drvdata(dev); int retval = 0; - int has_pci_pm; - - hcd = pci_get_drvdata(dev); + int wake, w; /* Root hub suspend should have stopped all downstream traffic, * and all bus master traffic. And done so for both the interface @@ -212,8 +211,15 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) * otherwise the swsusp will save (and restore) garbage state. */ if (!(hcd->state == HC_STATE_SUSPENDED || - hcd->state == HC_STATE_HALT)) - return -EBUSY; + hcd->state == HC_STATE_HALT)) { + dev_warn(&dev->dev, "Root hub is not suspended\n"); + retval = -EBUSY; + goto done; + } + + /* We might already be suspended (runtime PM -- not yet written) */ + if (dev->current_state != PCI_D0) + goto done; if (hcd->driver->pci_suspend) { retval = hcd->driver->pci_suspend(hcd, message); @@ -221,49 +227,60 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) if (retval) goto done; } - synchronize_irq(dev->irq); - /* FIXME until the generic PM interfaces change a lot more, this - * can't use PCI D1 and D2 states. For example, the confusion - * between messages and states will need to vanish, and messages - * will need to provide a target system state again. - * - * It'll be important to learn characteristics of the target state, - * especially on embedded hardware where the HCD will often be in - * charge of an external VBUS power supply and one or more clocks. - * Some target system states will leave them active; others won't. - * (With PCI, that's often handled by platform BIOS code.) - */ + synchronize_irq(dev->irq); - /* even when the PCI layer rejects some of the PCI calls - * below, HCs can try global suspend and reduce DMA traffic. - * PM-sensitive HCDs may already have done this. + /* Don't fail on error to enable wakeup. We rely on pci code + * to reject requests the hardware can't implement, rather + * than coding the same thing. */ - has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); + wake = (hcd->state == HC_STATE_SUSPENDED && + device_may_wakeup(&dev->dev)); + w = pci_wake_from_d3(dev, wake); + if (w < 0) + wake = w; + dev_dbg(&dev->dev, "wakeup: %d\n", wake); /* Downstream ports from this root hub should already be quiesced, so * there will be no DMA activity. Now we can shut down the upstream * link (except maybe for PME# resume signaling) and enter some PCI * low power state, if the hardware allows. */ - if (hcd->state == HC_STATE_SUSPENDED) { + pci_disable_device(dev); + done: + return retval; +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); - /* no DMA or IRQs except when HC is active */ - if (dev->current_state == PCI_D0) { - pci_save_state(dev); - pci_disable_device(dev); - } +/** + * usb_hcd_pci_suspend_late - suspend a PCI-based HCD after IRQs are disabled + * @dev: USB Host Controller being suspended + * @message: Power Management message describing this state transition + * + * Store this function in the HCD's struct pci_driver as .suspend_late. + */ +int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t message) +{ + int retval = 0; + int has_pci_pm; - if (message.event == PM_EVENT_FREEZE || - message.event == PM_EVENT_PRETHAW) { - dev_dbg(hcd->self.controller, "--> no state change\n"); - goto done; - } + /* We might already be suspended (runtime PM -- not yet written) */ + if (dev->current_state != PCI_D0) + goto done; - if (!has_pci_pm) { - dev_dbg(hcd->self.controller, "--> PCI D0/legacy\n"); - goto done; - } + pci_save_state(dev); + + /* Don't change state if we don't need to */ + if (message.event == PM_EVENT_FREEZE || + message.event == PM_EVENT_PRETHAW) { + dev_dbg(&dev->dev, "--> no state change\n"); + goto done; + } + + has_pci_pm = pci_find_capability(dev, PCI_CAP_ID_PM); + if (!has_pci_pm) { + dev_dbg(&dev->dev, "--> PCI D0 legacy\n"); + } else { /* NOTE: dev->current_state becomes nonzero only here, and * only for devices that support PCI PM. Also, exiting @@ -273,35 +290,16 @@ int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t message) retval = pci_set_power_state(dev, PCI_D3hot); suspend_report_result(pci_set_power_state, retval); if (retval == 0) { - int wake = device_can_wakeup(&hcd->self.root_hub->dev); - - wake = wake && device_may_wakeup(hcd->self.controller); - - dev_dbg(hcd->self.controller, "--> PCI D3%s\n", - wake ? "/wakeup" : ""); - - /* Ignore these return values. We rely on pci code to - * reject requests the hardware can't implement, rather - * than coding the same thing. - */ - (void) pci_enable_wake(dev, PCI_D3hot, wake); - (void) pci_enable_wake(dev, PCI_D3cold, wake); + dev_dbg(&dev->dev, "--> PCI D3\n"); } else { dev_dbg(&dev->dev, "PCI D3 suspend fail, %d\n", retval); - (void) usb_hcd_pci_resume(dev); + pci_restore_state(dev); } - - } else if (hcd->state != HC_STATE_HALT) { - dev_dbg(hcd->self.controller, "hcd state %d; not suspended\n", - hcd->state); - WARN_ON(1); - retval = -EINVAL; } -done: - if (retval == 0) { #ifdef CONFIG_PPC_PMAC + if (retval == 0) { /* Disable ASIC clocks for USB */ if (machine_is(powermac)) { struct device_node *of_node; @@ -311,30 +309,24 @@ done: pmac_call_feature(PMAC_FTR_USB_ENABLE, of_node, 0, 0); } -#endif } +#endif + done: return retval; } -EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend); +EXPORT_SYMBOL_GPL(usb_hcd_pci_suspend_late); /** - * usb_hcd_pci_resume - power management resume of a PCI-based HCD + * usb_hcd_pci_resume_early - resume a PCI-based HCD before IRQs are enabled * @dev: USB Host Controller being resumed * - * Store this function in the HCD's struct pci_driver as resume(). + * Store this function in the HCD's struct pci_driver as .resume_early. */ -int usb_hcd_pci_resume(struct pci_dev *dev) +int usb_hcd_pci_resume_early(struct pci_dev *dev) { - struct usb_hcd *hcd; - int retval; - - hcd = pci_get_drvdata(dev); - if (hcd->state != HC_STATE_SUSPENDED) { - dev_dbg(hcd->self.controller, - "can't resume, not suspended!\n"); - return 0; - } + int retval = 0; + pci_power_t state = dev->current_state; #ifdef CONFIG_PPC_PMAC /* Reenable ASIC clocks for USB */ @@ -352,7 +344,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev) * calls "standby", "suspend to RAM", and so on). There are also * dirty cases when swsusp fakes a suspend in "shutdown" mode. */ - if (dev->current_state != PCI_D0) { + if (state != PCI_D0) { #ifdef DEBUG int pci_pm; u16 pmcr; @@ -364,8 +356,7 @@ int usb_hcd_pci_resume(struct pci_dev *dev) /* Clean case: power to USB and to HC registers was * maintained; remote wakeup is easy. */ - dev_dbg(hcd->self.controller, "resume from PCI D%d\n", - pmcr); + dev_dbg(&dev->dev, "resume from PCI D%d\n", pmcr); } else { /* Clean: HC lost Vcc power, D0 uninitialized * + Vaux may have preserved port and transceiver @@ -376,32 +367,55 @@ int usb_hcd_pci_resume(struct pci_dev *dev) * + after BIOS init * + after Linux init (HCD statically linked) */ - dev_dbg(hcd->self.controller, - "PCI D0, from previous PCI D%d\n", - dev->current_state); + dev_dbg(&dev->dev, "resume from previous PCI D%d\n", + state); } #endif - /* yes, ignore these results too... */ - (void) pci_enable_wake(dev, dev->current_state, 0); - (void) pci_enable_wake(dev, PCI_D3cold, 0); + + retval = pci_set_power_state(dev, PCI_D0); } else { /* Same basic cases: clean (powered/not), dirty */ - dev_dbg(hcd->self.controller, "PCI legacy resume\n"); + dev_dbg(&dev->dev, "PCI legacy resume\n"); + } + + if (retval < 0) + dev_err(&dev->dev, "can't resume: %d\n", retval); + else + pci_restore_state(dev); + + return retval; +} +EXPORT_SYMBOL_GPL(usb_hcd_pci_resume_early); + +/** + * usb_hcd_pci_resume - power management resume of a PCI-based HCD + * @dev: USB Host Controller being resumed + * + * Store this function in the HCD's struct pci_driver as .resume. + */ +int usb_hcd_pci_resume(struct pci_dev *dev) +{ + struct usb_hcd *hcd; + int retval; + + hcd = pci_get_drvdata(dev); + if (hcd->state != HC_STATE_SUSPENDED) { + dev_dbg(hcd->self.controller, + "can't resume, not suspended!\n"); + return 0; } - /* NOTE: the PCI API itself is asymmetric here. We don't need to - * pci_set_power_state(PCI_D0) since that's part of re-enabling; - * but that won't re-enable bus mastering. Yet pci_disable_device() - * explicitly disables bus mastering... - */ retval = pci_enable_device(dev); if (retval < 0) { - dev_err(hcd->self.controller, - "can't re-enable after resume, %d!\n", retval); + dev_err(&dev->dev, "can't re-enable after resume, %d!\n", + retval); return retval; } + pci_set_master(dev); - pci_restore_state(dev); + + /* yes, ignore this result too... */ + (void) pci_wake_from_d3(dev, 0); clear_bit(HCD_FLAG_SAW_IRQ, &hcd->flags); @@ -413,7 +427,6 @@ int usb_hcd_pci_resume(struct pci_dev *dev) usb_hc_died(hcd); } } - return retval; } EXPORT_SYMBOL_GPL(usb_hcd_pci_resume); diff --git a/drivers/usb/core/hcd.c b/drivers/usb/core/hcd.c index e1b42626d04d..3c711db55d86 100644 --- a/drivers/usb/core/hcd.c +++ b/drivers/usb/core/hcd.c @@ -1010,7 +1010,7 @@ int usb_hcd_link_urb_to_ep(struct usb_hcd *hcd, struct urb *urb) spin_lock(&hcd_urb_list_lock); /* Check that the URB isn't being killed */ - if (unlikely(urb->reject)) { + if (unlikely(atomic_read(&urb->reject))) { rc = -EPERM; goto done; } @@ -1340,7 +1340,7 @@ int usb_hcd_submit_urb (struct urb *urb, gfp_t mem_flags) INIT_LIST_HEAD(&urb->urb_list); atomic_dec(&urb->use_count); atomic_dec(&urb->dev->urbnum); - if (urb->reject) + if (atomic_read(&urb->reject)) wake_up(&usb_kill_urb_queue); usb_put_urb(urb); } @@ -1444,7 +1444,7 @@ void usb_hcd_giveback_urb(struct usb_hcd *hcd, struct urb *urb, int status) urb->status = status; urb->complete (urb); atomic_dec (&urb->use_count); - if (unlikely (urb->reject)) + if (unlikely(atomic_read(&urb->reject))) wake_up (&usb_kill_urb_queue); usb_put_urb (urb); } @@ -1573,14 +1573,14 @@ int usb_hcd_get_frame_number (struct usb_device *udev) #ifdef CONFIG_PM -int hcd_bus_suspend(struct usb_device *rhdev) +int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg) { struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self); int status; int old_state = hcd->state; dev_dbg(&rhdev->dev, "bus %s%s\n", - rhdev->auto_pm ? "auto-" : "", "suspend"); + (msg.event & PM_EVENT_AUTO ? "auto-" : ""), "suspend"); if (!hcd->driver->bus_suspend) { status = -ENOENT; } else { @@ -1598,14 +1598,14 @@ int hcd_bus_suspend(struct usb_device *rhdev) return status; } -int hcd_bus_resume(struct usb_device *rhdev) +int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg) { struct usb_hcd *hcd = container_of(rhdev->bus, struct usb_hcd, self); int status; int old_state = hcd->state; dev_dbg(&rhdev->dev, "usb %s%s\n", - rhdev->auto_pm ? "auto-" : "", "resume"); + (msg.event & PM_EVENT_AUTO ? "auto-" : ""), "resume"); if (!hcd->driver->bus_resume) return -ENOENT; if (hcd->state == HC_STATE_RUNNING) @@ -1638,7 +1638,7 @@ static void hcd_resume_work(struct work_struct *work) usb_lock_device(udev); usb_mark_last_busy(udev); - usb_external_resume_device(udev); + usb_external_resume_device(udev, PMSG_REMOTE_RESUME); usb_unlock_device(udev); } @@ -2028,7 +2028,7 @@ EXPORT_SYMBOL_GPL(usb_hcd_platform_shutdown); /*-------------------------------------------------------------------------*/ -#if defined(CONFIG_USB_MON) +#if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) struct usb_mon_operations *mon_ops; @@ -2064,4 +2064,4 @@ void usb_mon_deregister (void) } EXPORT_SYMBOL_GPL (usb_mon_deregister); -#endif /* CONFIG_USB_MON */ +#endif /* CONFIG_USB_MON || CONFIG_USB_MON_MODULE */ diff --git a/drivers/usb/core/hcd.h b/drivers/usb/core/hcd.h index 9465e70f4dd0..572d2cf46e8d 100644 --- a/drivers/usb/core/hcd.h +++ b/drivers/usb/core/hcd.h @@ -16,6 +16,8 @@ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ +#ifndef __USB_CORE_HCD_H +#define __USB_CORE_HCD_H #ifdef __KERNEL__ @@ -254,7 +256,9 @@ extern int usb_hcd_pci_probe(struct pci_dev *dev, extern void usb_hcd_pci_remove(struct pci_dev *dev); #ifdef CONFIG_PM -extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t state); +extern int usb_hcd_pci_suspend(struct pci_dev *dev, pm_message_t msg); +extern int usb_hcd_pci_suspend_late(struct pci_dev *dev, pm_message_t msg); +extern int usb_hcd_pci_resume_early(struct pci_dev *dev); extern int usb_hcd_pci_resume(struct pci_dev *dev); #endif /* CONFIG_PM */ @@ -386,8 +390,8 @@ extern int usb_find_interface_driver(struct usb_device *dev, #ifdef CONFIG_PM extern void usb_hcd_resume_root_hub(struct usb_hcd *hcd); extern void usb_root_hub_lost_power(struct usb_device *rhdev); -extern int hcd_bus_suspend(struct usb_device *rhdev); -extern int hcd_bus_resume(struct usb_device *rhdev); +extern int hcd_bus_suspend(struct usb_device *rhdev, pm_message_t msg); +extern int hcd_bus_resume(struct usb_device *rhdev, pm_message_t msg); #else static inline void usb_hcd_resume_root_hub(struct usb_hcd *hcd) { @@ -419,7 +423,7 @@ static inline void usbfs_cleanup(void) { } /*-------------------------------------------------------------------------*/ -#if defined(CONFIG_USB_MON) +#if defined(CONFIG_USB_MON) || defined(CONFIG_USB_MON_MODULE) struct usb_mon_operations { void (*urb_submit)(struct usb_bus *bus, struct urb *urb); @@ -461,7 +465,7 @@ static inline void usbmon_urb_submit_error(struct usb_bus *bus, struct urb *urb, static inline void usbmon_urb_complete(struct usb_bus *bus, struct urb *urb, int status) {} -#endif /* CONFIG_USB_MON */ +#endif /* CONFIG_USB_MON || CONFIG_USB_MON_MODULE */ /*-------------------------------------------------------------------------*/ @@ -490,3 +494,5 @@ extern struct rw_semaphore ehci_cf_port_reset_rwsem; extern unsigned long usb_hcds_loaded; #endif /* __KERNEL__ */ + +#endif /* __USB_CORE_HCD_H */ diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index b19cbfcd51da..d5d0e40b1e2d 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -107,7 +107,9 @@ MODULE_PARM_DESC (blinkenlights, "true to cycle leds on hubs"); /* define initial 64-byte descriptor request timeout in milliseconds */ static int initial_descriptor_timeout = USB_CTRL_GET_TIMEOUT; module_param(initial_descriptor_timeout, int, S_IRUGO|S_IWUSR); -MODULE_PARM_DESC(initial_descriptor_timeout, "initial 64-byte descriptor request timeout in milliseconds (default 5000 - 5.0 seconds)"); +MODULE_PARM_DESC(initial_descriptor_timeout, + "initial 64-byte descriptor request timeout in milliseconds " + "(default 5000 - 5.0 seconds)"); /* * As of 2.6.10 we introduce a new USB device initialization scheme which @@ -1136,8 +1138,8 @@ static int hub_probe(struct usb_interface *intf, const struct usb_device_id *id) hdev = interface_to_usbdev(intf); if (hdev->level == MAX_TOPO_LEVEL) { - dev_err(&intf->dev, "Unsupported bus topology: " - "hub nested too deep\n"); + dev_err(&intf->dev, + "Unsupported bus topology: hub nested too deep\n"); return -E2BIG; } @@ -1374,8 +1376,9 @@ static void usb_stop_pm(struct usb_device *udev) usb_autosuspend_device(udev->parent); usb_pm_unlock(udev); - /* Stop any autosuspend requests already submitted */ - cancel_rearming_delayed_work(&udev->autosuspend); + /* Stop any autosuspend or autoresume requests already submitted */ + cancel_delayed_work_sync(&udev->autosuspend); + cancel_work_sync(&udev->autoresume); } #else @@ -1434,17 +1437,12 @@ void usb_disconnect(struct usb_device **pdev) usb_disable_device(udev, 0); usb_hcd_synchronize_unlinks(udev); + usb_remove_ep_devs(&udev->ep0); usb_unlock_device(udev); - /* Remove the device-specific files from sysfs. This must be - * done with udev unlocked, because some of the attribute - * routines try to acquire the device lock. - */ - usb_remove_sysfs_dev_files(udev); - /* Unregister the device. The device driver is responsible - * for removing the device files from usbfs and sysfs and for - * de-configuring the device. + * for de-configuring the device and invoking the remove-device + * notifier chain (used by usbfs and possibly others). */ device_del(&udev->dev); @@ -1476,8 +1474,8 @@ static void announce_device(struct usb_device *udev) dev_info(&udev->dev, "New USB device found, idVendor=%04x, idProduct=%04x\n", le16_to_cpu(udev->descriptor.idVendor), le16_to_cpu(udev->descriptor.idProduct)); - dev_info(&udev->dev, "New USB device strings: Mfr=%d, Product=%d, " - "SerialNumber=%d\n", + dev_info(&udev->dev, + "New USB device strings: Mfr=%d, Product=%d, SerialNumber=%d\n", udev->descriptor.iManufacturer, udev->descriptor.iProduct, udev->descriptor.iSerialNumber); @@ -1542,7 +1540,7 @@ static int usb_configure_device_otg(struct usb_device *udev) * customize to match your product. */ dev_info(&udev->dev, - "can't set HNP mode; %d\n", + "can't set HNP mode: %d\n", err); bus->b_hnp_enable = 0; } @@ -1635,6 +1633,10 @@ int usb_new_device(struct usb_device *udev) { int err; + /* Increment the parent's count of unsuspended children */ + if (udev->parent) + usb_autoresume_device(udev->parent); + usb_detect_quirks(udev); /* Determine quirks */ err = usb_configure_device(udev); /* detect & probe dev/intfs */ if (err < 0) @@ -1643,13 +1645,12 @@ int usb_new_device(struct usb_device *udev) udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); - /* Increment the parent's count of unsuspended children */ - if (udev->parent) - usb_autoresume_device(udev->parent); + /* Tell the world! */ + announce_device(udev); /* Register the device. The device driver is responsible - * for adding the device files to sysfs and for configuring - * the device. + * for configuring the device and invoking the add-device + * notifier chain (used by usbfs and possibly others). */ err = device_add(&udev->dev); if (err) { @@ -1657,15 +1658,12 @@ int usb_new_device(struct usb_device *udev) goto fail; } - /* put device-specific files into sysfs */ - usb_create_sysfs_dev_files(udev); - - /* Tell the world! */ - announce_device(udev); + (void) usb_create_ep_devs(&udev->dev, &udev->ep0, udev); return err; fail: usb_set_device_state(udev, USB_STATE_NOTATTACHED); + usb_stop_pm(udev); return err; } @@ -1982,7 +1980,7 @@ static int check_port_resume_type(struct usb_device *udev, * * Returns 0 on success, else negative errno. */ -int usb_port_suspend(struct usb_device *udev) +int usb_port_suspend(struct usb_device *udev, pm_message_t msg) { struct usb_hub *hub = hdev_to_hub(udev->parent); int port1 = udev->portnum; @@ -2021,7 +2019,7 @@ int usb_port_suspend(struct usb_device *udev) } else { /* device has up to 10 msec to fully suspend */ dev_dbg(&udev->dev, "usb %ssuspend\n", - udev->auto_pm ? "auto-" : ""); + (msg.event & PM_EVENT_AUTO ? "auto-" : "")); usb_set_device_state(udev, USB_STATE_SUSPENDED); msleep(10); } @@ -2045,8 +2043,8 @@ static int finish_port_resume(struct usb_device *udev) u16 devstatus; /* caller owns the udev device lock */ - dev_dbg(&udev->dev, "finish %sresume\n", - udev->reset_resume ? "reset-" : ""); + dev_dbg(&udev->dev, "%s\n", + udev->reset_resume ? "finish reset-resume" : "finish resume"); /* usb ch9 identifies four variants of SUSPENDED, based on what * state the device resumes to. Linux currently won't see the @@ -2098,8 +2096,9 @@ static int finish_port_resume(struct usb_device *udev) NULL, 0, USB_CTRL_SET_TIMEOUT); if (status) - dev_dbg(&udev->dev, "disable remote " - "wakeup, status %d\n", status); + dev_dbg(&udev->dev, + "disable remote wakeup, status %d\n", + status); } status = 0; } @@ -2140,7 +2139,7 @@ static int finish_port_resume(struct usb_device *udev) * * Returns 0 on success, else negative errno. */ -int usb_port_resume(struct usb_device *udev) +int usb_port_resume(struct usb_device *udev, pm_message_t msg) { struct usb_hub *hub = hdev_to_hub(udev->parent); int port1 = udev->portnum; @@ -2165,7 +2164,7 @@ int usb_port_resume(struct usb_device *udev) } else { /* drive resume for at least 20 msec */ dev_dbg(&udev->dev, "usb %sresume\n", - udev->auto_pm ? "auto-" : ""); + (msg.event & PM_EVENT_AUTO ? "auto-" : "")); msleep(25); /* Virtual root hubs can trigger on GET_PORT_STATUS to @@ -2206,7 +2205,7 @@ static int remote_wakeup(struct usb_device *udev) if (udev->state == USB_STATE_SUSPENDED) { dev_dbg(&udev->dev, "usb %sresume\n", "wakeup-"); usb_mark_last_busy(udev); - status = usb_external_resume_device(udev); + status = usb_external_resume_device(udev, PMSG_REMOTE_RESUME); } return status; } @@ -2215,14 +2214,14 @@ static int remote_wakeup(struct usb_device *udev) /* When CONFIG_USB_SUSPEND isn't set, we never suspend or resume any ports. */ -int usb_port_suspend(struct usb_device *udev) +int usb_port_suspend(struct usb_device *udev, pm_message_t msg) { return 0; } /* However we may need to do a reset-resume */ -int usb_port_resume(struct usb_device *udev) +int usb_port_resume(struct usb_device *udev, pm_message_t msg) { struct usb_hub *hub = hdev_to_hub(udev->parent); int port1 = udev->portnum; @@ -2262,7 +2261,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) udev = hdev->children [port1-1]; if (udev && udev->can_submit) { - if (!hdev->auto_pm) + if (!(msg.event & PM_EVENT_AUTO)) dev_dbg(&intf->dev, "port %d nyet suspended\n", port1); return -EBUSY; @@ -2385,7 +2384,7 @@ void usb_ep0_reinit(struct usb_device *udev) { usb_disable_endpoint(udev, 0 + USB_DIR_IN); usb_disable_endpoint(udev, 0 + USB_DIR_OUT); - usb_enable_endpoint(udev, &udev->ep0); + usb_enable_endpoint(udev, &udev->ep0, true); } EXPORT_SYMBOL_GPL(usb_ep0_reinit); @@ -2582,9 +2581,9 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, goto fail; } if (r) { - dev_err(&udev->dev, "device descriptor " - "read/%s, error %d\n", - "64", r); + dev_err(&udev->dev, + "device descriptor read/64, error %d\n", + r); retval = -EMSGSIZE; continue; } @@ -2621,9 +2620,9 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, retval = usb_get_device_descriptor(udev, 8); if (retval < 8) { - dev_err(&udev->dev, "device descriptor " - "read/%s, error %d\n", - "8", retval); + dev_err(&udev->dev, + "device descriptor read/8, error %d\n", + retval); if (retval >= 0) retval = -EMSGSIZE; } else { @@ -2650,8 +2649,8 @@ hub_port_init (struct usb_hub *hub, struct usb_device *udev, int port1, retval = usb_get_device_descriptor(udev, USB_DT_DEVICE_SIZE); if (retval < (signed)sizeof(udev->descriptor)) { - dev_err(&udev->dev, "device descriptor read/%s, error %d\n", - "all", retval); + dev_err(&udev->dev, "device descriptor read/all, error %d\n", + retval); if (retval >= 0) retval = -ENOMSG; goto fail; @@ -2719,9 +2718,9 @@ hub_power_remaining (struct usb_hub *hub) else delta = 8; if (delta > hub->mA_per_port) - dev_warn(&udev->dev, "%dmA is over %umA budget " - "for port %d!\n", - delta, hub->mA_per_port, port1); + dev_warn(&udev->dev, + "%dmA is over %umA budget for port %d!\n", + delta, hub->mA_per_port, port1); remaining -= delta; } if (remaining < 0) { @@ -3517,3 +3516,46 @@ int usb_reset_device(struct usb_device *udev) return ret; } EXPORT_SYMBOL_GPL(usb_reset_device); + + +/** + * usb_queue_reset_device - Reset a USB device from an atomic context + * @iface: USB interface belonging to the device to reset + * + * This function can be used to reset a USB device from an atomic + * context, where usb_reset_device() won't work (as it blocks). + * + * Doing a reset via this method is functionally equivalent to calling + * usb_reset_device(), except for the fact that it is delayed to a + * workqueue. This means that any drivers bound to other interfaces + * might be unbound, as well as users from usbfs in user space. + * + * Corner cases: + * + * - Scheduling two resets at the same time from two different drivers + * attached to two different interfaces of the same device is + * possible; depending on how the driver attached to each interface + * handles ->pre_reset(), the second reset might happen or not. + * + * - If a driver is unbound and it had a pending reset, the reset will + * be cancelled. + * + * - This function can be called during .probe() or .disconnect() + * times. On return from .disconnect(), any pending resets will be + * cancelled. + * + * There is no no need to lock/unlock the @reset_ws as schedule_work() + * does its own. + * + * NOTE: We don't do any reference count tracking because it is not + * needed. The lifecycle of the work_struct is tied to the + * usb_interface. Before destroying the interface we cancel the + * work_struct, so the fact that work_struct is queued and or + * running means the interface (and thus, the device) exist and + * are referenced. + */ +void usb_queue_reset_device(struct usb_interface *iface) +{ + schedule_work(&iface->reset_ws); +} +EXPORT_SYMBOL_GPL(usb_queue_reset_device); diff --git a/drivers/usb/core/message.c b/drivers/usb/core/message.c index 6d1048faf08e..de51667dd64d 100644 --- a/drivers/usb/core/message.c +++ b/drivers/usb/core/message.c @@ -18,6 +18,8 @@ #include "hcd.h" /* for usbcore internals */ #include "usb.h" +static void cancel_async_set_config(struct usb_device *udev); + struct api_context { struct completion done; int status; @@ -139,9 +141,9 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe, __u8 request, dr->bRequestType = requesttype; dr->bRequest = request; - dr->wValue = cpu_to_le16p(&value); - dr->wIndex = cpu_to_le16p(&index); - dr->wLength = cpu_to_le16p(&size); + dr->wValue = cpu_to_le16(value); + dr->wIndex = cpu_to_le16(index); + dr->wLength = cpu_to_le16(size); /* dbg("usb_control_msg"); */ @@ -1004,6 +1006,34 @@ int usb_clear_halt(struct usb_device *dev, int pipe) } EXPORT_SYMBOL_GPL(usb_clear_halt); +static int create_intf_ep_devs(struct usb_interface *intf) +{ + struct usb_device *udev = interface_to_usbdev(intf); + struct usb_host_interface *alt = intf->cur_altsetting; + int i; + + if (intf->ep_devs_created || intf->unregistering) + return 0; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) + (void) usb_create_ep_devs(&intf->dev, &alt->endpoint[i], udev); + intf->ep_devs_created = 1; + return 0; +} + +static void remove_intf_ep_devs(struct usb_interface *intf) +{ + struct usb_host_interface *alt = intf->cur_altsetting; + int i; + + if (!intf->ep_devs_created) + return; + + for (i = 0; i < alt->desc.bNumEndpoints; ++i) + usb_remove_ep_devs(&alt->endpoint[i]); + intf->ep_devs_created = 0; +} + /** * usb_disable_endpoint -- Disable an endpoint by address * @dev: the device whose endpoint is being disabled @@ -1092,7 +1122,7 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0) dev_dbg(&dev->dev, "unregistering interface %s\n", dev_name(&interface->dev)); interface->unregistering = 1; - usb_remove_sysfs_intf_files(interface); + remove_intf_ep_devs(interface); device_del(&interface->dev); } @@ -1113,22 +1143,26 @@ void usb_disable_device(struct usb_device *dev, int skip_ep0) * usb_enable_endpoint - Enable an endpoint for USB communications * @dev: the device whose interface is being enabled * @ep: the endpoint + * @reset_toggle: flag to set the endpoint's toggle back to 0 * - * Resets the endpoint toggle, and sets dev->ep_{in,out} pointers. + * Resets the endpoint toggle if asked, and sets dev->ep_{in,out} pointers. * For control endpoints, both the input and output sides are handled. */ -void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep) +void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep, + bool reset_toggle) { int epnum = usb_endpoint_num(&ep->desc); int is_out = usb_endpoint_dir_out(&ep->desc); int is_control = usb_endpoint_xfer_control(&ep->desc); if (is_out || is_control) { - usb_settoggle(dev, epnum, 1, 0); + if (reset_toggle) + usb_settoggle(dev, epnum, 1, 0); dev->ep_out[epnum] = ep; } if (!is_out || is_control) { - usb_settoggle(dev, epnum, 0, 0); + if (reset_toggle) + usb_settoggle(dev, epnum, 0, 0); dev->ep_in[epnum] = ep; } ep->enabled = 1; @@ -1138,17 +1172,18 @@ void usb_enable_endpoint(struct usb_device *dev, struct usb_host_endpoint *ep) * usb_enable_interface - Enable all the endpoints for an interface * @dev: the device whose interface is being enabled * @intf: pointer to the interface descriptor + * @reset_toggles: flag to set the endpoints' toggles back to 0 * * Enables all the endpoints for the interface's current altsetting. */ -static void usb_enable_interface(struct usb_device *dev, - struct usb_interface *intf) +void usb_enable_interface(struct usb_device *dev, + struct usb_interface *intf, bool reset_toggles) { struct usb_host_interface *alt = intf->cur_altsetting; int i; for (i = 0; i < alt->desc.bNumEndpoints; ++i) - usb_enable_endpoint(dev, &alt->endpoint[i]); + usb_enable_endpoint(dev, &alt->endpoint[i], reset_toggles); } /** @@ -1235,8 +1270,10 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) */ /* prevent submissions using previous endpoint settings */ - if (iface->cur_altsetting != alt) + if (iface->cur_altsetting != alt) { + remove_intf_ep_devs(iface); usb_remove_sysfs_intf_files(iface); + } usb_disable_interface(dev, iface); iface->cur_altsetting = alt; @@ -1271,10 +1308,11 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate) * during the SETUP stage - hence EP0 toggles are "don't care" here. * (Likewise, EP0 never "halts" on well designed devices.) */ - usb_enable_interface(dev, iface); - if (device_is_registered(&iface->dev)) + usb_enable_interface(dev, iface, true); + if (device_is_registered(&iface->dev)) { usb_create_sysfs_intf_files(iface); - + create_intf_ep_devs(iface); + } return 0; } EXPORT_SYMBOL_GPL(usb_set_interface); @@ -1334,7 +1372,6 @@ int usb_reset_configuration(struct usb_device *dev) struct usb_interface *intf = config->interface[i]; struct usb_host_interface *alt; - usb_remove_sysfs_intf_files(intf); alt = usb_altnum_to_altsetting(intf, 0); /* No altsetting 0? We'll assume the first altsetting. @@ -1345,10 +1382,16 @@ int usb_reset_configuration(struct usb_device *dev) if (!alt) alt = &intf->altsetting[0]; + if (alt != intf->cur_altsetting) { + remove_intf_ep_devs(intf); + usb_remove_sysfs_intf_files(intf); + } intf->cur_altsetting = alt; - usb_enable_interface(dev, intf); - if (device_is_registered(&intf->dev)) + usb_enable_interface(dev, intf, true); + if (device_is_registered(&intf->dev)) { usb_create_sysfs_intf_files(intf); + create_intf_ep_devs(intf); + } } return 0; } @@ -1441,6 +1484,46 @@ static struct usb_interface_assoc_descriptor *find_iad(struct usb_device *dev, return retval; } + +/* + * Internal function to queue a device reset + * + * This is initialized into the workstruct in 'struct + * usb_device->reset_ws' that is launched by + * message.c:usb_set_configuration() when initializing each 'struct + * usb_interface'. + * + * It is safe to get the USB device without reference counts because + * the life cycle of @iface is bound to the life cycle of @udev. Then, + * this function will be ran only if @iface is alive (and before + * freeing it any scheduled instances of it will have been cancelled). + * + * We need to set a flag (usb_dev->reset_running) because when we call + * the reset, the interfaces might be unbound. The current interface + * cannot try to remove the queued work as it would cause a deadlock + * (you cannot remove your work from within your executing + * workqueue). This flag lets it know, so that + * usb_cancel_queued_reset() doesn't try to do it. + * + * See usb_queue_reset_device() for more details + */ +void __usb_queue_reset_device(struct work_struct *ws) +{ + int rc; + struct usb_interface *iface = + container_of(ws, struct usb_interface, reset_ws); + struct usb_device *udev = interface_to_usbdev(iface); + + rc = usb_lock_device_for_reset(udev, iface); + if (rc >= 0) { + iface->reset_running = 1; + usb_reset_device(udev); + iface->reset_running = 0; + usb_unlock_device(udev); + } +} + + /* * usb_set_configuration - Makes a particular device setting be current * @dev: the device whose configuration is being updated @@ -1560,6 +1643,9 @@ free_interfaces: if (dev->state != USB_STATE_ADDRESS) usb_disable_device(dev, 1); /* Skip ep0 */ + /* Get rid of pending async Set-Config requests for this device */ + cancel_async_set_config(dev); + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), USB_REQ_SET_CONFIGURATION, 0, configuration, 0, NULL, 0, USB_CTRL_SET_TIMEOUT); @@ -1604,13 +1690,14 @@ free_interfaces: alt = &intf->altsetting[0]; intf->cur_altsetting = alt; - usb_enable_interface(dev, intf); + usb_enable_interface(dev, intf, true); intf->dev.parent = &dev->dev; intf->dev.driver = NULL; intf->dev.bus = &usb_bus_type; intf->dev.type = &usb_if_device_type; intf->dev.groups = usb_interface_groups; intf->dev.dma_mask = dev->dev.dma_mask; + INIT_WORK(&intf->reset_ws, __usb_queue_reset_device); device_initialize(&intf->dev); mark_quiesced(intf); dev_set_name(&intf->dev, "%d-%s:%d.%d", @@ -1641,17 +1728,21 @@ free_interfaces: dev_name(&intf->dev), ret); continue; } - usb_create_sysfs_intf_files(intf); + create_intf_ep_devs(intf); } usb_autosuspend_device(dev); return 0; } +static LIST_HEAD(set_config_list); +static DEFINE_SPINLOCK(set_config_lock); + struct set_config_request { struct usb_device *udev; int config; struct work_struct work; + struct list_head node; }; /* Worker routine for usb_driver_set_configuration() */ @@ -1659,14 +1750,35 @@ static void driver_set_config_work(struct work_struct *work) { struct set_config_request *req = container_of(work, struct set_config_request, work); + struct usb_device *udev = req->udev; + + usb_lock_device(udev); + spin_lock(&set_config_lock); + list_del(&req->node); + spin_unlock(&set_config_lock); - usb_lock_device(req->udev); - usb_set_configuration(req->udev, req->config); - usb_unlock_device(req->udev); - usb_put_dev(req->udev); + if (req->config >= -1) /* Is req still valid? */ + usb_set_configuration(udev, req->config); + usb_unlock_device(udev); + usb_put_dev(udev); kfree(req); } +/* Cancel pending Set-Config requests for a device whose configuration + * was just changed + */ +static void cancel_async_set_config(struct usb_device *udev) +{ + struct set_config_request *req; + + spin_lock(&set_config_lock); + list_for_each_entry(req, &set_config_list, node) { + if (req->udev == udev) + req->config = -999; /* Mark as cancelled */ + } + spin_unlock(&set_config_lock); +} + /** * usb_driver_set_configuration - Provide a way for drivers to change device configurations * @udev: the device whose configuration is being updated @@ -1698,6 +1810,10 @@ int usb_driver_set_configuration(struct usb_device *udev, int config) req->config = config; INIT_WORK(&req->work, driver_set_config_work); + spin_lock(&set_config_lock); + list_add(&req->node, &set_config_list); + spin_unlock(&set_config_lock); + usb_get_dev(udev); schedule_work(&req->work); return 0; diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 4fb65fdc9dc3..4cc2456ef3be 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -359,19 +359,19 @@ set_level(struct device *dev, struct device_attribute *attr, strncmp(buf, on_string, len) == 0) { udev->autosuspend_disabled = 1; udev->autoresume_disabled = 0; - rc = usb_external_resume_device(udev); + rc = usb_external_resume_device(udev, PMSG_USER_RESUME); } else if (len == sizeof auto_string - 1 && strncmp(buf, auto_string, len) == 0) { udev->autosuspend_disabled = 0; udev->autoresume_disabled = 0; - rc = usb_external_resume_device(udev); + rc = usb_external_resume_device(udev, PMSG_USER_RESUME); } else if (len == sizeof suspend_string - 1 && strncmp(buf, suspend_string, len) == 0) { udev->autosuspend_disabled = 0; udev->autoresume_disabled = 1; - rc = usb_external_suspend_device(udev, PMSG_SUSPEND); + rc = usb_external_suspend_device(udev, PMSG_USER_SUSPEND); } else rc = -EINVAL; @@ -629,9 +629,6 @@ int usb_create_sysfs_dev_files(struct usb_device *udev) struct device *dev = &udev->dev; int retval; - /* Unforunately these attributes cannot be created before - * the uevent is broadcast. - */ retval = device_create_bin_file(dev, &dev_bin_attr_descriptors); if (retval) goto error; @@ -643,11 +640,7 @@ int usb_create_sysfs_dev_files(struct usb_device *udev) retval = add_power_attributes(dev); if (retval) goto error; - - retval = usb_create_ep_files(dev, &udev->ep0, udev); - if (retval) - goto error; - return 0; + return retval; error: usb_remove_sysfs_dev_files(udev); return retval; @@ -657,7 +650,6 @@ void usb_remove_sysfs_dev_files(struct usb_device *udev) { struct device *dev = &udev->dev; - usb_remove_ep_files(&udev->ep0); remove_power_attributes(dev); remove_persist_attributes(dev); device_remove_bin_file(dev, &dev_bin_attr_descriptors); @@ -812,28 +804,6 @@ struct attribute_group *usb_interface_groups[] = { NULL }; -static inline void usb_create_intf_ep_files(struct usb_interface *intf, - struct usb_device *udev) -{ - struct usb_host_interface *iface_desc; - int i; - - iface_desc = intf->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) - usb_create_ep_files(&intf->dev, &iface_desc->endpoint[i], - udev); -} - -static inline void usb_remove_intf_ep_files(struct usb_interface *intf) -{ - struct usb_host_interface *iface_desc; - int i; - - iface_desc = intf->cur_altsetting; - for (i = 0; i < iface_desc->desc.bNumEndpoints; ++i) - usb_remove_ep_files(&iface_desc->endpoint[i]); -} - int usb_create_sysfs_intf_files(struct usb_interface *intf) { struct usb_device *udev = interface_to_usbdev(intf); @@ -843,26 +813,19 @@ int usb_create_sysfs_intf_files(struct usb_interface *intf) if (intf->sysfs_files_created || intf->unregistering) return 0; - /* The interface string may be present in some altsettings - * and missing in others. Hence its attribute cannot be created - * before the uevent is broadcast. - */ if (alt->string == NULL) alt->string = usb_cache_string(udev, alt->desc.iInterface); if (alt->string) retval = device_create_file(&intf->dev, &dev_attr_interface); - usb_create_intf_ep_files(intf, udev); intf->sysfs_files_created = 1; return 0; } void usb_remove_sysfs_intf_files(struct usb_interface *intf) { - struct device *dev = &intf->dev; - if (!intf->sysfs_files_created) return; - usb_remove_intf_ep_files(intf); - device_remove_file(dev, &dev_attr_interface); + + device_remove_file(&intf->dev, &dev_attr_interface); intf->sysfs_files_created = 0; } diff --git a/drivers/usb/core/urb.c b/drivers/usb/core/urb.c index 1f68af9db3f7..58bc5e3c2560 100644 --- a/drivers/usb/core/urb.c +++ b/drivers/usb/core/urb.c @@ -10,7 +10,6 @@ #define to_urb(d) container_of(d, struct urb, kref) -static DEFINE_SPINLOCK(usb_reject_lock); static void urb_destroy(struct kref *kref) { @@ -131,9 +130,7 @@ void usb_anchor_urb(struct urb *urb, struct usb_anchor *anchor) urb->anchor = anchor; if (unlikely(anchor->poisoned)) { - spin_lock(&usb_reject_lock); - urb->reject++; - spin_unlock(&usb_reject_lock); + atomic_inc(&urb->reject); } spin_unlock_irqrestore(&anchor->lock, flags); @@ -565,16 +562,12 @@ void usb_kill_urb(struct urb *urb) might_sleep(); if (!(urb && urb->dev && urb->ep)) return; - spin_lock_irq(&usb_reject_lock); - ++urb->reject; - spin_unlock_irq(&usb_reject_lock); + atomic_inc(&urb->reject); usb_hcd_unlink_urb(urb, -ENOENT); wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); - spin_lock_irq(&usb_reject_lock); - --urb->reject; - spin_unlock_irq(&usb_reject_lock); + atomic_dec(&urb->reject); } EXPORT_SYMBOL_GPL(usb_kill_urb); @@ -606,9 +599,7 @@ void usb_poison_urb(struct urb *urb) might_sleep(); if (!(urb && urb->dev && urb->ep)) return; - spin_lock_irq(&usb_reject_lock); - ++urb->reject; - spin_unlock_irq(&usb_reject_lock); + atomic_inc(&urb->reject); usb_hcd_unlink_urb(urb, -ENOENT); wait_event(usb_kill_urb_queue, atomic_read(&urb->use_count) == 0); @@ -617,14 +608,10 @@ EXPORT_SYMBOL_GPL(usb_poison_urb); void usb_unpoison_urb(struct urb *urb) { - unsigned long flags; - if (!urb) return; - spin_lock_irqsave(&usb_reject_lock, flags); - --urb->reject; - spin_unlock_irqrestore(&usb_reject_lock, flags); + atomic_dec(&urb->reject); } EXPORT_SYMBOL_GPL(usb_unpoison_urb); @@ -692,6 +679,26 @@ void usb_poison_anchored_urbs(struct usb_anchor *anchor) EXPORT_SYMBOL_GPL(usb_poison_anchored_urbs); /** + * usb_unpoison_anchored_urbs - let an anchor be used successfully again + * @anchor: anchor the requests are bound to + * + * Reverses the effect of usb_poison_anchored_urbs + * the anchor can be used normally after it returns + */ +void usb_unpoison_anchored_urbs(struct usb_anchor *anchor) +{ + unsigned long flags; + struct urb *lazarus; + + spin_lock_irqsave(&anchor->lock, flags); + list_for_each_entry(lazarus, &anchor->urb_list, anchor_list) { + usb_unpoison_urb(lazarus); + } + anchor->poisoned = 0; + spin_unlock_irqrestore(&anchor->lock, flags); +} +EXPORT_SYMBOL_GPL(usb_unpoison_anchored_urbs); +/** * usb_unlink_anchored_urbs - asynchronously cancel transfer requests en masse * @anchor: anchor the requests are bound to * diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 399e15fc5052..dcfc072630c1 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -253,7 +253,7 @@ static int usb_dev_prepare(struct device *dev) static void usb_dev_complete(struct device *dev) { /* Currently used only for rebinding interfaces */ - usb_resume(dev); /* Implement eventually? */ + usb_resume(dev, PMSG_RESUME); /* Message event is meaningless */ } static int usb_dev_suspend(struct device *dev) @@ -263,7 +263,7 @@ static int usb_dev_suspend(struct device *dev) static int usb_dev_resume(struct device *dev) { - return usb_resume(dev); + return usb_resume(dev, PMSG_RESUME); } static int usb_dev_freeze(struct device *dev) @@ -273,7 +273,7 @@ static int usb_dev_freeze(struct device *dev) static int usb_dev_thaw(struct device *dev) { - return usb_resume(dev); + return usb_resume(dev, PMSG_THAW); } static int usb_dev_poweroff(struct device *dev) @@ -283,7 +283,7 @@ static int usb_dev_poweroff(struct device *dev) static int usb_dev_restore(struct device *dev) { - return usb_resume(dev); + return usb_resume(dev, PMSG_RESTORE); } static struct dev_pm_ops usb_device_pm_ops = { @@ -362,7 +362,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, dev->ep0.desc.bLength = USB_DT_ENDPOINT_SIZE; dev->ep0.desc.bDescriptorType = USB_DT_ENDPOINT; /* ep0 maxpacket comes later, from device descriptor */ - usb_enable_endpoint(dev, &dev->ep0); + usb_enable_endpoint(dev, &dev->ep0, true); dev->can_submit = 1; /* Save readable and stable topology id, distinguishing devices @@ -402,6 +402,7 @@ struct usb_device *usb_alloc_dev(struct usb_device *parent, #ifdef CONFIG_PM mutex_init(&dev->pm_mutex); INIT_DELAYED_WORK(&dev->autosuspend, usb_autosuspend_work); + INIT_WORK(&dev->autoresume, usb_autoresume_work); dev->autosuspend_delay = usb_autosuspend_delay * HZ; dev->connect_time = jiffies; dev->active_duration = -jiffies; @@ -513,10 +514,7 @@ EXPORT_SYMBOL_GPL(usb_put_intf); * disconnect; in some drivers (such as usb-storage) the disconnect() * or suspend() method will block waiting for a device reset to complete. * - * Returns a negative error code for failure, otherwise 1 or 0 to indicate - * that the device will or will not have to be unlocked. (0 can be - * returned when an interface is given and is BINDING, because in that - * case the driver already owns the device lock.) + * Returns a negative error code for failure, otherwise 0. */ int usb_lock_device_for_reset(struct usb_device *udev, const struct usb_interface *iface) @@ -527,16 +525,9 @@ int usb_lock_device_for_reset(struct usb_device *udev, return -ENODEV; if (udev->state == USB_STATE_SUSPENDED) return -EHOSTUNREACH; - if (iface) { - switch (iface->condition) { - case USB_INTERFACE_BINDING: - return 0; - case USB_INTERFACE_BOUND: - break; - default: - return -EINTR; - } - } + if (iface && (iface->condition == USB_INTERFACE_UNBINDING || + iface->condition == USB_INTERFACE_UNBOUND)) + return -EINTR; while (usb_trylock_device(udev) != 0) { @@ -550,10 +541,11 @@ int usb_lock_device_for_reset(struct usb_device *udev, return -ENODEV; if (udev->state == USB_STATE_SUSPENDED) return -EHOSTUNREACH; - if (iface && iface->condition != USB_INTERFACE_BOUND) + if (iface && (iface->condition == USB_INTERFACE_UNBINDING || + iface->condition == USB_INTERFACE_UNBOUND)) return -EINTR; } - return 1; + return 0; } EXPORT_SYMBOL_GPL(usb_lock_device_for_reset); @@ -962,8 +954,12 @@ void usb_buffer_unmap_sg(const struct usb_device *dev, int is_in, } EXPORT_SYMBOL_GPL(usb_buffer_unmap_sg); -/* format to disable USB on kernel command line is: nousb */ -__module_param_call("", nousb, param_set_bool, param_get_bool, &nousb, 0444); +/* To disable USB, kernel command line is 'nousb' not 'usbcore.nousb' */ +#ifdef MODULE +module_param(nousb, bool, 0444); +#else +core_param(nousb, nousb, bool, 0444); +#endif /* * for external read access to <nousb> @@ -975,6 +971,37 @@ int usb_disabled(void) EXPORT_SYMBOL_GPL(usb_disabled); /* + * Notifications of device and interface registration + */ +static int usb_bus_notify(struct notifier_block *nb, unsigned long action, + void *data) +{ + struct device *dev = data; + + switch (action) { + case BUS_NOTIFY_ADD_DEVICE: + if (dev->type == &usb_device_type) + (void) usb_create_sysfs_dev_files(to_usb_device(dev)); + else if (dev->type == &usb_if_device_type) + (void) usb_create_sysfs_intf_files( + to_usb_interface(dev)); + break; + + case BUS_NOTIFY_DEL_DEVICE: + if (dev->type == &usb_device_type) + usb_remove_sysfs_dev_files(to_usb_device(dev)); + else if (dev->type == &usb_if_device_type) + usb_remove_sysfs_intf_files(to_usb_interface(dev)); + break; + } + return 0; +} + +static struct notifier_block usb_bus_nb = { + .notifier_call = usb_bus_notify, +}; + +/* * Init */ static int __init usb_init(void) @@ -991,6 +1018,9 @@ static int __init usb_init(void) retval = bus_register(&usb_bus_type); if (retval) goto bus_register_failed; + retval = bus_register_notifier(&usb_bus_type, &usb_bus_nb); + if (retval) + goto bus_notifier_failed; retval = usb_host_init(); if (retval) goto host_init_failed; @@ -1025,6 +1055,8 @@ driver_register_failed: major_init_failed: usb_host_cleanup(); host_init_failed: + bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); +bus_notifier_failed: bus_unregister(&usb_bus_type); bus_register_failed: ksuspend_usb_cleanup(); @@ -1048,6 +1080,7 @@ static void __exit usb_exit(void) usb_devio_cleanup(); usb_hub_cleanup(); usb_host_cleanup(); + bus_unregister_notifier(&usb_bus_type, &usb_bus_nb); bus_unregister(&usb_bus_type); ksuspend_usb_cleanup(); } diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 9a1a45ac3add..386177867a8a 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -1,16 +1,20 @@ +#include <linux/pm.h> + /* Functions local to drivers/usb/core/ */ extern int usb_create_sysfs_dev_files(struct usb_device *dev); extern void usb_remove_sysfs_dev_files(struct usb_device *dev); extern int usb_create_sysfs_intf_files(struct usb_interface *intf); extern void usb_remove_sysfs_intf_files(struct usb_interface *intf); -extern int usb_create_ep_files(struct device *parent, +extern int usb_create_ep_devs(struct device *parent, struct usb_host_endpoint *endpoint, struct usb_device *udev); -extern void usb_remove_ep_files(struct usb_host_endpoint *endpoint); +extern void usb_remove_ep_devs(struct usb_host_endpoint *endpoint); extern void usb_enable_endpoint(struct usb_device *dev, - struct usb_host_endpoint *ep); + struct usb_host_endpoint *ep, bool reset_toggle); +extern void usb_enable_interface(struct usb_device *dev, + struct usb_interface *intf, bool reset_toggles); extern void usb_disable_endpoint(struct usb_device *dev, unsigned int epaddr); extern void usb_disable_interface(struct usb_device *dev, struct usb_interface *intf); @@ -42,14 +46,16 @@ extern void usb_host_cleanup(void); #ifdef CONFIG_PM extern int usb_suspend(struct device *dev, pm_message_t msg); -extern int usb_resume(struct device *dev); +extern int usb_resume(struct device *dev, pm_message_t msg); extern void usb_autosuspend_work(struct work_struct *work); -extern int usb_port_suspend(struct usb_device *dev); -extern int usb_port_resume(struct usb_device *dev); +extern void usb_autoresume_work(struct work_struct *work); +extern int usb_port_suspend(struct usb_device *dev, pm_message_t msg); +extern int usb_port_resume(struct usb_device *dev, pm_message_t msg); extern int usb_external_suspend_device(struct usb_device *udev, pm_message_t msg); -extern int usb_external_resume_device(struct usb_device *udev); +extern int usb_external_resume_device(struct usb_device *udev, + pm_message_t msg); static inline void usb_pm_lock(struct usb_device *udev) { @@ -63,12 +69,12 @@ static inline void usb_pm_unlock(struct usb_device *udev) #else -static inline int usb_port_suspend(struct usb_device *udev) +static inline int usb_port_suspend(struct usb_device *udev, pm_message_t msg) { return 0; } -static inline int usb_port_resume(struct usb_device *udev) +static inline int usb_port_resume(struct usb_device *udev, pm_message_t msg) { return 0; } diff --git a/drivers/usb/gadget/Kconfig b/drivers/usb/gadget/Kconfig index dd4cd5a51370..3219d137340a 100644 --- a/drivers/usb/gadget/Kconfig +++ b/drivers/usb/gadget/Kconfig @@ -297,13 +297,34 @@ config USB_S3C2410_DEBUG # musb builds in ../musb along with host support config USB_GADGET_MUSB_HDRC - boolean "Inventra HDRC USB Peripheral (TI, ...)" + boolean "Inventra HDRC USB Peripheral (TI, ADI, ...)" depends on USB_MUSB_HDRC && (USB_MUSB_PERIPHERAL || USB_MUSB_OTG) select USB_GADGET_DUALSPEED select USB_GADGET_SELECTED help This OTG-capable silicon IP is used in dual designs including - the TI DaVinci, OMAP 243x, OMAP 343x, and TUSB 6010. + the TI DaVinci, OMAP 243x, OMAP 343x, TUSB 6010, and ADI Blackfin + +config USB_GADGET_IMX + boolean "Freescale IMX USB Peripheral Controller" + depends on ARCH_MX1 + help + Freescale's IMX series include an integrated full speed + USB 1.1 device controller. The controller in the IMX series + is register-compatible. + + It has Six fixed-function endpoints, as well as endpoint + zero (for control transfers). + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "imx_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_IMX + tristate + depends on USB_GADGET_IMX + default USB_GADGET + select USB_GADGET_SELECTED config USB_GADGET_M66592 boolean "Renesas M66592 USB Peripheral Controller" @@ -377,6 +398,24 @@ config USB_FSL_QE default USB_GADGET select USB_GADGET_SELECTED +config USB_GADGET_CI13XXX + boolean "MIPS USB CI13xxx" + depends on PCI + select USB_GADGET_DUALSPEED + help + MIPS USB IP core family device controller + Currently it only supports IP part number CI13412 + + Say "y" to link the driver statically, or "m" to build a + dynamically linked module called "ci13xxx_udc" and force all + gadget drivers to also be dynamically linked. + +config USB_CI13XXX + tristate + depends on USB_GADGET_CI13XXX + default USB_GADGET + select USB_GADGET_SELECTED + config USB_GADGET_NET2280 boolean "NetChip 228x" depends on PCI diff --git a/drivers/usb/gadget/Makefile b/drivers/usb/gadget/Makefile index bd4041b47dce..39a51d746cb7 100644 --- a/drivers/usb/gadget/Makefile +++ b/drivers/usb/gadget/Makefile @@ -10,6 +10,7 @@ obj-$(CONFIG_USB_NET2280) += net2280.o obj-$(CONFIG_USB_AMD5536UDC) += amd5536udc.o obj-$(CONFIG_USB_PXA25X) += pxa25x_udc.o 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 @@ -19,6 +20,7 @@ obj-$(CONFIG_USB_ATMEL_USBA) += atmel_usba_udc.o obj-$(CONFIG_USB_FSL_USB2) += fsl_usb2_udc.o obj-$(CONFIG_USB_M66592) += m66592-udc.o obj-$(CONFIG_USB_FSL_QE) += fsl_qe_udc.o +obj-$(CONFIG_USB_CI13XXX) += ci13xxx_udc.o # # USB gadget drivers diff --git a/drivers/usb/gadget/ci13xxx_udc.c b/drivers/usb/gadget/ci13xxx_udc.c new file mode 100644 index 000000000000..bebf911c7e5f --- /dev/null +++ b/drivers/usb/gadget/ci13xxx_udc.c @@ -0,0 +1,2830 @@ +/* + * ci13xxx_udc.c - MIPS USB IP core family device controller + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * 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. + */ + +/* + * Description: MIPS USB IP core family device controller + * Currently it only supports IP part number CI13412 + * + * This driver is composed of several blocks: + * - HW: hardware interface + * - DBG: debug facilities (optional) + * - UTIL: utilities + * - ISR: interrupts handling + * - ENDPT: endpoint operations (Gadget API) + * - GADGET: gadget operations (Gadget API) + * - BUS: bus glue code, bus abstraction layer + * - PCI: PCI core interface and PCI resources (interrupts, memory...) + * + * Compile Options + * - CONFIG_USB_GADGET_DEBUG_FILES: enable debug facilities + * - STALL_IN: non-empty bulk-in pipes cannot be halted + * if defined mass storage compliance succeeds but with warnings + * => case 4: Hi > Dn + * => case 5: Hi > Di + * => case 8: Hi <> Do + * if undefined usbtest 13 fails + * - TRACE: enable function tracing (depends on DEBUG) + * + * Main Features + * - Chapter 9 & Mass Storage Compliance with Gadget File Storage + * - Chapter 9 Compliance with Gadget Zero (STALL_IN undefined) + * - Normal & LPM support + * + * USBTEST Report + * - OK: 0-12, 13 (STALL_IN defined) & 14 + * - Not Supported: 15 & 16 (ISO) + * + * TODO List + * - OTG + * - Isochronous & Interrupt Traffic + * - Handle requests which spawns into several TDs + * - GET_STATUS(device) - always reports 0 + * - Gadget API (majority of optional features) + * - Suspend & Remote Wakeup + */ +#include <linux/device.h> +#include <linux/dmapool.h> +#include <linux/dma-mapping.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include "ci13xxx_udc.h" + + +/****************************************************************************** + * DEFINE + *****************************************************************************/ +/* ctrl register bank access */ +static DEFINE_SPINLOCK(udc_lock); + +/* driver name */ +#define UDC_DRIVER_NAME "ci13xxx_udc" + +/* control endpoint description */ +static const struct usb_endpoint_descriptor +ctrl_endpt_desc = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + .wMaxPacketSize = cpu_to_le16(CTRL_PAYLOAD_MAX), +}; + +/* UDC descriptor */ +static struct ci13xxx *_udc; + +/* Interrupt statistics */ +#define ISR_MASK 0x1F +static struct { + u32 test; + u32 ui; + u32 uei; + u32 pci; + u32 uri; + u32 sli; + u32 none; + struct { + u32 cnt; + u32 buf[ISR_MASK+1]; + u32 idx; + } hndl; +} isr_statistics; + +/** + * ffs_nr: find first (least significant) bit set + * @x: the word to search + * + * This function returns bit number (instead of position) + */ +static int ffs_nr(u32 x) +{ + int n = ffs(x); + + return n ? n-1 : 32; +} + +/****************************************************************************** + * HW block + *****************************************************************************/ +/* register bank descriptor */ +static struct { + unsigned lpm; /* is LPM? */ + void __iomem *abs; /* bus map offset */ + void __iomem *cap; /* bus map offset + CAP offset + CAP data */ + size_t size; /* bank size */ +} hw_bank; + +/* UDC register map */ +#define ABS_CAPLENGTH (0x100UL) +#define ABS_HCCPARAMS (0x108UL) +#define ABS_DCCPARAMS (0x124UL) +#define ABS_TESTMODE (hw_bank.lpm ? 0x0FCUL : 0x138UL) +/* offset to CAPLENTGH (addr + data) */ +#define CAP_USBCMD (0x000UL) +#define CAP_USBSTS (0x004UL) +#define CAP_USBINTR (0x008UL) +#define CAP_DEVICEADDR (0x014UL) +#define CAP_ENDPTLISTADDR (0x018UL) +#define CAP_PORTSC (0x044UL) +#define CAP_DEVLC (0x0B4UL) +#define CAP_USBMODE (hw_bank.lpm ? 0x0C8UL : 0x068UL) +#define CAP_ENDPTSETUPSTAT (hw_bank.lpm ? 0x0D8UL : 0x06CUL) +#define CAP_ENDPTPRIME (hw_bank.lpm ? 0x0DCUL : 0x070UL) +#define CAP_ENDPTFLUSH (hw_bank.lpm ? 0x0E0UL : 0x074UL) +#define CAP_ENDPTSTAT (hw_bank.lpm ? 0x0E4UL : 0x078UL) +#define CAP_ENDPTCOMPLETE (hw_bank.lpm ? 0x0E8UL : 0x07CUL) +#define CAP_ENDPTCTRL (hw_bank.lpm ? 0x0ECUL : 0x080UL) +#define CAP_LAST (hw_bank.lpm ? 0x12CUL : 0x0C0UL) + +/* maximum number of enpoints: valid only after hw_device_reset() */ +static unsigned hw_ep_max; + +/** + * hw_ep_bit: calculates the bit number + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns bit number + */ +static inline int hw_ep_bit(int num, int dir) +{ + return num + (dir ? 16 : 0); +} + +/** + * hw_aread: reads from register bitfield + * @addr: address relative to bus map + * @mask: bitfield mask + * + * This function returns register bitfield data + */ +static u32 hw_aread(u32 addr, u32 mask) +{ + return ioread32(addr + hw_bank.abs) & mask; +} + +/** + * hw_awrite: writes to register bitfield + * @addr: address relative to bus map + * @mask: bitfield mask + * @data: new data + */ +static void hw_awrite(u32 addr, u32 mask, u32 data) +{ + iowrite32(hw_aread(addr, ~mask) | (data & mask), + addr + hw_bank.abs); +} + +/** + * hw_cread: reads from register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * + * This function returns register bitfield data + */ +static u32 hw_cread(u32 addr, u32 mask) +{ + return ioread32(addr + hw_bank.cap) & mask; +} + +/** + * hw_cwrite: writes to register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * @data: new data + */ +static void hw_cwrite(u32 addr, u32 mask, u32 data) +{ + iowrite32(hw_cread(addr, ~mask) | (data & mask), + addr + hw_bank.cap); +} + +/** + * hw_ctest_and_clear: tests & clears register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * + * This function returns register bitfield data + */ +static u32 hw_ctest_and_clear(u32 addr, u32 mask) +{ + u32 reg = hw_cread(addr, mask); + + iowrite32(reg, addr + hw_bank.cap); + return reg; +} + +/** + * hw_ctest_and_write: tests & writes register bitfield + * @addr: address relative to CAP offset plus content + * @mask: bitfield mask + * @data: new data + * + * This function returns register bitfield data + */ +static u32 hw_ctest_and_write(u32 addr, u32 mask, u32 data) +{ + u32 reg = hw_cread(addr, ~0); + + iowrite32((reg & ~mask) | (data & mask), addr + hw_bank.cap); + return (reg & mask) >> ffs_nr(mask); +} + +/** + * hw_device_reset: resets chip (execute without interruption) + * @base: register base address + * + * This function returns an error code + */ +static int hw_device_reset(void __iomem *base) +{ + u32 reg; + + /* bank is a module variable */ + hw_bank.abs = base; + + hw_bank.cap = hw_bank.abs; + hw_bank.cap += ABS_CAPLENGTH; + hw_bank.cap += ioread8(hw_bank.cap); + + reg = hw_aread(ABS_HCCPARAMS, HCCPARAMS_LEN) >> ffs_nr(HCCPARAMS_LEN); + hw_bank.lpm = reg; + hw_bank.size = hw_bank.cap - hw_bank.abs; + hw_bank.size += CAP_LAST; + hw_bank.size /= sizeof(u32); + + /* should flush & stop before reset */ + hw_cwrite(CAP_ENDPTFLUSH, ~0, ~0); + hw_cwrite(CAP_USBCMD, USBCMD_RS, 0); + + hw_cwrite(CAP_USBCMD, USBCMD_RST, USBCMD_RST); + while (hw_cread(CAP_USBCMD, USBCMD_RST)) + udelay(10); /* not RTOS friendly */ + + /* USBMODE should be configured step by step */ + hw_cwrite(CAP_USBMODE, USBMODE_CM, USBMODE_CM_IDLE); + hw_cwrite(CAP_USBMODE, USBMODE_CM, USBMODE_CM_DEVICE); + hw_cwrite(CAP_USBMODE, USBMODE_SLOM, USBMODE_SLOM); /* HW >= 2.3 */ + + if (hw_cread(CAP_USBMODE, USBMODE_CM) != USBMODE_CM_DEVICE) { + pr_err("cannot enter in device mode"); + pr_err("lpm = %i", hw_bank.lpm); + return -ENODEV; + } + + reg = hw_aread(ABS_DCCPARAMS, DCCPARAMS_DEN) >> ffs_nr(DCCPARAMS_DEN); + if (reg == 0 || reg > ENDPT_MAX) + return -ENODEV; + + hw_ep_max = reg; /* cache hw ENDPT_MAX */ + + /* setup lock mode ? */ + + /* ENDPTSETUPSTAT is '0' by default */ + + /* HCSPARAMS.bf.ppc SHOULD BE zero for device */ + + return 0; +} + +/** + * hw_device_state: enables/disables interrupts & starts/stops device (execute + * without interruption) + * @dma: 0 => disable, !0 => enable and set dma engine + * + * This function returns an error code + */ +static int hw_device_state(u32 dma) +{ + if (dma) { + hw_cwrite(CAP_ENDPTLISTADDR, ~0, dma); + /* interrupt, error, port change, reset, sleep/suspend */ + hw_cwrite(CAP_USBINTR, ~0, + USBi_UI|USBi_UEI|USBi_PCI|USBi_URI|USBi_SLI); + hw_cwrite(CAP_USBCMD, USBCMD_RS, USBCMD_RS); + } else { + hw_cwrite(CAP_USBCMD, USBCMD_RS, 0); + hw_cwrite(CAP_USBINTR, ~0, 0); + } + return 0; +} + +/** + * hw_ep_flush: flush endpoint fifo (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_flush(int num, int dir) +{ + int n = hw_ep_bit(num, dir); + + do { + /* flush any pending transfer */ + hw_cwrite(CAP_ENDPTFLUSH, BIT(n), BIT(n)); + while (hw_cread(CAP_ENDPTFLUSH, BIT(n))) + cpu_relax(); + } while (hw_cread(CAP_ENDPTSTAT, BIT(n))); + + return 0; +} + +/** + * hw_ep_disable: disables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns an error code + */ +static int hw_ep_disable(int num, int dir) +{ + hw_ep_flush(num, dir); + hw_cwrite(CAP_ENDPTCTRL + num * sizeof(u32), + dir ? ENDPTCTRL_TXE : ENDPTCTRL_RXE, 0); + return 0; +} + +/** + * hw_ep_enable: enables endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @type: endpoint type + * + * This function returns an error code + */ +static int hw_ep_enable(int num, int dir, int type) +{ + u32 mask, data; + + if (dir) { + mask = ENDPTCTRL_TXT; /* type */ + data = type << ffs_nr(mask); + + mask |= ENDPTCTRL_TXS; /* unstall */ + mask |= ENDPTCTRL_TXR; /* reset data toggle */ + data |= ENDPTCTRL_TXR; + mask |= ENDPTCTRL_TXE; /* enable */ + data |= ENDPTCTRL_TXE; + } else { + mask = ENDPTCTRL_RXT; /* type */ + data = type << ffs_nr(mask); + + mask |= ENDPTCTRL_RXS; /* unstall */ + mask |= ENDPTCTRL_RXR; /* reset data toggle */ + data |= ENDPTCTRL_RXR; + mask |= ENDPTCTRL_RXE; /* enable */ + data |= ENDPTCTRL_RXE; + } + hw_cwrite(CAP_ENDPTCTRL + num * sizeof(u32), mask, data); + return 0; +} + +/** + * hw_ep_get_halt: return endpoint halt status + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns 1 if endpoint halted + */ +static int hw_ep_get_halt(int num, int dir) +{ + u32 mask = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + + return hw_cread(CAP_ENDPTCTRL + num * sizeof(u32), mask) ? 1 : 0; +} + +/** + * hw_ep_is_primed: test if endpoint is primed (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * + * This function returns true if endpoint primed + */ +static int hw_ep_is_primed(int num, int dir) +{ + u32 reg = hw_cread(CAP_ENDPTPRIME, ~0) | hw_cread(CAP_ENDPTSTAT, ~0); + + return test_bit(hw_ep_bit(num, dir), (void *)®); +} + +/** + * hw_test_and_clear_setup_status: test & clear setup status (execute without + * interruption) + * @n: bit number (endpoint) + * + * This function returns setup status + */ +static int hw_test_and_clear_setup_status(int n) +{ + return hw_ctest_and_clear(CAP_ENDPTSETUPSTAT, BIT(n)); +} + +/** + * hw_ep_prime: primes endpoint (execute without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @is_ctrl: true if control endpoint + * + * This function returns an error code + */ +static int hw_ep_prime(int num, int dir, int is_ctrl) +{ + int n = hw_ep_bit(num, dir); + + /* the caller should flush first */ + if (hw_ep_is_primed(num, dir)) + return -EBUSY; + + if (is_ctrl && dir == RX && hw_cread(CAP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + hw_cwrite(CAP_ENDPTPRIME, BIT(n), BIT(n)); + + while (hw_cread(CAP_ENDPTPRIME, BIT(n))) + cpu_relax(); + if (is_ctrl && dir == RX && hw_cread(CAP_ENDPTSETUPSTAT, BIT(num))) + return -EAGAIN; + + /* status shoult be tested according with manual but it doesn't work */ + return 0; +} + +/** + * hw_ep_set_halt: configures ep halt & resets data toggle after clear (execute + * without interruption) + * @num: endpoint number + * @dir: endpoint direction + * @value: true => stall, false => unstall + * + * This function returns an error code + */ +static int hw_ep_set_halt(int num, int dir, int value) +{ + if (value != 0 && value != 1) + return -EINVAL; + + do { + u32 addr = CAP_ENDPTCTRL + num * sizeof(u32); + u32 mask_xs = dir ? ENDPTCTRL_TXS : ENDPTCTRL_RXS; + u32 mask_xr = dir ? ENDPTCTRL_TXR : ENDPTCTRL_RXR; + + /* data toggle - reserved for EP0 but it's in ESS */ + hw_cwrite(addr, mask_xs|mask_xr, value ? mask_xs : mask_xr); + + } while (value != hw_ep_get_halt(num, dir)); + + return 0; +} + +/** + * hw_intr_clear: disables interrupt & clears interrupt status (execute without + * interruption) + * @n: interrupt bit + * + * This function returns an error code + */ +static int hw_intr_clear(int n) +{ + if (n >= REG_BITS) + return -EINVAL; + + hw_cwrite(CAP_USBINTR, BIT(n), 0); + hw_cwrite(CAP_USBSTS, BIT(n), BIT(n)); + return 0; +} + +/** + * hw_intr_force: enables interrupt & forces interrupt status (execute without + * interruption) + * @n: interrupt bit + * + * This function returns an error code + */ +static int hw_intr_force(int n) +{ + if (n >= REG_BITS) + return -EINVAL; + + hw_awrite(ABS_TESTMODE, TESTMODE_FORCE, TESTMODE_FORCE); + hw_cwrite(CAP_USBINTR, BIT(n), BIT(n)); + hw_cwrite(CAP_USBSTS, BIT(n), BIT(n)); + hw_awrite(ABS_TESTMODE, TESTMODE_FORCE, 0); + return 0; +} + +/** + * hw_is_port_high_speed: test if port is high speed + * + * This function returns true if high speed port + */ +static int hw_port_is_high_speed(void) +{ + return hw_bank.lpm ? hw_cread(CAP_DEVLC, DEVLC_PSPD) : + hw_cread(CAP_PORTSC, PORTSC_HSP); +} + +/** + * hw_port_test_get: reads port test mode value + * + * This function returns port test mode value + */ +static u8 hw_port_test_get(void) +{ + return hw_cread(CAP_PORTSC, PORTSC_PTC) >> ffs_nr(PORTSC_PTC); +} + +/** + * hw_port_test_set: writes port test mode (execute without interruption) + * @mode: new value + * + * This function returns an error code + */ +static int hw_port_test_set(u8 mode) +{ + const u8 TEST_MODE_MAX = 7; + + if (mode > TEST_MODE_MAX) + return -EINVAL; + + hw_cwrite(CAP_PORTSC, PORTSC_PTC, mode << ffs_nr(PORTSC_PTC)); + return 0; +} + +/** + * hw_read_intr_enable: returns interrupt enable register + * + * This function returns register data + */ +static u32 hw_read_intr_enable(void) +{ + return hw_cread(CAP_USBINTR, ~0); +} + +/** + * hw_read_intr_status: returns interrupt status register + * + * This function returns register data + */ +static u32 hw_read_intr_status(void) +{ + return hw_cread(CAP_USBSTS, ~0); +} + +/** + * hw_register_read: reads all device registers (execute without interruption) + * @buf: destination buffer + * @size: buffer size + * + * This function returns number of registers read + */ +static size_t hw_register_read(u32 *buf, size_t size) +{ + unsigned i; + + if (size > hw_bank.size) + size = hw_bank.size; + + for (i = 0; i < size; i++) + buf[i] = hw_aread(i * sizeof(u32), ~0); + + return size; +} + +/** + * hw_register_write: writes to register + * @addr: register address + * @data: register value + * + * This function returns an error code + */ +static int hw_register_write(u16 addr, u32 data) +{ + /* align */ + addr /= sizeof(u32); + + if (addr >= hw_bank.size) + return -EINVAL; + + /* align */ + addr *= sizeof(u32); + + hw_awrite(addr, ~0, data); + return 0; +} + +/** + * hw_test_and_clear_complete: test & clear complete status (execute without + * interruption) + * @n: bit number (endpoint) + * + * This function returns complete status + */ +static int hw_test_and_clear_complete(int n) +{ + return hw_ctest_and_clear(CAP_ENDPTCOMPLETE, BIT(n)); +} + +/** + * hw_test_and_clear_intr_active: test & clear active interrupts (execute + * without interruption) + * + * This function returns active interrutps + */ +static u32 hw_test_and_clear_intr_active(void) +{ + u32 reg = hw_read_intr_status() & hw_read_intr_enable(); + + hw_cwrite(CAP_USBSTS, ~0, reg); + return reg; +} + +/** + * hw_test_and_clear_setup_guard: test & clear setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_clear_setup_guard(void) +{ + return hw_ctest_and_write(CAP_USBCMD, USBCMD_SUTW, 0); +} + +/** + * hw_test_and_set_setup_guard: test & set setup guard (execute without + * interruption) + * + * This function returns guard value + */ +static int hw_test_and_set_setup_guard(void) +{ + return hw_ctest_and_write(CAP_USBCMD, USBCMD_SUTW, USBCMD_SUTW); +} + +/** + * hw_usb_set_address: configures USB address (execute without interruption) + * @value: new USB address + * + * This function returns an error code + */ +static int hw_usb_set_address(u8 value) +{ + /* advance */ + hw_cwrite(CAP_DEVICEADDR, DEVICEADDR_USBADR | DEVICEADDR_USBADRA, + value << ffs_nr(DEVICEADDR_USBADR) | DEVICEADDR_USBADRA); + return 0; +} + +/** + * hw_usb_reset: restart device after a bus reset (execute without + * interruption) + * + * This function returns an error code + */ +static int hw_usb_reset(void) +{ + hw_usb_set_address(0); + + /* ESS flushes only at end?!? */ + hw_cwrite(CAP_ENDPTFLUSH, ~0, ~0); /* flush all EPs */ + + /* clear setup token semaphores */ + hw_cwrite(CAP_ENDPTSETUPSTAT, 0, 0); /* writes its content */ + + /* clear complete status */ + hw_cwrite(CAP_ENDPTCOMPLETE, 0, 0); /* writes its content */ + + /* wait until all bits cleared */ + while (hw_cread(CAP_ENDPTPRIME, ~0)) + udelay(10); /* not RTOS friendly */ + + /* reset all endpoints ? */ + + /* reset internal status and wait for further instructions + no need to verify the port reset status (ESS does it) */ + + return 0; +} + +/****************************************************************************** + * DBG block + *****************************************************************************/ +/** + * show_device: prints information about device capabilities and status + * + * Check "device.h" for details + */ +static ssize_t show_device(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct usb_gadget *gadget = &udc->gadget; + int n = 0; + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + n += scnprintf(buf + n, PAGE_SIZE - n, "speed = %d\n", + gadget->speed); + n += scnprintf(buf + n, PAGE_SIZE - n, "is_dualspeed = %d\n", + gadget->is_dualspeed); + n += scnprintf(buf + n, PAGE_SIZE - n, "is_otg = %d\n", + gadget->is_otg); + n += scnprintf(buf + n, PAGE_SIZE - n, "is_a_peripheral = %d\n", + gadget->is_a_peripheral); + n += scnprintf(buf + n, PAGE_SIZE - n, "b_hnp_enable = %d\n", + gadget->b_hnp_enable); + n += scnprintf(buf + n, PAGE_SIZE - n, "a_hnp_support = %d\n", + gadget->a_hnp_support); + n += scnprintf(buf + n, PAGE_SIZE - n, "a_alt_hnp_support = %d\n", + gadget->a_alt_hnp_support); + n += scnprintf(buf + n, PAGE_SIZE - n, "name = %s\n", + (gadget->name ? gadget->name : "")); + + return n; +} +static DEVICE_ATTR(device, S_IRUSR, show_device, NULL); + +/** + * show_driver: prints information about attached gadget (if any) + * + * Check "device.h" for details + */ +static ssize_t show_driver(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + struct usb_gadget_driver *driver = udc->driver; + int n = 0; + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + if (driver == NULL) + return scnprintf(buf, PAGE_SIZE, + "There is no gadget attached!\n"); + + n += scnprintf(buf + n, PAGE_SIZE - n, "function = %s\n", + (driver->function ? driver->function : "")); + n += scnprintf(buf + n, PAGE_SIZE - n, "max speed = %d\n", + driver->speed); + + return n; +} +static DEVICE_ATTR(driver, S_IRUSR, show_driver, NULL); + +/* Maximum event message length */ +#define DBG_DATA_MSG 64UL + +/* Maximum event messages */ +#define DBG_DATA_MAX 128UL + +/* Event buffer descriptor */ +static struct { + char (buf[DBG_DATA_MAX])[DBG_DATA_MSG]; /* buffer */ + unsigned idx; /* index */ + unsigned tty; /* print to console? */ + rwlock_t lck; /* lock */ +} dbg_data = { + .idx = 0, + .tty = 0, + .lck = __RW_LOCK_UNLOCKED(lck) +}; + +/** + * dbg_dec: decrements debug event index + * @idx: buffer index + */ +static void dbg_dec(unsigned *idx) +{ + *idx = (*idx - 1) & (DBG_DATA_MAX-1); +} + +/** + * dbg_inc: increments debug event index + * @idx: buffer index + */ +static void dbg_inc(unsigned *idx) +{ + *idx = (*idx + 1) & (DBG_DATA_MAX-1); +} + +/** + * dbg_print: prints the common part of the event + * @addr: endpoint address + * @name: event name + * @status: status + * @extra: extra information + */ +static void dbg_print(u8 addr, const char *name, int status, const char *extra) +{ + struct timeval tval; + unsigned int stamp; + unsigned long flags; + + write_lock_irqsave(&dbg_data.lck, flags); + + do_gettimeofday(&tval); + stamp = tval.tv_sec & 0xFFFF; /* 2^32 = 4294967296. Limit to 4096s */ + stamp = stamp * 1000000 + tval.tv_usec; + + scnprintf(dbg_data.buf[dbg_data.idx], DBG_DATA_MSG, + "%04X\t» %02X %-7.7s %4i «\t%s\n", + stamp, addr, name, status, extra); + + dbg_inc(&dbg_data.idx); + + write_unlock_irqrestore(&dbg_data.lck, flags); + + if (dbg_data.tty != 0) + pr_notice("%04X\t» %02X %-7.7s %4i «\t%s\n", + stamp, addr, name, status, extra); +} + +/** + * dbg_done: prints a DONE event + * @addr: endpoint address + * @td: transfer descriptor + * @status: status + */ +static void dbg_done(u8 addr, const u32 token, int status) +{ + char msg[DBG_DATA_MSG]; + + scnprintf(msg, sizeof(msg), "%d %02X", + (int)(token & TD_TOTAL_BYTES) >> ffs_nr(TD_TOTAL_BYTES), + (int)(token & TD_STATUS) >> ffs_nr(TD_STATUS)); + dbg_print(addr, "DONE", status, msg); +} + +/** + * dbg_event: prints a generic event + * @addr: endpoint address + * @name: event name + * @status: status + */ +static void dbg_event(u8 addr, const char *name, int status) +{ + if (name != NULL) + dbg_print(addr, name, status, ""); +} + +/* + * dbg_queue: prints a QUEUE event + * @addr: endpoint address + * @req: USB request + * @status: status + */ +static void dbg_queue(u8 addr, const struct usb_request *req, int status) +{ + char msg[DBG_DATA_MSG]; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%d %d", !req->no_interrupt, req->length); + dbg_print(addr, "QUEUE", status, msg); + } +} + +/** + * dbg_setup: prints a SETUP event + * @addr: endpoint address + * @req: setup request + */ +static void dbg_setup(u8 addr, const struct usb_ctrlrequest *req) +{ + char msg[DBG_DATA_MSG]; + + if (req != NULL) { + scnprintf(msg, sizeof(msg), + "%02X %02X %04X %04X %d", req->bRequestType, + req->bRequest, le16_to_cpu(req->wValue), + le16_to_cpu(req->wIndex), le16_to_cpu(req->wLength)); + dbg_print(addr, "SETUP", 0, msg); + } +} + +/** + * show_events: displays the event buffer + * + * Check "device.h" for details + */ +static ssize_t show_events(struct device *dev, struct device_attribute *attr, + char *buf) +{ + unsigned long flags; + unsigned i, j, n = 0; + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + read_lock_irqsave(&dbg_data.lck, flags); + + i = dbg_data.idx; + for (dbg_dec(&i); i != dbg_data.idx; dbg_dec(&i)) { + n += strlen(dbg_data.buf[i]); + if (n >= PAGE_SIZE) { + n -= strlen(dbg_data.buf[i]); + break; + } + } + for (j = 0, dbg_inc(&i); j < n; dbg_inc(&i)) + j += scnprintf(buf + j, PAGE_SIZE - j, + "%s", dbg_data.buf[i]); + + read_unlock_irqrestore(&dbg_data.lck, flags); + + return n; +} + +/** + * store_events: configure if events are going to be also printed to console + * + * Check "device.h" for details + */ +static ssize_t store_events(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned tty; + + dbg_trace("[%s] %p, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%u", &tty) != 1 || tty > 1) { + dev_err(dev, "<1|0>: enable|disable console log\n"); + goto done; + } + + dbg_data.tty = tty; + dev_info(dev, "tty = %u", dbg_data.tty); + + done: + return count; +} +static DEVICE_ATTR(events, S_IRUSR | S_IWUSR, show_events, store_events); + +/** + * show_inters: interrupt status, enable status and historic + * + * Check "device.h" for details + */ +static ssize_t show_inters(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + u32 intr; + unsigned i, j, n = 0; + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + + n += scnprintf(buf + n, PAGE_SIZE - n, + "status = %08x\n", hw_read_intr_status()); + n += scnprintf(buf + n, PAGE_SIZE - n, + "enable = %08x\n", hw_read_intr_enable()); + + n += scnprintf(buf + n, PAGE_SIZE - n, "*test = %d\n", + isr_statistics.test); + n += scnprintf(buf + n, PAGE_SIZE - n, "» ui = %d\n", + isr_statistics.ui); + n += scnprintf(buf + n, PAGE_SIZE - n, "» uei = %d\n", + isr_statistics.uei); + n += scnprintf(buf + n, PAGE_SIZE - n, "» pci = %d\n", + isr_statistics.pci); + n += scnprintf(buf + n, PAGE_SIZE - n, "» uri = %d\n", + isr_statistics.uri); + n += scnprintf(buf + n, PAGE_SIZE - n, "» sli = %d\n", + isr_statistics.sli); + n += scnprintf(buf + n, PAGE_SIZE - n, "*none = %d\n", + isr_statistics.none); + n += scnprintf(buf + n, PAGE_SIZE - n, "*hndl = %d\n", + isr_statistics.hndl.cnt); + + for (i = isr_statistics.hndl.idx, j = 0; j <= ISR_MASK; j++, i++) { + i &= ISR_MASK; + intr = isr_statistics.hndl.buf[i]; + + if (USBi_UI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "ui "); + intr &= ~USBi_UI; + if (USBi_UEI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "uei "); + intr &= ~USBi_UEI; + if (USBi_PCI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "pci "); + intr &= ~USBi_PCI; + if (USBi_URI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "uri "); + intr &= ~USBi_URI; + if (USBi_SLI & intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "sli "); + intr &= ~USBi_SLI; + if (intr) + n += scnprintf(buf + n, PAGE_SIZE - n, "??? "); + if (isr_statistics.hndl.buf[i]) + n += scnprintf(buf + n, PAGE_SIZE - n, "\n"); + } + + spin_unlock_irqrestore(udc->lock, flags); + + return n; +} + +/** + * store_inters: enable & force or disable an individual interrutps + * (to be used for test purposes only) + * + * Check "device.h" for details + */ +static ssize_t store_inters(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned en, bit; + + dbg_trace("[%s] %p, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%u %u", &en, &bit) != 2 || en > 1) { + dev_err(dev, "<1|0> <bit>: enable|disable interrupt"); + goto done; + } + + spin_lock_irqsave(udc->lock, flags); + if (en) { + if (hw_intr_force(bit)) + dev_err(dev, "invalid bit number\n"); + else + isr_statistics.test++; + } else { + if (hw_intr_clear(bit)) + dev_err(dev, "invalid bit number\n"); + } + spin_unlock_irqrestore(udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR(inters, S_IRUSR | S_IWUSR, show_inters, store_inters); + +/** + * show_port_test: reads port test mode + * + * Check "device.h" for details + */ +static ssize_t show_port_test(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned mode; + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + mode = hw_port_test_get(); + spin_unlock_irqrestore(udc->lock, flags); + + return scnprintf(buf, PAGE_SIZE, "mode = %u\n", mode); +} + +/** + * store_port_test: writes port test mode + * + * Check "device.h" for details + */ +static ssize_t store_port_test(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned mode; + + dbg_trace("[%s] %p, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%u", &mode) != 1) { + dev_err(dev, "<mode>: set port test mode"); + goto done; + } + + spin_lock_irqsave(udc->lock, flags); + if (hw_port_test_set(mode)) + dev_err(dev, "invalid mode\n"); + spin_unlock_irqrestore(udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR(port_test, S_IRUSR | S_IWUSR, + show_port_test, store_port_test); + +/** + * show_qheads: DMA contents of all queue heads + * + * Check "device.h" for details + */ +static ssize_t show_qheads(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + unsigned i, j, n = 0; + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + n += scnprintf(buf + n, PAGE_SIZE - n, + "EP=%02i: RX=%08X TX=%08X\n", + i, (u32)mEp->qh[RX].dma, (u32)mEp->qh[TX].dma); + for (j = 0; j < (sizeof(struct ci13xxx_qh)/sizeof(u32)); j++) { + n += scnprintf(buf + n, PAGE_SIZE - n, + " %04X: %08X %08X\n", j, + *((u32 *)mEp->qh[RX].ptr + j), + *((u32 *)mEp->qh[TX].ptr + j)); + } + } + spin_unlock_irqrestore(udc->lock, flags); + + return n; +} +static DEVICE_ATTR(qheads, S_IRUSR, show_qheads, NULL); + +/** + * show_registers: dumps all registers + * + * Check "device.h" for details + */ +static ssize_t show_registers(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + u32 dump[512]; + unsigned i, k, n = 0; + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + k = hw_register_read(dump, sizeof(dump)/sizeof(u32)); + spin_unlock_irqrestore(udc->lock, flags); + + for (i = 0; i < k; i++) { + n += scnprintf(buf + n, PAGE_SIZE - n, + "reg[0x%04X] = 0x%08X\n", + i * (unsigned)sizeof(u32), dump[i]); + } + + return n; +} + +/** + * store_registers: writes value to register address + * + * Check "device.h" for details + */ +static ssize_t store_registers(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long addr, data, flags; + + dbg_trace("[%s] %p, %d\n", __func__, buf, count); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + goto done; + } + + if (sscanf(buf, "%li %li", &addr, &data) != 2) { + dev_err(dev, "<addr> <data>: write data to register address"); + goto done; + } + + spin_lock_irqsave(udc->lock, flags); + if (hw_register_write(addr, data)) + dev_err(dev, "invalid address range\n"); + spin_unlock_irqrestore(udc->lock, flags); + + done: + return count; +} +static DEVICE_ATTR(registers, S_IRUSR | S_IWUSR, + show_registers, store_registers); + +/** + * show_requests: DMA contents of all requests currently queued (all endpts) + * + * Check "device.h" for details + */ +static ssize_t show_requests(struct device *dev, struct device_attribute *attr, + char *buf) +{ + struct ci13xxx *udc = container_of(dev, struct ci13xxx, gadget.dev); + unsigned long flags; + struct list_head *ptr = NULL; + struct ci13xxx_req *req = NULL; + unsigned i, j, k, n = 0, qSize = sizeof(struct ci13xxx_td)/sizeof(u32); + + dbg_trace("[%s] %p\n", __func__, buf); + if (attr == NULL || buf == NULL) { + dev_err(dev, "[%s] EINVAL\n", __func__); + return 0; + } + + spin_lock_irqsave(udc->lock, flags); + for (i = 0; i < hw_ep_max; i++) + for (k = RX; k <= TX; k++) + list_for_each(ptr, &udc->ci13xxx_ep[i].qh[k].queue) + { + req = list_entry(ptr, + struct ci13xxx_req, queue); + + n += scnprintf(buf + n, PAGE_SIZE - n, + "EP=%02i: TD=%08X %s\n", + i, (u32)req->dma, + ((k == RX) ? "RX" : "TX")); + + for (j = 0; j < qSize; j++) + n += scnprintf(buf + n, PAGE_SIZE - n, + " %04X: %08X\n", j, + *((u32 *)req->ptr + j)); + } + spin_unlock_irqrestore(udc->lock, flags); + + return n; +} +static DEVICE_ATTR(requests, S_IRUSR, show_requests, NULL); + +/** + * dbg_create_files: initializes the attribute interface + * @dev: device + * + * This function returns an error code + */ +__maybe_unused static int dbg_create_files(struct device *dev) +{ + int retval = 0; + + if (dev == NULL) + return -EINVAL; + retval = device_create_file(dev, &dev_attr_device); + if (retval) + goto done; + retval = device_create_file(dev, &dev_attr_driver); + if (retval) + goto rm_device; + retval = device_create_file(dev, &dev_attr_events); + if (retval) + goto rm_driver; + retval = device_create_file(dev, &dev_attr_inters); + if (retval) + goto rm_events; + retval = device_create_file(dev, &dev_attr_port_test); + if (retval) + goto rm_inters; + retval = device_create_file(dev, &dev_attr_qheads); + if (retval) + goto rm_port_test; + retval = device_create_file(dev, &dev_attr_registers); + if (retval) + goto rm_qheads; + retval = device_create_file(dev, &dev_attr_requests); + if (retval) + goto rm_registers; + return 0; + + rm_registers: + device_remove_file(dev, &dev_attr_registers); + rm_qheads: + device_remove_file(dev, &dev_attr_qheads); + rm_port_test: + device_remove_file(dev, &dev_attr_port_test); + rm_inters: + device_remove_file(dev, &dev_attr_inters); + rm_events: + device_remove_file(dev, &dev_attr_events); + rm_driver: + device_remove_file(dev, &dev_attr_driver); + rm_device: + device_remove_file(dev, &dev_attr_device); + done: + return retval; +} + +/** + * dbg_remove_files: destroys the attribute interface + * @dev: device + * + * This function returns an error code + */ +__maybe_unused static int dbg_remove_files(struct device *dev) +{ + if (dev == NULL) + return -EINVAL; + device_remove_file(dev, &dev_attr_requests); + device_remove_file(dev, &dev_attr_registers); + device_remove_file(dev, &dev_attr_qheads); + device_remove_file(dev, &dev_attr_port_test); + device_remove_file(dev, &dev_attr_inters); + device_remove_file(dev, &dev_attr_events); + device_remove_file(dev, &dev_attr_driver); + device_remove_file(dev, &dev_attr_device); + return 0; +} + +/****************************************************************************** + * UTIL block + *****************************************************************************/ +/** + * _usb_addr: calculates endpoint address from direction & number + * @ep: endpoint + */ +static inline u8 _usb_addr(struct ci13xxx_ep *ep) +{ + return ((ep->dir == TX) ? USB_ENDPOINT_DIR_MASK : 0) | ep->num; +} + +/** + * _hardware_queue: configures a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_enqueue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + unsigned i; + + trace("%p, %p", mEp, mReq); + + /* don't queue twice */ + if (mReq->req.status == -EALREADY) + return -EALREADY; + + if (hw_ep_is_primed(mEp->num, mEp->dir)) + return -EBUSY; + + mReq->req.status = -EALREADY; + + if (mReq->req.length && !mReq->req.dma) { + mReq->req.dma = \ + dma_map_single(mEp->device, mReq->req.buf, + mReq->req.length, mEp->dir ? + DMA_TO_DEVICE : DMA_FROM_DEVICE); + if (mReq->req.dma == 0) + return -ENOMEM; + + mReq->map = 1; + } + + /* + * TD configuration + * TODO - handle requests which spawns into several TDs + */ + memset(mReq->ptr, 0, sizeof(*mReq->ptr)); + mReq->ptr->next |= TD_TERMINATE; + mReq->ptr->token = mReq->req.length << ffs_nr(TD_TOTAL_BYTES); + mReq->ptr->token &= TD_TOTAL_BYTES; + mReq->ptr->token |= TD_IOC; + mReq->ptr->token |= TD_STATUS_ACTIVE; + mReq->ptr->page[0] = mReq->req.dma; + for (i = 1; i < 5; i++) + mReq->ptr->page[i] = + (mReq->req.dma + i * PAGE_SIZE) & ~TD_RESERVED_MASK; + + /* + * QH configuration + * At this point it's guaranteed exclusive access to qhead + * (endpt is not primed) so it's no need to use tripwire + */ + mEp->qh[mEp->dir].ptr->td.next = mReq->dma; /* TERMINATE = 0 */ + mEp->qh[mEp->dir].ptr->td.token &= ~TD_STATUS; /* clear status */ + if (mReq->req.zero == 0) + mEp->qh[mEp->dir].ptr->cap |= QH_ZLT; + else + mEp->qh[mEp->dir].ptr->cap &= ~QH_ZLT; + + wmb(); /* synchronize before ep prime */ + + return hw_ep_prime(mEp->num, mEp->dir, + mEp->type == USB_ENDPOINT_XFER_CONTROL); +} + +/** + * _hardware_dequeue: handles a request at hardware level + * @gadget: gadget + * @mEp: endpoint + * + * This function returns an error code + */ +static int _hardware_dequeue(struct ci13xxx_ep *mEp, struct ci13xxx_req *mReq) +{ + trace("%p, %p", mEp, mReq); + + if (mReq->req.status != -EALREADY) + return -EINVAL; + + if (hw_ep_is_primed(mEp->num, mEp->dir)) + hw_ep_flush(mEp->num, mEp->dir); + + mReq->req.status = 0; + + if (mReq->map) { + dma_unmap_single(mEp->device, mReq->req.dma, mReq->req.length, + mEp->dir ? DMA_TO_DEVICE : DMA_FROM_DEVICE); + mReq->req.dma = 0; + mReq->map = 0; + } + + mReq->req.status = mReq->ptr->token & TD_STATUS; + if ((TD_STATUS_ACTIVE & mReq->req.status) != 0) + mReq->req.status = -ECONNRESET; + else if ((TD_STATUS_HALTED & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_DT_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + else if ((TD_STATUS_TR_ERR & mReq->req.status) != 0) + mReq->req.status = -1; + + mReq->req.actual = mReq->ptr->token & TD_TOTAL_BYTES; + mReq->req.actual >>= ffs_nr(TD_TOTAL_BYTES); + mReq->req.actual = mReq->req.length - mReq->req.actual; + mReq->req.actual = mReq->req.status ? 0 : mReq->req.actual; + + return mReq->req.actual; +} + +/** + * _ep_nuke: dequeues all endpoint requests + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int _ep_nuke(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + trace("%p", mEp); + + if (mEp == NULL) + return -EINVAL; + + hw_ep_flush(mEp->num, mEp->dir); + + while (!list_empty(&mEp->qh[mEp->dir].queue)) { + + /* pop oldest request */ + struct ci13xxx_req *mReq = \ + list_entry(mEp->qh[mEp->dir].queue.next, + struct ci13xxx_req, queue); + list_del_init(&mReq->queue); + mReq->req.status = -ESHUTDOWN; + + if (!mReq->req.no_interrupt && mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + mReq->req.complete(&mEp->ep, &mReq->req); + spin_lock(mEp->lock); + } + } + return 0; +} + +/** + * _gadget_stop_activity: stops all USB activity, flushes & disables all endpts + * @gadget: gadget + * + * This function returns an error code + * Caller must hold lock + */ +static int _gadget_stop_activity(struct usb_gadget *gadget) +__releases(udc->lock) +__acquires(udc->lock) +{ + struct usb_ep *ep; + struct ci13xxx *udc = container_of(gadget, struct ci13xxx, gadget); + struct ci13xxx_ep *mEp = container_of(gadget->ep0, + struct ci13xxx_ep, ep); + + trace("%p", gadget); + + if (gadget == NULL) + return -EINVAL; + + spin_unlock(udc->lock); + + /* flush all endpoints */ + gadget_for_each_ep(ep, gadget) { + usb_ep_fifo_flush(ep); + } + usb_ep_fifo_flush(gadget->ep0); + + udc->driver->disconnect(gadget); + + /* make sure to disable all endpoints */ + gadget_for_each_ep(ep, gadget) { + usb_ep_disable(ep); + } + usb_ep_disable(gadget->ep0); + + if (mEp->status != NULL) { + usb_ep_free_request(gadget->ep0, mEp->status); + mEp->status = NULL; + } + + spin_lock(udc->lock); + + return 0; +} + +/****************************************************************************** + * ISR block + *****************************************************************************/ +/** + * isr_reset_handler: USB reset interrupt handler + * @udc: UDC device + * + * This function resets USB engine after a bus reset occurred + */ +static void isr_reset_handler(struct ci13xxx *udc) +__releases(udc->lock) +__acquires(udc->lock) +{ + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[0]; + int retval; + + trace("%p", udc); + + if (udc == NULL) { + err("EINVAL"); + return; + } + + dbg_event(0xFF, "BUS RST", 0); + + retval = _gadget_stop_activity(&udc->gadget); + if (retval) + goto done; + + retval = hw_usb_reset(); + if (retval) + goto done; + + spin_unlock(udc->lock); + retval = usb_ep_enable(&mEp->ep, &ctrl_endpt_desc); + if (!retval) { + mEp->status = usb_ep_alloc_request(&mEp->ep, GFP_KERNEL); + if (mEp->status == NULL) { + usb_ep_disable(&mEp->ep); + retval = -ENOMEM; + } + } + spin_lock(udc->lock); + + done: + if (retval) + err("error: %i", retval); +} + +/** + * isr_get_status_complete: get_status request complete function + * @ep: endpoint + * @req: request handled + * + * Caller must release lock + */ +static void isr_get_status_complete(struct usb_ep *ep, struct usb_request *req) +{ + trace("%p, %p", ep, req); + + if (ep == NULL || req == NULL) { + err("EINVAL"); + return; + } + + kfree(req->buf); + usb_ep_free_request(ep, req); +} + +/** + * isr_get_status_response: get_status request response + * @ep: endpoint + * @setup: setup request packet + * + * This function returns an error code + */ +static int isr_get_status_response(struct ci13xxx_ep *mEp, + struct usb_ctrlrequest *setup) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct usb_request *req = NULL; + gfp_t gfp_flags = GFP_ATOMIC; + int dir, num, retval; + + trace("%p, %p", mEp, setup); + + if (mEp == NULL || setup == NULL) + return -EINVAL; + + spin_unlock(mEp->lock); + req = usb_ep_alloc_request(&mEp->ep, gfp_flags); + spin_lock(mEp->lock); + if (req == NULL) + return -ENOMEM; + + req->complete = isr_get_status_complete; + req->length = 2; + req->buf = kzalloc(req->length, gfp_flags); + if (req->buf == NULL) { + retval = -ENOMEM; + goto err_free_req; + } + + if ((setup->bRequestType & USB_RECIP_MASK) == USB_RECIP_DEVICE) { + /* TODO: D1 - Remote Wakeup; D0 - Self Powered */ + retval = 0; + } else if ((setup->bRequestType & USB_RECIP_MASK) \ + == USB_RECIP_ENDPOINT) { + dir = (le16_to_cpu(setup->wIndex) & USB_ENDPOINT_DIR_MASK) ? + TX : RX; + num = le16_to_cpu(setup->wIndex) & USB_ENDPOINT_NUMBER_MASK; + *((u16 *)req->buf) = hw_ep_get_halt(num, dir); + } + /* else do nothing; reserved for future use */ + + spin_unlock(mEp->lock); + retval = usb_ep_queue(&mEp->ep, req, gfp_flags); + spin_lock(mEp->lock); + if (retval) + goto err_free_buf; + + return 0; + + err_free_buf: + kfree(req->buf); + err_free_req: + spin_unlock(mEp->lock); + usb_ep_free_request(&mEp->ep, req); + spin_lock(mEp->lock); + return retval; +} + +/** + * isr_setup_status_phase: queues the status phase of a setup transation + * @mEp: endpoint + * + * This function returns an error code + */ +static int isr_setup_status_phase(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + int retval; + + trace("%p", mEp); + + /* mEp is always valid & configured */ + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + mEp->status->no_interrupt = 1; + + spin_unlock(mEp->lock); + retval = usb_ep_queue(&mEp->ep, mEp->status, GFP_ATOMIC); + spin_lock(mEp->lock); + + return retval; +} + +/** + * isr_tr_complete_low: transaction complete low level handler + * @mEp: endpoint + * + * This function returns an error code + * Caller must hold lock + */ +static int isr_tr_complete_low(struct ci13xxx_ep *mEp) +__releases(mEp->lock) +__acquires(mEp->lock) +{ + struct ci13xxx_req *mReq; + int retval; + + trace("%p", mEp); + + if (list_empty(&mEp->qh[mEp->dir].queue)) + return -EINVAL; + + /* pop oldest request */ + mReq = list_entry(mEp->qh[mEp->dir].queue.next, + struct ci13xxx_req, queue); + list_del_init(&mReq->queue); + + retval = _hardware_dequeue(mEp, mReq); + if (retval < 0) { + dbg_event(_usb_addr(mEp), "DONE", retval); + goto done; + } + + dbg_done(_usb_addr(mEp), mReq->ptr->token, retval); + + if (!mReq->req.no_interrupt && mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + mReq->req.complete(&mEp->ep, &mReq->req); + spin_lock(mEp->lock); + } + + if (!list_empty(&mEp->qh[mEp->dir].queue)) { + mReq = list_entry(mEp->qh[mEp->dir].queue.next, + struct ci13xxx_req, queue); + _hardware_enqueue(mEp, mReq); + } + + done: + return retval; +} + +/** + * isr_tr_complete_handler: transaction complete interrupt handler + * @udc: UDC descriptor + * + * This function handles traffic events + */ +static void isr_tr_complete_handler(struct ci13xxx *udc) +__releases(udc->lock) +__acquires(udc->lock) +{ + unsigned i; + + trace("%p", udc); + + if (udc == NULL) { + err("EINVAL"); + return; + } + + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + int type, num, err = -EINVAL; + struct usb_ctrlrequest req; + + + if (mEp->desc == NULL) + continue; /* not configured */ + + if ((mEp->dir == RX && hw_test_and_clear_complete(i)) || + (mEp->dir == TX && hw_test_and_clear_complete(i + 16))) { + err = isr_tr_complete_low(mEp); + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) { + if (err > 0) /* needs status phase */ + err = isr_setup_status_phase(mEp); + if (err < 0) { + dbg_event(_usb_addr(mEp), + "ERROR", err); + spin_unlock(udc->lock); + if (usb_ep_set_halt(&mEp->ep)) + err("error: ep_set_halt"); + spin_lock(udc->lock); + } + } + } + + if (mEp->type != USB_ENDPOINT_XFER_CONTROL || + !hw_test_and_clear_setup_status(i)) + continue; + + if (i != 0) { + warn("ctrl traffic received at endpoint"); + continue; + } + + /* read_setup_packet */ + do { + hw_test_and_set_setup_guard(); + memcpy(&req, &mEp->qh[RX].ptr->setup, sizeof(req)); + } while (!hw_test_and_clear_setup_guard()); + + type = req.bRequestType; + + mEp->dir = (type & USB_DIR_IN) ? TX : RX; + + dbg_setup(_usb_addr(mEp), &req); + + switch (req.bRequest) { + case USB_REQ_CLEAR_FEATURE: + if (type != (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) != USB_ENDPOINT_HALT) + goto delegate; + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + num &= USB_ENDPOINT_NUMBER_MASK; + if (!udc->ci13xxx_ep[num].wedge) { + spin_unlock(udc->lock); + err = usb_ep_clear_halt( + &udc->ci13xxx_ep[num].ep); + spin_lock(udc->lock); + if (err) + break; + } + err = isr_setup_status_phase(mEp); + break; + case USB_REQ_GET_STATUS: + if (type != (USB_DIR_IN|USB_RECIP_DEVICE) && + type != (USB_DIR_IN|USB_RECIP_ENDPOINT) && + type != (USB_DIR_IN|USB_RECIP_INTERFACE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 2 || + le16_to_cpu(req.wValue) != 0) + break; + err = isr_get_status_response(mEp, &req); + break; + case USB_REQ_SET_ADDRESS: + if (type != (USB_DIR_OUT|USB_RECIP_DEVICE)) + goto delegate; + if (le16_to_cpu(req.wLength) != 0 || + le16_to_cpu(req.wIndex) != 0) + break; + err = hw_usb_set_address((u8)le16_to_cpu(req.wValue)); + if (err) + break; + err = isr_setup_status_phase(mEp); + break; + case USB_REQ_SET_FEATURE: + if (type != (USB_DIR_OUT|USB_RECIP_ENDPOINT) && + le16_to_cpu(req.wValue) != USB_ENDPOINT_HALT) + goto delegate; + if (req.wLength != 0) + break; + num = le16_to_cpu(req.wIndex); + num &= USB_ENDPOINT_NUMBER_MASK; + + spin_unlock(udc->lock); + err = usb_ep_set_halt(&udc->ci13xxx_ep[num].ep); + spin_lock(udc->lock); + if (err) + break; + err = isr_setup_status_phase(mEp); + break; + default: +delegate: + if (req.wLength == 0) /* no data phase */ + mEp->dir = TX; + + spin_unlock(udc->lock); + err = udc->driver->setup(&udc->gadget, &req); + spin_lock(udc->lock); + break; + } + + if (err < 0) { + dbg_event(_usb_addr(mEp), "ERROR", err); + + spin_unlock(udc->lock); + if (usb_ep_set_halt(&mEp->ep)) + err("error: ep_set_halt"); + spin_lock(udc->lock); + } + } +} + +/****************************************************************************** + * ENDPT block + *****************************************************************************/ +/** + * ep_enable: configure endpoint, making it usable + * + * Check usb_ep_enable() at "usb_gadget.h" for details + */ +static int ep_enable(struct usb_ep *ep, + const struct usb_endpoint_descriptor *desc) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + trace("%p, %p", ep, desc); + + if (ep == NULL || desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should enable ctrl endpts */ + + mEp->desc = desc; + + if (!list_empty(&mEp->qh[mEp->dir].queue)) + warn("enabling a non-empty endpoint!"); + + mEp->dir = (desc->bEndpointAddress & USB_ENDPOINT_DIR_MASK) ? TX : RX; + mEp->num = desc->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + mEp->type = desc->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + mEp->ep.maxpacket = __constant_le16_to_cpu(desc->wMaxPacketSize); + + direction = mEp->dir; + do { + dbg_event(_usb_addr(mEp), "ENABLE", 0); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->qh[mEp->dir].ptr->cap |= QH_IOS; + else if (mEp->type == USB_ENDPOINT_XFER_ISOC) + mEp->qh[mEp->dir].ptr->cap &= ~QH_MULT; + else + mEp->qh[mEp->dir].ptr->cap &= ~QH_ZLT; + + mEp->qh[mEp->dir].ptr->cap |= + (mEp->ep.maxpacket << ffs_nr(QH_MAX_PKT)) & QH_MAX_PKT; + mEp->qh[mEp->dir].ptr->td.next |= TD_TERMINATE; /* needed? */ + + retval |= hw_ep_enable(mEp->num, mEp->dir, mEp->type); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_disable: endpoint is no longer usable + * + * Check usb_ep_disable() at "usb_gadget.h" for details + */ +static int ep_disable(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + trace("%p", ep); + + if (ep == NULL) + return -EINVAL; + else if (mEp->desc == NULL) + return -EBUSY; + + spin_lock_irqsave(mEp->lock, flags); + + /* only internal SW should disable ctrl endpts */ + + direction = mEp->dir; + do { + dbg_event(_usb_addr(mEp), "DISABLE", 0); + + retval |= _ep_nuke(mEp); + retval |= hw_ep_disable(mEp->num, mEp->dir); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + mEp->desc = NULL; + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_alloc_request: allocate a request object to use with this endpoint + * + * Check usb_ep_alloc_request() at "usb_gadget.h" for details + */ +static struct usb_request *ep_alloc_request(struct usb_ep *ep, gfp_t gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = NULL; + unsigned long flags; + + trace("%p, %i", ep, gfp_flags); + + if (ep == NULL) { + err("EINVAL"); + return NULL; + } + + spin_lock_irqsave(mEp->lock, flags); + + mReq = kzalloc(sizeof(struct ci13xxx_req), gfp_flags); + if (mReq != NULL) { + INIT_LIST_HEAD(&mReq->queue); + + mReq->ptr = dma_pool_alloc(mEp->td_pool, gfp_flags, + &mReq->dma); + if (mReq->ptr == NULL) { + kfree(mReq); + mReq = NULL; + } + } + + dbg_event(_usb_addr(mEp), "ALLOC", mReq == NULL); + + spin_unlock_irqrestore(mEp->lock, flags); + + return (mReq == NULL) ? NULL : &mReq->req; +} + +/** + * ep_free_request: frees a request object + * + * Check usb_ep_free_request() at "usb_gadget.h" for details + */ +static void ep_free_request(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + unsigned long flags; + + trace("%p, %p", ep, req); + + if (ep == NULL || req == NULL) { + err("EINVAL"); + return; + } else if (!list_empty(&mReq->queue)) { + err("EBUSY"); + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + if (mReq->ptr) + dma_pool_free(mEp->td_pool, mReq->ptr, mReq->dma); + kfree(mReq); + + dbg_event(_usb_addr(mEp), "FREE", 0); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * ep_queue: queues (submits) an I/O request to an endpoint + * + * Check usb_ep_queue()* at usb_gadget.h" for details + */ +static int ep_queue(struct usb_ep *ep, struct usb_request *req, + gfp_t __maybe_unused gfp_flags) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + int retval = 0; + unsigned long flags; + + trace("%p, %p, %X", ep, req, gfp_flags); + + if (ep == NULL || req == NULL || mEp->desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL && + !list_empty(&mEp->qh[mEp->dir].queue)) { + _ep_nuke(mEp); + retval = -EOVERFLOW; + warn("endpoint ctrl %X nuked", _usb_addr(mEp)); + } + + /* first nuke then test link, e.g. previous status has not sent */ + if (!list_empty(&mReq->queue)) { + retval = -EBUSY; + err("request already in queue"); + goto done; + } + + if (req->length > (4 * PAGE_SIZE)) { + req->length = (4 * PAGE_SIZE); + retval = -EMSGSIZE; + warn("request length truncated"); + } + + dbg_queue(_usb_addr(mEp), req, retval); + + /* push request */ + mReq->req.status = -EINPROGRESS; + mReq->req.actual = 0; + list_add_tail(&mReq->queue, &mEp->qh[mEp->dir].queue); + + retval = _hardware_enqueue(mEp, mReq); + if (retval == -EALREADY || retval == -EBUSY) { + dbg_event(_usb_addr(mEp), "QUEUE", retval); + retval = 0; + } + + done: + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_dequeue: dequeues (cancels, unlinks) an I/O request from an endpoint + * + * Check usb_ep_dequeue() at "usb_gadget.h" for details + */ +static int ep_dequeue(struct usb_ep *ep, struct usb_request *req) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + struct ci13xxx_req *mReq = container_of(req, struct ci13xxx_req, req); + unsigned long flags; + + trace("%p, %p", ep, req); + + if (ep == NULL || req == NULL || mEp->desc == NULL || + list_empty(&mReq->queue) || list_empty(&mEp->qh[mEp->dir].queue)) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "DEQUEUE", 0); + + if (mReq->req.status == -EALREADY) + _hardware_dequeue(mEp, mReq); + + /* pop request */ + list_del_init(&mReq->queue); + req->status = -ECONNRESET; + + if (!mReq->req.no_interrupt && mReq->req.complete != NULL) { + spin_unlock(mEp->lock); + mReq->req.complete(&mEp->ep, &mReq->req); + spin_lock(mEp->lock); + } + + spin_unlock_irqrestore(mEp->lock, flags); + return 0; +} + +/** + * ep_set_halt: sets the endpoint halt feature + * + * Check usb_ep_set_halt() at "usb_gadget.h" for details + */ +static int ep_set_halt(struct usb_ep *ep, int value) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + int direction, retval = 0; + unsigned long flags; + + trace("%p, %i", ep, value); + + if (ep == NULL || mEp->desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + +#ifndef STALL_IN + /* g_file_storage MS compliant but g_zero fails chapter 9 compliance */ + if (value && mEp->type == USB_ENDPOINT_XFER_BULK && mEp->dir == TX && + !list_empty(&mEp->qh[mEp->dir].queue)) { + spin_unlock_irqrestore(mEp->lock, flags); + return -EAGAIN; + } +#endif + + direction = mEp->dir; + do { + dbg_event(_usb_addr(mEp), "HALT", value); + retval |= hw_ep_set_halt(mEp->num, mEp->dir, value); + + if (!value) + mEp->wedge = 0; + + if (mEp->type == USB_ENDPOINT_XFER_CONTROL) + mEp->dir = (mEp->dir == TX) ? RX : TX; + + } while (mEp->dir != direction); + + spin_unlock_irqrestore(mEp->lock, flags); + return retval; +} + +/** + * ep_set_wedge: sets the halt feature and ignores clear requests + * + * Check usb_ep_set_wedge() at "usb_gadget.h" for details + */ +static int ep_set_wedge(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + trace("%p", ep); + + if (ep == NULL || mEp->desc == NULL) + return -EINVAL; + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "WEDGE", 0); + mEp->wedge = 1; + + spin_unlock_irqrestore(mEp->lock, flags); + + return usb_ep_set_halt(ep); +} + +/** + * ep_fifo_flush: flushes contents of a fifo + * + * Check usb_ep_fifo_flush() at "usb_gadget.h" for details + */ +static void ep_fifo_flush(struct usb_ep *ep) +{ + struct ci13xxx_ep *mEp = container_of(ep, struct ci13xxx_ep, ep); + unsigned long flags; + + trace("%p", ep); + + if (ep == NULL) { + err("%02X: -EINVAL", _usb_addr(mEp)); + return; + } + + spin_lock_irqsave(mEp->lock, flags); + + dbg_event(_usb_addr(mEp), "FFLUSH", 0); + hw_ep_flush(mEp->num, mEp->dir); + + spin_unlock_irqrestore(mEp->lock, flags); +} + +/** + * Endpoint-specific part of the API to the USB controller hardware + * Check "usb_gadget.h" for details + */ +static const struct usb_ep_ops usb_ep_ops = { + .enable = ep_enable, + .disable = ep_disable, + .alloc_request = ep_alloc_request, + .free_request = ep_free_request, + .queue = ep_queue, + .dequeue = ep_dequeue, + .set_halt = ep_set_halt, + .set_wedge = ep_set_wedge, + .fifo_flush = ep_fifo_flush, +}; + +/****************************************************************************** + * GADGET block + *****************************************************************************/ +/** + * Device operations part of the API to the USB controller hardware, + * which don't involve endpoints (or i/o) + * Check "usb_gadget.h" for details + */ +static const struct usb_gadget_ops usb_gadget_ops; + +/** + * usb_gadget_register_driver: register a gadget driver + * + * Check usb_gadget_register_driver() at "usb_gadget.h" for details + * Interrupts are enabled here + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct ci13xxx *udc = _udc; + unsigned long i, k, flags; + int retval = -ENOMEM; + + trace("%p", driver); + + if (driver == NULL || + driver->bind == NULL || + driver->unbind == NULL || + driver->setup == NULL || + driver->disconnect == NULL || + driver->suspend == NULL || + driver->resume == NULL) + return -EINVAL; + else if (udc == NULL) + return -ENODEV; + else if (udc->driver != NULL) + return -EBUSY; + + /* alloc resources */ + udc->qh_pool = dma_pool_create("ci13xxx_qh", &udc->gadget.dev, + sizeof(struct ci13xxx_qh), + 64, PAGE_SIZE); + if (udc->qh_pool == NULL) + return -ENOMEM; + + udc->td_pool = dma_pool_create("ci13xxx_td", &udc->gadget.dev, + sizeof(struct ci13xxx_td), + 64, PAGE_SIZE); + if (udc->td_pool == NULL) { + dma_pool_destroy(udc->qh_pool); + udc->qh_pool = NULL; + return -ENOMEM; + } + + spin_lock_irqsave(udc->lock, flags); + + info("hw_ep_max = %d", hw_ep_max); + + udc->driver = driver; + udc->gadget.ops = NULL; + udc->gadget.dev.driver = NULL; + + retval = 0; + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + + scnprintf(mEp->name, sizeof(mEp->name), "ep%i", (int)i); + + mEp->lock = udc->lock; + mEp->device = &udc->gadget.dev; + mEp->td_pool = udc->td_pool; + + mEp->ep.name = mEp->name; + mEp->ep.ops = &usb_ep_ops; + mEp->ep.maxpacket = CTRL_PAYLOAD_MAX; + + /* this allocation cannot be random */ + for (k = RX; k <= TX; k++) { + INIT_LIST_HEAD(&mEp->qh[k].queue); + mEp->qh[k].ptr = dma_pool_alloc(udc->qh_pool, + GFP_KERNEL, + &mEp->qh[k].dma); + if (mEp->qh[k].ptr == NULL) + retval = -ENOMEM; + else + memset(mEp->qh[k].ptr, 0, + sizeof(*mEp->qh[k].ptr)); + } + if (i == 0) + udc->gadget.ep0 = &mEp->ep; + else + list_add_tail(&mEp->ep.ep_list, &udc->gadget.ep_list); + } + if (retval) + goto done; + + /* bind gadget */ + driver->driver.bus = NULL; + udc->gadget.ops = &usb_gadget_ops; + udc->gadget.dev.driver = &driver->driver; + + spin_unlock_irqrestore(udc->lock, flags); + retval = driver->bind(&udc->gadget); /* MAY SLEEP */ + spin_lock_irqsave(udc->lock, flags); + + if (retval) { + udc->gadget.ops = NULL; + udc->gadget.dev.driver = NULL; + goto done; + } + + retval = hw_device_state(udc->ci13xxx_ep[0].qh[RX].dma); + + done: + spin_unlock_irqrestore(udc->lock, flags); + if (retval) + usb_gadget_unregister_driver(driver); + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +/** + * usb_gadget_unregister_driver: unregister a gadget driver + * + * Check usb_gadget_unregister_driver() at "usb_gadget.h" for details + */ +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct ci13xxx *udc = _udc; + unsigned long i, k, flags; + + trace("%p", driver); + + if (driver == NULL || + driver->bind == NULL || + driver->unbind == NULL || + driver->setup == NULL || + driver->disconnect == NULL || + driver->suspend == NULL || + driver->resume == NULL || + driver != udc->driver) + return -EINVAL; + + spin_lock_irqsave(udc->lock, flags); + + hw_device_state(0); + + /* unbind gadget */ + if (udc->gadget.ops != NULL) { + _gadget_stop_activity(&udc->gadget); + + spin_unlock_irqrestore(udc->lock, flags); + driver->unbind(&udc->gadget); /* MAY SLEEP */ + spin_lock_irqsave(udc->lock, flags); + + udc->gadget.ops = NULL; + udc->gadget.dev.driver = NULL; + } + + /* free resources */ + for (i = 0; i < hw_ep_max; i++) { + struct ci13xxx_ep *mEp = &udc->ci13xxx_ep[i]; + + if (i == 0) + udc->gadget.ep0 = NULL; + else if (!list_empty(&mEp->ep.ep_list)) + list_del_init(&mEp->ep.ep_list); + + for (k = RX; k <= TX; k++) + if (mEp->qh[k].ptr != NULL) + dma_pool_free(udc->qh_pool, + mEp->qh[k].ptr, mEp->qh[k].dma); + } + + udc->driver = NULL; + + spin_unlock_irqrestore(udc->lock, flags); + + if (udc->td_pool != NULL) { + dma_pool_destroy(udc->td_pool); + udc->td_pool = NULL; + } + if (udc->qh_pool != NULL) { + dma_pool_destroy(udc->qh_pool); + udc->qh_pool = NULL; + } + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/****************************************************************************** + * BUS block + *****************************************************************************/ +/** + * udc_irq: global interrupt handler + * + * This function returns IRQ_HANDLED if the IRQ has been handled + * It locks access to registers + */ +static irqreturn_t udc_irq(void) +{ + struct ci13xxx *udc = _udc; + irqreturn_t retval; + u32 intr; + + trace(); + + if (udc == NULL) { + err("ENODEV"); + return IRQ_HANDLED; + } + + spin_lock(udc->lock); + intr = hw_test_and_clear_intr_active(); + if (intr) { + isr_statistics.hndl.buf[isr_statistics.hndl.idx++] = intr; + isr_statistics.hndl.idx &= ISR_MASK; + isr_statistics.hndl.cnt++; + + /* order defines priority - do NOT change it */ + if (USBi_URI & intr) { + isr_statistics.uri++; + isr_reset_handler(udc); + } + if (USBi_PCI & intr) { + isr_statistics.pci++; + udc->gadget.speed = hw_port_is_high_speed() ? + USB_SPEED_HIGH : USB_SPEED_FULL; + } + if (USBi_UEI & intr) + isr_statistics.uei++; + if (USBi_UI & intr) { + isr_statistics.ui++; + isr_tr_complete_handler(udc); + } + if (USBi_SLI & intr) + isr_statistics.sli++; + retval = IRQ_HANDLED; + } else { + isr_statistics.none++; + retval = IRQ_NONE; + } + spin_unlock(udc->lock); + + return retval; +} + +/** + * udc_release: driver release function + * @dev: device + * + * Currently does nothing + */ +static void udc_release(struct device *dev) +{ + trace("%p", dev); + + if (dev == NULL) + err("EINVAL"); +} + +/** + * udc_probe: parent probe must call this to initialize UDC + * @dev: parent device + * @regs: registers base address + * @name: driver name + * + * This function returns an error code + * No interrupts active, the IRQ has not been requested yet + * Kernel assumes 32-bit DMA operations by default, no need to dma_set_mask + */ +static int udc_probe(struct device *dev, void __iomem *regs, const char *name) +{ + struct ci13xxx *udc; + int retval = 0; + + trace("%p, %p, %p", dev, regs, name); + + if (dev == NULL || regs == NULL || name == NULL) + return -EINVAL; + + udc = kzalloc(sizeof(struct ci13xxx), GFP_KERNEL); + if (udc == NULL) + return -ENOMEM; + + udc->lock = &udc_lock; + + retval = hw_device_reset(regs); + if (retval) + goto done; + + udc->gadget.ops = NULL; + udc->gadget.speed = USB_SPEED_UNKNOWN; + udc->gadget.is_dualspeed = 1; + udc->gadget.is_otg = 0; + udc->gadget.name = name; + + INIT_LIST_HEAD(&udc->gadget.ep_list); + udc->gadget.ep0 = NULL; + + strcpy(udc->gadget.dev.bus_id, "gadget"); + udc->gadget.dev.dma_mask = dev->dma_mask; + udc->gadget.dev.parent = dev; + udc->gadget.dev.release = udc_release; + + retval = device_register(&udc->gadget.dev); + if (retval) + goto done; + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + retval = dbg_create_files(&udc->gadget.dev); +#endif + if (retval) { + device_unregister(&udc->gadget.dev); + goto done; + } + + _udc = udc; + return retval; + + done: + err("error = %i", retval); + kfree(udc); + _udc = NULL; + return retval; +} + +/** + * udc_remove: parent remove must call this to remove UDC + * + * No interrupts active, the IRQ has been released + */ +static void udc_remove(void) +{ + struct ci13xxx *udc = _udc; + + if (udc == NULL) { + err("EINVAL"); + return; + } + +#ifdef CONFIG_USB_GADGET_DEBUG_FILES + dbg_remove_files(&udc->gadget.dev); +#endif + device_unregister(&udc->gadget.dev); + + kfree(udc); + _udc = NULL; +} + +/****************************************************************************** + * PCI block + *****************************************************************************/ +/** + * ci13xxx_pci_irq: interrut handler + * @irq: irq number + * @pdev: USB Device Controller interrupt source + * + * This function returns IRQ_HANDLED if the IRQ has been handled + * This is an ISR don't trace, use attribute interface instead + */ +static irqreturn_t ci13xxx_pci_irq(int irq, void *pdev) +{ + if (irq == 0) { + dev_err(&((struct pci_dev *)pdev)->dev, "Invalid IRQ0 usage!"); + return IRQ_HANDLED; + } + return udc_irq(); +} + +/** + * ci13xxx_pci_probe: PCI probe + * @pdev: USB device controller being probed + * @id: PCI hotplug ID connecting controller to UDC framework + * + * This function returns an error code + * Allocates basic PCI resources for this USB device controller, and then + * invokes the udc_probe() method to start the UDC associated with it + */ +static int __devinit ci13xxx_pci_probe(struct pci_dev *pdev, + const struct pci_device_id *id) +{ + void __iomem *regs = NULL; + int retval = 0; + + if (id == NULL) + return -EINVAL; + + retval = pci_enable_device(pdev); + if (retval) + goto done; + + if (!pdev->irq) { + dev_err(&pdev->dev, "No IRQ, check BIOS/PCI setup!"); + retval = -ENODEV; + goto disable_device; + } + + retval = pci_request_regions(pdev, UDC_DRIVER_NAME); + if (retval) + goto disable_device; + + /* BAR 0 holds all the registers */ + regs = pci_iomap(pdev, 0, 0); + if (!regs) { + dev_err(&pdev->dev, "Error mapping memory!"); + retval = -EFAULT; + goto release_regions; + } + pci_set_drvdata(pdev, (__force void *)regs); + + pci_set_master(pdev); + pci_try_set_mwi(pdev); + + retval = udc_probe(&pdev->dev, regs, UDC_DRIVER_NAME); + if (retval) + goto iounmap; + + /* our device does not have MSI capability */ + + retval = request_irq(pdev->irq, ci13xxx_pci_irq, IRQF_SHARED, + UDC_DRIVER_NAME, pdev); + if (retval) + goto gadget_remove; + + return 0; + + gadget_remove: + udc_remove(); + iounmap: + pci_iounmap(pdev, regs); + release_regions: + pci_release_regions(pdev); + disable_device: + pci_disable_device(pdev); + done: + return retval; +} + +/** + * ci13xxx_pci_remove: PCI remove + * @pdev: USB Device Controller being removed + * + * Reverses the effect of ci13xxx_pci_probe(), + * first invoking the udc_remove() and then releases + * all PCI resources allocated for this USB device controller + */ +static void __devexit ci13xxx_pci_remove(struct pci_dev *pdev) +{ + free_irq(pdev->irq, pdev); + udc_remove(); + pci_iounmap(pdev, (__force void __iomem *)pci_get_drvdata(pdev)); + pci_release_regions(pdev); + pci_disable_device(pdev); +} + +/** + * PCI device table + * PCI device structure + * + * Check "pci.h" for details + */ +static DEFINE_PCI_DEVICE_TABLE(ci13xxx_pci_id_table) = { + { PCI_DEVICE(0x153F, 0x1004) }, + { PCI_DEVICE(0x153F, 0x1006) }, + { 0, 0, 0, 0, 0, 0, 0 /* end: all zeroes */ } +}; +MODULE_DEVICE_TABLE(pci, ci13xxx_pci_id_table); + +static struct pci_driver ci13xxx_pci_driver = { + .name = UDC_DRIVER_NAME, + .id_table = ci13xxx_pci_id_table, + .probe = ci13xxx_pci_probe, + .remove = __devexit_p(ci13xxx_pci_remove), +}; + +/** + * ci13xxx_pci_init: module init + * + * Driver load + */ +static int __init ci13xxx_pci_init(void) +{ + return pci_register_driver(&ci13xxx_pci_driver); +} +module_init(ci13xxx_pci_init); + +/** + * ci13xxx_pci_exit: module exit + * + * Driver unload + */ +static void __exit ci13xxx_pci_exit(void) +{ + pci_unregister_driver(&ci13xxx_pci_driver); +} +module_exit(ci13xxx_pci_exit); + +MODULE_AUTHOR("MIPS - David Lopo <dlopo@chipidea.mips.com>"); +MODULE_DESCRIPTION("MIPS CI13XXX USB Peripheral Controller"); +MODULE_LICENSE("GPL"); +MODULE_VERSION("June 2008"); diff --git a/drivers/usb/gadget/ci13xxx_udc.h b/drivers/usb/gadget/ci13xxx_udc.h new file mode 100644 index 000000000000..4026e9cede34 --- /dev/null +++ b/drivers/usb/gadget/ci13xxx_udc.h @@ -0,0 +1,195 @@ +/* + * ci13xxx_udc.h - structures, registers, and macros MIPS USB IP core + * + * Copyright (C) 2008 Chipidea - MIPS Technologies, Inc. All rights reserved. + * + * Author: David Lopo + * + * 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. + * + * Description: MIPS USB IP core family device controller + * Structures, registers and logging macros + */ + +#ifndef _CI13XXX_h_ +#define _CI13XXX_h_ + +/****************************************************************************** + * DEFINE + *****************************************************************************/ +#define ENDPT_MAX (16) +#define CTRL_PAYLOAD_MAX (64) +#define RX (0) /* similar to USB_DIR_OUT but can be used as an index */ +#define TX (1) /* similar to USB_DIR_IN but can be used as an index */ + +/****************************************************************************** + * STRUCTURES + *****************************************************************************/ +/* DMA layout of transfer descriptors */ +struct ci13xxx_td { + /* 0 */ + u32 next; +#define TD_TERMINATE BIT(0) + /* 1 */ + u32 token; +#define TD_STATUS (0x00FFUL << 0) +#define TD_STATUS_TR_ERR BIT(3) +#define TD_STATUS_DT_ERR BIT(5) +#define TD_STATUS_HALTED BIT(6) +#define TD_STATUS_ACTIVE BIT(7) +#define TD_MULTO (0x0003UL << 10) +#define TD_IOC BIT(15) +#define TD_TOTAL_BYTES (0x7FFFUL << 16) + /* 2 */ + u32 page[5]; +#define TD_CURR_OFFSET (0x0FFFUL << 0) +#define TD_FRAME_NUM (0x07FFUL << 0) +#define TD_RESERVED_MASK (0x0FFFUL << 0) +} __attribute__ ((packed)); + +/* DMA layout of queue heads */ +struct ci13xxx_qh { + /* 0 */ + u32 cap; +#define QH_IOS BIT(15) +#define QH_MAX_PKT (0x07FFUL << 16) +#define QH_ZLT BIT(29) +#define QH_MULT (0x0003UL << 30) + /* 1 */ + u32 curr; + /* 2 - 8 */ + struct ci13xxx_td td; + /* 9 */ + u32 RESERVED; + struct usb_ctrlrequest setup; +} __attribute__ ((packed)); + +/* Extension of usb_request */ +struct ci13xxx_req { + struct usb_request req; + unsigned map; + struct list_head queue; + struct ci13xxx_td *ptr; + dma_addr_t dma; +}; + +/* Extension of usb_ep */ +struct ci13xxx_ep { + struct usb_ep ep; + const struct usb_endpoint_descriptor *desc; + u8 dir; + u8 num; + u8 type; + char name[16]; + struct { + struct list_head queue; + struct ci13xxx_qh *ptr; + dma_addr_t dma; + } qh[2]; + struct usb_request *status; + int wedge; + + /* global resources */ + spinlock_t *lock; + struct device *device; + struct dma_pool *td_pool; +}; + +/* CI13XXX UDC descriptor & global resources */ +struct ci13xxx { + spinlock_t *lock; /* ctrl register bank access */ + + struct dma_pool *qh_pool; /* DMA pool for queue heads */ + struct dma_pool *td_pool; /* DMA pool for transfer descs */ + + struct usb_gadget gadget; /* USB slave device */ + struct ci13xxx_ep ci13xxx_ep[ENDPT_MAX]; /* extended endpts */ + + struct usb_gadget_driver *driver; /* 3rd party gadget driver */ +}; + +/****************************************************************************** + * REGISTERS + *****************************************************************************/ +/* register size */ +#define REG_BITS (32) + +/* HCCPARAMS */ +#define HCCPARAMS_LEN BIT(17) + +/* DCCPARAMS */ +#define DCCPARAMS_DEN (0x1F << 0) +#define DCCPARAMS_DC BIT(7) + +/* TESTMODE */ +#define TESTMODE_FORCE BIT(0) + +/* USBCMD */ +#define USBCMD_RS BIT(0) +#define USBCMD_RST BIT(1) +#define USBCMD_SUTW BIT(13) + +/* USBSTS & USBINTR */ +#define USBi_UI BIT(0) +#define USBi_UEI BIT(1) +#define USBi_PCI BIT(2) +#define USBi_URI BIT(6) +#define USBi_SLI BIT(8) + +/* DEVICEADDR */ +#define DEVICEADDR_USBADRA BIT(24) +#define DEVICEADDR_USBADR (0x7FUL << 25) + +/* PORTSC */ +#define PORTSC_SUSP BIT(7) +#define PORTSC_HSP BIT(9) +#define PORTSC_PTC (0x0FUL << 16) + +/* DEVLC */ +#define DEVLC_PSPD (0x03UL << 25) +#define DEVLC_PSPD_HS (0x02UL << 25) + +/* USBMODE */ +#define USBMODE_CM (0x03UL << 0) +#define USBMODE_CM_IDLE (0x00UL << 0) +#define USBMODE_CM_DEVICE (0x02UL << 0) +#define USBMODE_CM_HOST (0x03UL << 0) +#define USBMODE_SLOM BIT(3) + +/* ENDPTCTRL */ +#define ENDPTCTRL_RXS BIT(0) +#define ENDPTCTRL_RXT (0x03UL << 2) +#define ENDPTCTRL_RXR BIT(6) /* reserved for port 0 */ +#define ENDPTCTRL_RXE BIT(7) +#define ENDPTCTRL_TXS BIT(16) +#define ENDPTCTRL_TXT (0x03UL << 18) +#define ENDPTCTRL_TXR BIT(22) /* reserved for port 0 */ +#define ENDPTCTRL_TXE BIT(23) + +/****************************************************************************** + * LOGGING + *****************************************************************************/ +#define ci13xxx_printk(level, format, args...) \ +do { \ + if (_udc == NULL) \ + printk(level "[%s] " format "\n", __func__, ## args); \ + else \ + dev_printk(level, _udc->gadget.dev.parent, \ + "[%s] " format "\n", __func__, ## args); \ +} while (0) + +#define err(format, args...) ci13xxx_printk(KERN_ERR, format, ## args) +#define warn(format, args...) ci13xxx_printk(KERN_WARNING, format, ## args) +#define info(format, args...) ci13xxx_printk(KERN_INFO, format, ## args) + +#ifdef TRACE +#define trace(format, args...) ci13xxx_printk(KERN_DEBUG, format, ## args) +#define dbg_trace(format, args...) dev_dbg(dev, format, ##args) +#else +#define trace(format, args...) do {} while (0) +#define dbg_trace(format, args...) do {} while (0) +#endif + +#endif /* _CI13XXX_h_ */ diff --git a/drivers/usb/gadget/epautoconf.c b/drivers/usb/gadget/epautoconf.c index 9462e30192d8..a36b1175b18d 100644 --- a/drivers/usb/gadget/epautoconf.c +++ b/drivers/usb/gadget/epautoconf.c @@ -161,7 +161,7 @@ ep_matches ( /* report address */ desc->bEndpointAddress &= USB_DIR_IN; if (isdigit (ep->name [2])) { - u8 num = simple_strtol (&ep->name [2], NULL, 10); + u8 num = simple_strtoul (&ep->name [2], NULL, 10); desc->bEndpointAddress |= num; #ifdef MANY_ENDPOINTS } else if (desc->bEndpointAddress & USB_DIR_IN) { diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 2e71368f45b4..b10fa31cc915 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -1,7 +1,7 @@ /* * file_storage.c -- File-backed USB Storage Gadget, for USB development * - * Copyright (C) 2003-2007 Alan Stern + * Copyright (C) 2003-2008 Alan Stern * All rights reserved. * * Redistribution and use in source and binary forms, with or without @@ -38,16 +38,17 @@ /* * The File-backed Storage Gadget acts as a USB Mass Storage device, - * appearing to the host as a disk drive. In addition to providing an - * example of a genuinely useful gadget driver for a USB device, it also - * illustrates a technique of double-buffering for increased throughput. - * Last but not least, it gives an easy way to probe the behavior of the - * Mass Storage drivers in a USB host. + * appearing to the host as a disk drive or as a CD-ROM drive. In addition + * to providing an example of a genuinely useful gadget driver for a USB + * device, it also illustrates a technique of double-buffering for increased + * throughput. Last but not least, it gives an easy way to probe the + * behavior of the Mass Storage drivers in a USB host. * * Backing storage is provided by a regular file or a block device, specified * by the "file" module parameter. Access can be limited to read-only by - * setting the optional "ro" module parameter. The gadget will indicate that - * it has removable media if the optional "removable" module parameter is set. + * setting the optional "ro" module parameter. (For CD-ROM emulation, + * access is always read-only.) The gadget will indicate that it has + * removable media if the optional "removable" module parameter is set. * * The gadget supports the Control-Bulk (CB), Control-Bulk-Interrupt (CBI), * and Bulk-Only (also known as Bulk-Bulk-Bulk or BBB) transports, selected @@ -64,7 +65,12 @@ * The default number of LUNs is taken from the number of "file" elements; * it is 1 if "file" is not given. If "removable" is not set then a backing * file must be specified for each LUN. If it is set, then an unspecified - * or empty backing filename means the LUN's medium is not loaded. + * or empty backing filename means the LUN's medium is not loaded. Ideally + * each LUN would be settable independently as a disk drive or a CD-ROM + * drive, but currently all LUNs have to be the same type. The CD-ROM + * emulation includes a single data track and no audio tracks; hence there + * need be only one backing file per LUN. Note also that the CD-ROM block + * length is set to 512 rather than the more common value 2048. * * Requirements are modest; only a bulk-in and a bulk-out endpoint are * needed (an interrupt-out endpoint is also needed for CBI). The memory @@ -91,6 +97,8 @@ * USB device controller (usually true), * boolean to permit the driver to halt * bulk endpoints + * cdrom Default false, boolean for whether to emulate + * a CD-ROM drive * transport=XXX Default BBB, transport name (CB, CBI, or BBB) * protocol=YYY Default SCSI, protocol name (RBC, 8020 or * ATAPI, QIC, UFI, 8070, or SCSI; @@ -103,15 +111,16 @@ * PAGE_CACHE_SIZE) * * If CONFIG_USB_FILE_STORAGE_TEST is not set, only the "file", "ro", - * "removable", "luns", and "stall" options are available; default values - * are used for everything else. + * "removable", "luns", "stall", and "cdrom" options are available; default + * values are used for everything else. * * The pathnames of the backing files and the ro settings are available in * the attribute files "file" and "ro" in the lun<n> subdirectory of the * gadget's sysfs directory. If the "removable" option is set, writing to * these files will simulate ejecting/loading the medium (writing an empty * line means eject) and adjusting a write-enable tab. Changes to the ro - * setting are not allowed when the medium is loaded. + * setting are not allowed when the medium is loaded or if CD-ROM emulation + * is being used. * * This gadget driver is heavily based on "Gadget Zero" by David Brownell. * The driver's SCSI command interface was based on the "Information @@ -261,7 +270,7 @@ #define DRIVER_DESC "File-backed Storage Gadget" #define DRIVER_NAME "g_file_storage" -#define DRIVER_VERSION "7 August 2007" +#define DRIVER_VERSION "20 November 2008" static const char longname[] = DRIVER_DESC; static const char shortname[] = DRIVER_NAME; @@ -341,6 +350,7 @@ static struct { int removable; int can_stall; + int cdrom; char *transport_parm; char *protocol_parm; @@ -359,6 +369,7 @@ static struct { .protocol_parm = "SCSI", .removable = 0, .can_stall = 1, + .cdrom = 0, .vendor = DRIVER_VENDOR_ID, .product = DRIVER_PRODUCT_ID, .release = 0xffff, // Use controller chip type @@ -382,6 +393,9 @@ MODULE_PARM_DESC(removable, "true to simulate removable media"); module_param_named(stall, mod_data.can_stall, bool, S_IRUGO); MODULE_PARM_DESC(stall, "false to prevent bulk stalls"); +module_param_named(cdrom, mod_data.cdrom, bool, S_IRUGO); +MODULE_PARM_DESC(cdrom, "true to emulate cdrom instead of disk"); + /* In the non-TEST version, only the module parameters listed above * are available. */ @@ -411,6 +425,10 @@ MODULE_PARM_DESC(buflen, "I/O buffer size"); /*-------------------------------------------------------------------------*/ +/* SCSI device types */ +#define TYPE_DISK 0x00 +#define TYPE_CDROM 0x05 + /* USB protocol value = the transport method */ #define USB_PR_CBI 0x00 // Control/Bulk/Interrupt #define USB_PR_CB 0x01 // Control/Bulk w/o interrupt @@ -487,6 +505,8 @@ struct interrupt_data { #define SC_READ_12 0xa8 #define SC_READ_CAPACITY 0x25 #define SC_READ_FORMAT_CAPACITIES 0x23 +#define SC_READ_HEADER 0x44 +#define SC_READ_TOC 0x43 #define SC_RELEASE 0x17 #define SC_REQUEST_SENSE 0x03 #define SC_RESERVE 0x16 @@ -2006,23 +2026,28 @@ static int do_inquiry(struct fsg_dev *fsg, struct fsg_buffhd *bh) u8 *buf = (u8 *) bh->buf; static char vendor_id[] = "Linux "; - static char product_id[] = "File-Stor Gadget"; + static char product_disk_id[] = "File-Stor Gadget"; + static char product_cdrom_id[] = "File-CD Gadget "; if (!fsg->curlun) { // Unsupported LUNs are okay fsg->bad_lun_okay = 1; memset(buf, 0, 36); buf[0] = 0x7f; // Unsupported, no device-type + buf[4] = 31; // Additional length return 36; } - memset(buf, 0, 8); // Non-removable, direct-access device + memset(buf, 0, 8); + buf[0] = (mod_data.cdrom ? TYPE_CDROM : TYPE_DISK); if (mod_data.removable) buf[1] = 0x80; buf[2] = 2; // ANSI SCSI level 2 buf[3] = 2; // SCSI-2 INQUIRY data format buf[4] = 31; // Additional length // No special options - sprintf(buf + 8, "%-8s%-16s%04x", vendor_id, product_id, + sprintf(buf + 8, "%-8s%-16s%04x", vendor_id, + (mod_data.cdrom ? product_cdrom_id : + product_disk_id), mod_data.release); return 36; } @@ -2101,6 +2126,75 @@ static int do_read_capacity(struct fsg_dev *fsg, struct fsg_buffhd *bh) } +static void store_cdrom_address(u8 *dest, int msf, u32 addr) +{ + if (msf) { + /* Convert to Minutes-Seconds-Frames */ + addr >>= 2; /* Convert to 2048-byte frames */ + addr += 2*75; /* Lead-in occupies 2 seconds */ + dest[3] = addr % 75; /* Frames */ + addr /= 75; + dest[2] = addr % 60; /* Seconds */ + addr /= 60; + dest[1] = addr; /* Minutes */ + dest[0] = 0; /* Reserved */ + } else { + /* Absolute sector */ + put_be32(dest, addr); + } +} + +static int do_read_header(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct lun *curlun = fsg->curlun; + int msf = fsg->cmnd[1] & 0x02; + u32 lba = get_be32(&fsg->cmnd[2]); + u8 *buf = (u8 *) bh->buf; + + if ((fsg->cmnd[1] & ~0x02) != 0) { /* Mask away MSF */ + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + if (lba >= curlun->num_sectors) { + curlun->sense_data = SS_LOGICAL_BLOCK_ADDRESS_OUT_OF_RANGE; + return -EINVAL; + } + + memset(buf, 0, 8); + buf[0] = 0x01; /* 2048 bytes of user data, rest is EC */ + store_cdrom_address(&buf[4], msf, lba); + return 8; +} + + +static int do_read_toc(struct fsg_dev *fsg, struct fsg_buffhd *bh) +{ + struct lun *curlun = fsg->curlun; + int msf = fsg->cmnd[1] & 0x02; + int start_track = fsg->cmnd[6]; + u8 *buf = (u8 *) bh->buf; + + if ((fsg->cmnd[1] & ~0x02) != 0 || /* Mask away MSF */ + start_track > 1) { + curlun->sense_data = SS_INVALID_FIELD_IN_CDB; + return -EINVAL; + } + + memset(buf, 0, 20); + buf[1] = (20-2); /* TOC data length */ + buf[2] = 1; /* First track number */ + buf[3] = 1; /* Last track number */ + buf[5] = 0x16; /* Data track, copying allowed */ + buf[6] = 0x01; /* Only track is number 1 */ + store_cdrom_address(&buf[8], msf, 0); + + buf[13] = 0x16; /* Lead-out track is data */ + buf[14] = 0xAA; /* Lead-out track number */ + store_cdrom_address(&buf[16], msf, curlun->num_sectors); + return 20; +} + + static int do_mode_sense(struct fsg_dev *fsg, struct fsg_buffhd *bh) { struct lun *curlun = fsg->curlun; @@ -2848,6 +2942,26 @@ static int do_scsi_command(struct fsg_dev *fsg) reply = do_read_capacity(fsg, bh); break; + case SC_READ_HEADER: + if (!mod_data.cdrom) + goto unknown_cmnd; + fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (3<<7) | (0x1f<<1), 1, + "READ HEADER")) == 0) + reply = do_read_header(fsg, bh); + break; + + case SC_READ_TOC: + if (!mod_data.cdrom) + goto unknown_cmnd; + fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); + if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, + (7<<6) | (1<<1), 1, + "READ TOC")) == 0) + reply = do_read_toc(fsg, bh); + break; + case SC_READ_FORMAT_CAPACITIES: fsg->data_size_from_cmnd = get_be16(&fsg->cmnd[7]); if ((reply = check_command(fsg, 10, DATA_DIR_TO_HOST, @@ -2933,6 +3047,7 @@ static int do_scsi_command(struct fsg_dev *fsg) // Fall through default: + unknown_cmnd: fsg->data_size_from_cmnd = 0; sprintf(unknown, "Unknown x%02x", fsg->cmnd[0]); if ((reply = check_command(fsg, fsg->cmnd_size, @@ -3498,6 +3613,7 @@ static int open_backing_file(struct lun *curlun, const char *filename) struct inode *inode = NULL; loff_t size; loff_t num_sectors; + loff_t min_sectors; /* R/W if we can, R/O if we must */ ro = curlun->ro; @@ -3541,8 +3657,19 @@ static int open_backing_file(struct lun *curlun, const char *filename) rc = (int) size; goto out; } - num_sectors = size >> 9; // File size in 512-byte sectors - if (num_sectors == 0) { + num_sectors = size >> 9; // File size in 512-byte blocks + min_sectors = 1; + if (mod_data.cdrom) { + num_sectors &= ~3; // Reduce to a multiple of 2048 + min_sectors = 300*4; // Smallest track is 300 frames + if (num_sectors >= 256*60*75*4) { + num_sectors = (256*60*75 - 1) * 4; + LINFO(curlun, "file too big: %s\n", filename); + LINFO(curlun, "using only first %d blocks\n", + (int) num_sectors); + } + } + if (num_sectors < min_sectors) { LINFO(curlun, "file too small: %s\n", filename); rc = -ETOOSMALL; goto out; @@ -3845,9 +3972,12 @@ static int __init fsg_bind(struct usb_gadget *gadget) goto out; if (mod_data.removable) { // Enable the store_xxx attributes - dev_attr_ro.attr.mode = dev_attr_file.attr.mode = 0644; - dev_attr_ro.store = store_ro; + dev_attr_file.attr.mode = 0644; dev_attr_file.store = store_file; + if (!mod_data.cdrom) { + dev_attr_ro.attr.mode = 0644; + dev_attr_ro.store = store_ro; + } } /* Find out how many LUNs there should be */ @@ -3872,6 +4002,8 @@ static int __init fsg_bind(struct usb_gadget *gadget) for (i = 0; i < fsg->nluns; ++i) { curlun = &fsg->luns[i]; curlun->ro = mod_data.ro[i]; + if (mod_data.cdrom) + curlun->ro = 1; curlun->dev.release = lun_release; curlun->dev.parent = &gadget->dev; curlun->dev.driver = &fsg_driver.driver; @@ -4031,9 +4163,9 @@ static int __init fsg_bind(struct usb_gadget *gadget) mod_data.protocol_name, mod_data.protocol_type); DBG(fsg, "VendorID=x%04x, ProductID=x%04x, Release=x%04x\n", mod_data.vendor, mod_data.product, mod_data.release); - DBG(fsg, "removable=%d, stall=%d, buflen=%u\n", + DBG(fsg, "removable=%d, stall=%d, cdrom=%d, buflen=%u\n", mod_data.removable, mod_data.can_stall, - mod_data.buflen); + mod_data.cdrom, mod_data.buflen); DBG(fsg, "I/O thread pid: %d\n", task_pid_nr(fsg->thread_task)); set_bit(REGISTERED, &fsg->atomic_bitflags); @@ -4050,6 +4182,7 @@ out: fsg->state = FSG_STATE_TERMINATED; // The thread is dead fsg_unbind(gadget); close_all_backing_files(fsg); + complete(&fsg->thread_notifier); return rc; } diff --git a/drivers/usb/gadget/fsl_qe_udc.c b/drivers/usb/gadget/fsl_qe_udc.c index f40272565098..d6c5bcd40064 100644 --- a/drivers/usb/gadget/fsl_qe_udc.c +++ b/drivers/usb/gadget/fsl_qe_udc.c @@ -26,6 +26,7 @@ #include <linux/ioport.h> #include <linux/types.h> #include <linux/errno.h> +#include <linux/err.h> #include <linux/slab.h> #include <linux/list.h> #include <linux/interrupt.h> @@ -370,6 +371,9 @@ static int qe_ep_bd_init(struct qe_udc *udc, unsigned char pipe_num) /* alloc multi-ram for BD rings and set the ep parameters */ tmp_addr = cpm_muram_alloc(sizeof(struct qe_bd) * (bdring_len + USB_BDRING_LEN_TX), QE_ALIGNMENT_OF_BD); + if (IS_ERR_VALUE(tmp_addr)) + return -ENOMEM; + out_be16(&epparam->rbase, (u16)tmp_addr); out_be16(&epparam->tbase, (u16)(tmp_addr + (sizeof(struct qe_bd) * bdring_len))); @@ -689,7 +693,7 @@ en_done2: en_done1: spin_unlock_irqrestore(&udc->lock, flags); en_done: - dev_dbg(udc->dev, "failed to initialize %s\n", ep->ep.name); + dev_err(udc->dev, "failed to initialize %s\n", ep->ep.name); return -ENODEV; } @@ -2408,6 +2412,8 @@ static struct qe_udc __devinit *qe_udc_config(struct of_device *ofdev) tmp_addr = cpm_muram_alloc((USB_MAX_ENDPOINTS * sizeof(struct usb_ep_para)), USB_EP_PARA_ALIGNMENT); + if (IS_ERR_VALUE(tmp_addr)) + goto cleanup; for (i = 0; i < USB_MAX_ENDPOINTS; i++) { out_be16(&usbpram->epptr[i], (u16)tmp_addr); @@ -2513,7 +2519,7 @@ static int __devinit qe_udc_probe(struct of_device *ofdev, /* Initialize the udc structure including QH member and other member */ udc_controller = qe_udc_config(ofdev); if (!udc_controller) { - dev_dbg(&ofdev->dev, "udc_controll is NULL\n"); + dev_err(&ofdev->dev, "failed to initialize\n"); return -ENOMEM; } @@ -2568,7 +2574,7 @@ static int __devinit qe_udc_probe(struct of_device *ofdev, /* create a buf for ZLP send, need to remain zeroed */ udc_controller->nullbuf = kzalloc(256, GFP_KERNEL); if (udc_controller->nullbuf == NULL) { - dev_dbg(udc_controller->dev, "cannot alloc nullbuf\n"); + dev_err(udc_controller->dev, "cannot alloc nullbuf\n"); ret = -ENOMEM; goto err3; } diff --git a/drivers/usb/gadget/gadget_chips.h b/drivers/usb/gadget/gadget_chips.h index 4e3107dd2f34..ec6d439a2aa5 100644 --- a/drivers/usb/gadget/gadget_chips.h +++ b/drivers/usb/gadget/gadget_chips.h @@ -110,7 +110,6 @@ #define gadget_is_at91(g) 0 #endif -/* status unclear */ #ifdef CONFIG_USB_GADGET_IMX #define gadget_is_imx(g) !strcmp("imx_udc", (g)->name) #else @@ -158,6 +157,11 @@ #define gadget_is_fsl_qe(g) 0 #endif +#ifdef CONFIG_USB_GADGET_CI13XXX +#define gadget_is_ci13xxx(g) (!strcmp("ci13xxx_udc", (g)->name)) +#else +#define gadget_is_ci13xxx(g) 0 +#endif // CONFIG_USB_GADGET_SX2 // CONFIG_USB_GADGET_AU1X00 @@ -225,6 +229,8 @@ static inline int usb_gadget_controller_number(struct usb_gadget *gadget) return 0x21; else if (gadget_is_fsl_qe(gadget)) return 0x22; + else if (gadget_is_ci13xxx(gadget)) + return 0x23; return -ENOENT; } diff --git a/drivers/usb/gadget/goku_udc.c b/drivers/usb/gadget/goku_udc.c index 60aa04847b18..63419c4d503c 100644 --- a/drivers/usb/gadget/goku_udc.c +++ b/drivers/usb/gadget/goku_udc.c @@ -1349,7 +1349,7 @@ int usb_gadget_register_driver(struct usb_gadget_driver *driver) int retval; if (!driver - || driver->speed != USB_SPEED_FULL + || driver->speed < USB_SPEED_FULL || !driver->bind || !driver->disconnect || !driver->setup) diff --git a/drivers/usb/gadget/imx_udc.c b/drivers/usb/gadget/imx_udc.c new file mode 100644 index 000000000000..cde8fdf15d5b --- /dev/null +++ b/drivers/usb/gadget/imx_udc.c @@ -0,0 +1,1516 @@ +/* + * driver/usb/gadget/imx_udc.c + * + * Copyright (C) 2005 Mike Lee(eemike@gmail.com) + * Copyright (C) 2008 Darius Augulis <augulis.darius@gmail.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. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/irq.h> +#include <linux/device.h> +#include <linux/dma-mapping.h> +#include <linux/clk.h> +#include <linux/delay.h> + +#include <linux/usb/ch9.h> +#include <linux/usb/gadget.h> + +#include <mach/usb.h> +#include <mach/hardware.h> + +#include "imx_udc.h" + +static const char driver_name[] = "imx_udc"; +static const char ep0name[] = "ep0"; + +void ep0_chg_stat(const char *label, struct imx_udc_struct *imx_usb, + enum ep0_state stat); + +/******************************************************************************* + * IMX UDC hardware related functions + ******************************************************************************* + */ + +void imx_udc_enable(struct imx_udc_struct *imx_usb) +{ + int temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_FE_ENA | CTRL_AFE_ENA, imx_usb->base + USB_CTRL); + imx_usb->gadget.speed = USB_SPEED_FULL; +} + +void imx_udc_disable(struct imx_udc_struct *imx_usb) +{ + int temp = __raw_readl(imx_usb->base + USB_CTRL); + + __raw_writel(temp & ~(CTRL_FE_ENA | CTRL_AFE_ENA), + imx_usb->base + USB_CTRL); + + ep0_chg_stat(__func__, imx_usb, EP0_IDLE); + imx_usb->gadget.speed = USB_SPEED_UNKNOWN; +} + +void imx_udc_reset(struct imx_udc_struct *imx_usb) +{ + int temp = __raw_readl(imx_usb->base + USB_ENAB); + + /* set RST bit */ + __raw_writel(temp | ENAB_RST, imx_usb->base + USB_ENAB); + + /* wait RST bit to clear */ + do {} while (__raw_readl(imx_usb->base + USB_ENAB) & ENAB_RST); + + /* wait CFG bit to assert */ + do {} while (!(__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG)); + + /* udc module is now ready */ +} + +void imx_udc_config(struct imx_udc_struct *imx_usb) +{ + u8 ep_conf[5]; + u8 i, j, cfg; + struct imx_ep_struct *imx_ep; + + /* wait CFG bit to assert */ + do {} while (!(__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG)); + + /* Download the endpoint buffer for endpoint 0. */ + for (j = 0; j < 5; j++) { + i = (j == 2 ? imx_usb->imx_ep[0].fifosize : 0x00); + __raw_writeb(i, imx_usb->base + USB_DDAT); + do {} while (__raw_readl(imx_usb->base + USB_DADR) & DADR_BSY); + } + + /* Download the endpoint buffers for endpoints 1-5. + * We specify two configurations, one interface + */ + for (cfg = 1; cfg < 3; cfg++) { + for (i = 1; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + /* EP no | Config no */ + ep_conf[0] = (i << 4) | (cfg << 2); + /* Type | Direction */ + ep_conf[1] = (imx_ep->bmAttributes << 3) | + (EP_DIR(imx_ep) << 2); + /* Max packet size */ + ep_conf[2] = imx_ep->fifosize; + /* TRXTYP */ + ep_conf[3] = 0xC0; + /* FIFO no */ + ep_conf[4] = i; + + D_INI(imx_usb->dev, + "<%s> ep%d_conf[%d]:" + "[%02x-%02x-%02x-%02x-%02x]\n", + __func__, i, cfg, + ep_conf[0], ep_conf[1], ep_conf[2], + ep_conf[3], ep_conf[4]); + + for (j = 0; j < 5; j++) { + __raw_writeb(ep_conf[j], + imx_usb->base + USB_DDAT); + do {} while (__raw_readl(imx_usb->base + USB_DADR) + & DADR_BSY); + } + } + } + + /* wait CFG bit to clear */ + do {} while (__raw_readl(imx_usb->base + USB_DADR) & DADR_CFG); +} + +void imx_udc_init_irq(struct imx_udc_struct *imx_usb) +{ + int i; + + /* Mask and clear all irqs */ + __raw_writel(0xFFFFFFFF, imx_usb->base + USB_MASK); + __raw_writel(0xFFFFFFFF, imx_usb->base + USB_INTR); + for (i = 0; i < IMX_USB_NB_EP; i++) { + __raw_writel(0x1FF, imx_usb->base + USB_EP_MASK(i)); + __raw_writel(0x1FF, imx_usb->base + USB_EP_INTR(i)); + } + + /* Enable USB irqs */ + __raw_writel(INTR_MSOF | INTR_FRAME_MATCH, imx_usb->base + USB_MASK); + + /* Enable EP0 irqs */ + __raw_writel(0x1FF & ~(EPINTR_DEVREQ | EPINTR_MDEVREQ | EPINTR_EOT + | EPINTR_EOF | EPINTR_FIFO_EMPTY | EPINTR_FIFO_FULL), + imx_usb->base + USB_EP_MASK(0)); +} + +void imx_udc_init_ep(struct imx_udc_struct *imx_usb) +{ + int i, max, temp; + struct imx_ep_struct *imx_ep; + for (i = 0; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + switch (imx_ep->fifosize) { + case 8: + max = 0; + break; + case 16: + max = 1; + break; + case 32: + max = 2; + break; + case 64: + max = 3; + break; + default: + max = 1; + break; + } + temp = (EP_DIR(imx_ep) << 7) | (max << 5) + | (imx_ep->bmAttributes << 3); + __raw_writel(temp, imx_usb->base + USB_EP_STAT(i)); + __raw_writel(temp | EPSTAT_FLUSH, imx_usb->base + USB_EP_STAT(i)); + D_INI(imx_usb->dev, "<%s> ep%d_stat %08x\n", __func__, i, + __raw_readl(imx_usb->base + USB_EP_STAT(i))); + } +} + +void imx_udc_init_fifo(struct imx_udc_struct *imx_usb) +{ + int i, temp; + struct imx_ep_struct *imx_ep; + for (i = 0; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + + /* Fifo control */ + temp = EP_DIR(imx_ep) ? 0x0B000000 : 0x0F000000; + __raw_writel(temp, imx_usb->base + USB_EP_FCTRL(i)); + D_INI(imx_usb->dev, "<%s> ep%d_fctrl %08x\n", __func__, i, + __raw_readl(imx_usb->base + USB_EP_FCTRL(i))); + + /* Fifo alarm */ + temp = (i ? imx_ep->fifosize / 2 : 0); + __raw_writel(temp, imx_usb->base + USB_EP_FALRM(i)); + D_INI(imx_usb->dev, "<%s> ep%d_falrm %08x\n", __func__, i, + __raw_readl(imx_usb->base + USB_EP_FALRM(i))); + } +} + +static void imx_udc_init(struct imx_udc_struct *imx_usb) +{ + /* Reset UDC */ + imx_udc_reset(imx_usb); + + /* Download config to enpoint buffer */ + imx_udc_config(imx_usb); + + /* Setup interrups */ + imx_udc_init_irq(imx_usb); + + /* Setup endpoints */ + imx_udc_init_ep(imx_usb); + + /* Setup fifos */ + imx_udc_init_fifo(imx_usb); +} + +void imx_ep_irq_enable(struct imx_ep_struct *imx_ep) +{ + + int i = EP_NO(imx_ep); + + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_MASK(i)); + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_INTR(i)); + __raw_writel(0x1FF & ~(EPINTR_EOT | EPINTR_EOF), + imx_ep->imx_usb->base + USB_EP_MASK(i)); +} + +void imx_ep_irq_disable(struct imx_ep_struct *imx_ep) +{ + + int i = EP_NO(imx_ep); + + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_MASK(i)); + __raw_writel(0x1FF, imx_ep->imx_usb->base + USB_EP_INTR(i)); +} + +int imx_ep_empty(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + + return __raw_readl(imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep))) + & FSTAT_EMPTY; +} + +unsigned imx_fifo_bcount(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + + return (__raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))) + & EPSTAT_BCOUNT) >> 16; +} + +void imx_flush(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + + int temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + __raw_writel(temp | EPSTAT_FLUSH, + imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); +} + +void imx_ep_stall(struct imx_ep_struct *imx_ep) +{ + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + int temp, i; + + D_ERR(imx_usb->dev, "<%s> Forced stall on %s\n", __func__, imx_ep->ep.name); + + imx_flush(imx_ep); + + /* Special care for ep0 */ + if (EP_NO(imx_ep)) { + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_CMDOVER | CTRL_CMDERROR, imx_usb->base + USB_CTRL); + do { } while (__raw_readl(imx_usb->base + USB_CTRL) & CTRL_CMDOVER); + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp & ~CTRL_CMDERROR, imx_usb->base + USB_CTRL); + } + else { + temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + __raw_writel(temp | EPSTAT_STALL, + imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + + for (i = 0; i < 100; i ++) { + temp = __raw_readl(imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + if (!temp & EPSTAT_STALL) + break; + udelay(20); + } + if (i == 50) + D_ERR(imx_usb->dev, "<%s> Non finished stall on %s\n", + __func__, imx_ep->ep.name); + } +} + +static int imx_udc_get_frame(struct usb_gadget *_gadget) +{ + struct imx_udc_struct *imx_usb = container_of(_gadget, + struct imx_udc_struct, gadget); + + return __raw_readl(imx_usb->base + USB_FRAME) & 0x7FF; +} + +static int imx_udc_wakeup(struct usb_gadget *_gadget) +{ + return 0; +} + +/******************************************************************************* + * USB request control functions + ******************************************************************************* + */ + +static void ep_add_request(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + if (unlikely(!req)) + return; + + req->in_use = 1; + list_add_tail(&req->queue, &imx_ep->queue); +} + +static void ep_del_request(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + if (unlikely(!req)) + return; + + list_del_init(&req->queue); + req->in_use = 0; +} + +static void done(struct imx_ep_struct *imx_ep, struct imx_request *req, int status) +{ + ep_del_request(imx_ep, req); + + if (likely(req->req.status == -EINPROGRESS)) + req->req.status = status; + else + status = req->req.status; + + if (status && status != -ESHUTDOWN) + D_ERR(imx_ep->imx_usb->dev, + "<%s> complete %s req %p stat %d len %u/%u\n", __func__, + imx_ep->ep.name, &req->req, status, + req->req.actual, req->req.length); + + req->req.complete(&imx_ep->ep, &req->req); +} + +static void nuke(struct imx_ep_struct *imx_ep, int status) +{ + struct imx_request *req; + + while (!list_empty(&imx_ep->queue)) { + req = list_entry(imx_ep->queue.next, struct imx_request, queue); + done(imx_ep, req, status); + } +} + +/******************************************************************************* + * Data tansfer over USB functions + ******************************************************************************* + */ +static int read_packet(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + u8 *buf; + int bytes_ep, bufferspace, count, i; + + bytes_ep = imx_fifo_bcount(imx_ep); + bufferspace = req->req.length - req->req.actual; + + buf = req->req.buf + req->req.actual; + prefetchw(buf); + + if (unlikely(imx_ep_empty(imx_ep))) + count = 0; /* zlp */ + else + count = min(bytes_ep, bufferspace); + + for (i = count; i > 0; i--) + *buf++ = __raw_readb(imx_ep->imx_usb->base + + USB_EP_FDAT0(EP_NO(imx_ep))); + req->req.actual += count; + + return count; +} + +static int write_packet(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + u8 *buf; + int length, count, temp; + + buf = req->req.buf + req->req.actual; + prefetch(buf); + + length = min(req->req.length - req->req.actual, (u32)imx_ep->fifosize); + + if (imx_fifo_bcount(imx_ep) + length > imx_ep->fifosize) { + D_TRX(imx_ep->imx_usb->dev, "<%s> packet overfill %s fifo\n", + __func__, imx_ep->ep.name); + return -1; + } + + req->req.actual += length; + count = length; + + if (!count && req->req.zero) { /* zlp */ + temp = __raw_readl(imx_ep->imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))); + __raw_writel(temp | EPSTAT_ZLPS, imx_ep->imx_usb->base + + USB_EP_STAT(EP_NO(imx_ep))); + D_TRX(imx_ep->imx_usb->dev, "<%s> zero packet\n", __func__); + return 0; + } + + while (count--) { + if (count == 0) { /* last byte */ + temp = __raw_readl(imx_ep->imx_usb->base + + USB_EP_FCTRL(EP_NO(imx_ep))); + __raw_writel(temp | FCTRL_WFR, imx_ep->imx_usb->base + + USB_EP_FCTRL(EP_NO(imx_ep))); + } + __raw_writeb(*buf++, + imx_ep->imx_usb->base + USB_EP_FDAT0(EP_NO(imx_ep))); + } + + return length; +} + +static int read_fifo(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + int bytes = 0, + count, + completed = 0; + + while (__raw_readl(imx_ep->imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep))) + & FSTAT_FR) { + count = read_packet(imx_ep, req); + bytes += count; + + completed = (count != imx_ep->fifosize); + if (completed || req->req.actual == req->req.length) { + completed = 1; + break; + } + } + + if (completed || !req->req.length) { + done(imx_ep, req, 0); + D_REQ(imx_ep->imx_usb->dev, "<%s> %s req<%p> %s\n", + __func__, imx_ep->ep.name, req, + completed ? "completed" : "not completed"); + if (!EP_NO(imx_ep)) + ep0_chg_stat(__func__, imx_ep->imx_usb, EP0_IDLE); + } + + D_TRX(imx_ep->imx_usb->dev, "<%s> bytes read: %d\n", __func__, bytes); + + return completed; +} + +static int write_fifo(struct imx_ep_struct *imx_ep, struct imx_request *req) +{ + int bytes = 0, + count, + completed = 0; + + while (!completed) { + count = write_packet(imx_ep, req); + if (count < 0) + break; /* busy */ + bytes += count; + + /* last packet "must be" short (or a zlp) */ + completed = (count != imx_ep->fifosize); + + if (unlikely(completed)) { + done(imx_ep, req, 0); + D_REQ(imx_ep->imx_usb->dev, "<%s> %s req<%p> %s\n", + __func__, imx_ep->ep.name, req, + completed ? "completed" : "not completed"); + if (!EP_NO(imx_ep)) + ep0_chg_stat(__func__, imx_ep->imx_usb, EP0_IDLE); + } + } + + D_TRX(imx_ep->imx_usb->dev, "<%s> bytes sent: %d\n", __func__, bytes); + + return completed; +} + +/******************************************************************************* + * Endpoint handlers + ******************************************************************************* + */ +static int handle_ep(struct imx_ep_struct *imx_ep) +{ + struct imx_request *req; + int completed = 0; + + do { + if (!list_empty(&imx_ep->queue)) + req = list_entry(imx_ep->queue.next, + struct imx_request, queue); + else { + D_REQ(imx_ep->imx_usb->dev, "<%s> no request on %s\n", + __func__, imx_ep->ep.name); + return 0; + } + + if (EP_DIR(imx_ep)) /* to host */ + completed = write_fifo(imx_ep, req); + else /* to device */ + completed = read_fifo(imx_ep, req); + + dump_ep_stat(__func__, imx_ep); + + } while (completed); + + return 0; +} + +static int handle_ep0(struct imx_ep_struct *imx_ep) +{ + struct imx_request *req = NULL; + int ret = 0; + + if (!list_empty(&imx_ep->queue)) + req = list_entry(imx_ep->queue.next, struct imx_request, queue); + + if (req) { + switch (imx_ep->imx_usb->ep0state) { + + case EP0_IN_DATA_PHASE: /* GET_DESCRIPTOR */ + write_fifo(imx_ep, req); + break; + case EP0_OUT_DATA_PHASE: /* SET_DESCRIPTOR */ + read_fifo(imx_ep, req); + break; + default: + D_EP0(imx_ep->imx_usb->dev, + "<%s> ep0 i/o, odd state %d\n", + __func__, imx_ep->imx_usb->ep0state); + ep_del_request(imx_ep, req); + ret = -EL2HLT; + break; + } + } + + return ret; +} + +static void handle_ep0_devreq(struct imx_udc_struct *imx_usb) +{ + struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[0]; + union { + struct usb_ctrlrequest r; + u8 raw[8]; + u32 word[2]; + } u; + int temp, i; + + nuke(imx_ep, -EPROTO); + + /* read SETUP packet */ + for (i = 0; i < 2; i++) { + if (imx_ep_empty(imx_ep)) { + D_ERR(imx_usb->dev, + "<%s> no setup packet received\n", __func__); + goto stall; + } + u.word[i] = __raw_readl(imx_usb->base + USB_EP_FDAT(EP_NO(imx_ep))); + } + + temp = imx_ep_empty(imx_ep); + while (!imx_ep_empty(imx_ep)) { + i = __raw_readl(imx_usb->base + USB_EP_FDAT(EP_NO(imx_ep))); + D_ERR(imx_usb->dev, + "<%s> wrong to have extra bytes for setup : 0x%08x\n", + __func__, i); + } + if (!temp) + goto stall; + + le16_to_cpus(&u.r.wValue); + le16_to_cpus(&u.r.wIndex); + le16_to_cpus(&u.r.wLength); + + D_REQ(imx_usb->dev, "<%s> SETUP %02x.%02x v%04x i%04x l%04x\n", + __func__, u.r.bRequestType, u.r.bRequest, + u.r.wValue, u.r.wIndex, u.r.wLength); + + if (imx_usb->set_config) { + /* NACK the host by using CMDOVER */ + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_CMDOVER, imx_usb->base + USB_CTRL); + + D_ERR(imx_usb->dev, + "<%s> set config req is pending, NACK the host\n", + __func__); + return; + } + + if (u.r.bRequestType & USB_DIR_IN) + ep0_chg_stat(__func__, imx_usb, EP0_IN_DATA_PHASE); + else + ep0_chg_stat(__func__, imx_usb, EP0_OUT_DATA_PHASE); + + i = imx_usb->driver->setup(&imx_usb->gadget, &u.r); + if (i < 0) { + D_ERR(imx_usb->dev, "<%s> device setup error %d\n", + __func__, i); + goto stall; + } + + return; +stall: + D_ERR(imx_usb->dev, "<%s> protocol STALL\n", __func__); + imx_ep_stall(imx_ep); + ep0_chg_stat(__func__, imx_usb, EP0_STALL); + return; +} + +/******************************************************************************* + * USB gadget callback functions + ******************************************************************************* + */ + +static int imx_ep_enable(struct usb_ep *usb_ep, + const struct usb_endpoint_descriptor *desc) +{ + struct imx_ep_struct *imx_ep = container_of(usb_ep, + struct imx_ep_struct, ep); + struct imx_udc_struct *imx_usb = imx_ep->imx_usb; + unsigned long flags; + + if (!usb_ep + || !desc + || !EP_NO(imx_ep) + || desc->bDescriptorType != USB_DT_ENDPOINT + || imx_ep->bEndpointAddress != desc->bEndpointAddress) { + D_ERR(imx_usb->dev, + "<%s> bad ep or descriptor\n", __func__); + return -EINVAL; + } + + if (imx_ep->bmAttributes != desc->bmAttributes) { + D_ERR(imx_usb->dev, + "<%s> %s type mismatch\n", __func__, usb_ep->name); + return -EINVAL; + } + + if (imx_ep->fifosize < le16_to_cpu(desc->wMaxPacketSize)) { + D_ERR(imx_usb->dev, + "<%s> bad %s maxpacket\n", __func__, usb_ep->name); + return -ERANGE; + } + + if (!imx_usb->driver || imx_usb->gadget.speed == USB_SPEED_UNKNOWN) { + D_ERR(imx_usb->dev, "<%s> bogus device state\n", __func__); + return -ESHUTDOWN; + } + + local_irq_save(flags); + + imx_ep->stopped = 0; + imx_flush(imx_ep); + imx_ep_irq_enable(imx_ep); + + local_irq_restore(flags); + + D_EPX(imx_usb->dev, "<%s> ENABLED %s\n", __func__, usb_ep->name); + return 0; +} + +static int imx_ep_disable(struct usb_ep *usb_ep) +{ + struct imx_ep_struct *imx_ep = container_of(usb_ep, + struct imx_ep_struct, ep); + unsigned long flags; + + if (!usb_ep || !EP_NO(imx_ep) || !list_empty(&imx_ep->queue)) { + D_ERR(imx_ep->imx_usb->dev, "<%s> %s can not be disabled\n", + __func__, usb_ep ? imx_ep->ep.name : NULL); + return -EINVAL; + } + + local_irq_save(flags); + + imx_ep->stopped = 1; + nuke(imx_ep, -ESHUTDOWN); + imx_flush(imx_ep); + imx_ep_irq_disable(imx_ep); + + local_irq_restore(flags); + + D_EPX(imx_ep->imx_usb->dev, + "<%s> DISABLED %s\n", __func__, usb_ep->name); + return 0; +} + +static struct usb_request *imx_ep_alloc_request + (struct usb_ep *usb_ep, gfp_t gfp_flags) +{ + struct imx_request *req; + + req = kzalloc(sizeof *req, gfp_flags); + if (!req || !usb_ep) + return 0; + + INIT_LIST_HEAD(&req->queue); + req->in_use = 0; + + return &req->req; +} + +static void imx_ep_free_request + (struct usb_ep *usb_ep, struct usb_request *usb_req) +{ + struct imx_request *req; + + req = container_of(usb_req, struct imx_request, req); + WARN_ON(!list_empty(&req->queue)); + kfree(req); +} + +static int imx_ep_queue + (struct usb_ep *usb_ep, struct usb_request *usb_req, gfp_t gfp_flags) +{ + struct imx_ep_struct *imx_ep; + struct imx_udc_struct *imx_usb; + struct imx_request *req; + unsigned long flags; + int ret = 0; + + imx_ep = container_of(usb_ep, struct imx_ep_struct, ep); + imx_usb = imx_ep->imx_usb; + req = container_of(usb_req, struct imx_request, req); + + /* + Special care on IMX udc. + Ignore enqueue when after set configuration from the + host. This assume all gadget drivers reply set + configuration with the next ep0 req enqueue. + */ + if (imx_usb->set_config && !EP_NO(imx_ep)) { + imx_usb->set_config = 0; + D_EPX(imx_usb->dev, + "<%s> gadget reply set config\n", __func__); + return 0; + } + + if (unlikely(!usb_req || !req || !usb_req->complete || !usb_req->buf)) { + D_ERR(imx_usb->dev, "<%s> bad params\n", __func__); + return -EINVAL; + } + + if (unlikely(!usb_ep || !imx_ep)) { + D_ERR(imx_usb->dev, "<%s> bad ep\n", __func__); + return -EINVAL; + } + + if (!imx_usb->driver || imx_usb->gadget.speed == USB_SPEED_UNKNOWN) { + D_ERR(imx_usb->dev, "<%s> bogus device state\n", __func__); + return -ESHUTDOWN; + } + + local_irq_save(flags); + + /* Debug */ + D_REQ(imx_usb->dev, "<%s> ep%d %s request for [%d] bytes\n", + __func__, EP_NO(imx_ep), + ((!EP_NO(imx_ep) && imx_ep->imx_usb->ep0state == EP0_IN_DATA_PHASE) + || (EP_NO(imx_ep) && EP_DIR(imx_ep))) ? "IN" : "OUT", usb_req->length); + dump_req(__func__, imx_ep, usb_req); + + if (imx_ep->stopped) { + usb_req->status = -ESHUTDOWN; + ret = -ESHUTDOWN; + goto out; + } + + if (req->in_use) { + D_ERR(imx_usb->dev, + "<%s> refusing to queue req %p (already queued)\n", + __func__, req); + goto out; + } + + usb_req->status = -EINPROGRESS; + usb_req->actual = 0; + + ep_add_request(imx_ep, req); + + if (!EP_NO(imx_ep)) + ret = handle_ep0(imx_ep); + else + ret = handle_ep(imx_ep); +out: + local_irq_restore(flags); + return ret; +} + +static int imx_ep_dequeue(struct usb_ep *usb_ep, struct usb_request *usb_req) +{ + + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + struct imx_request *req; + unsigned long flags; + + if (unlikely(!usb_ep || !EP_NO(imx_ep))) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + return -EINVAL; + } + + local_irq_save(flags); + + /* make sure it's actually queued on this endpoint */ + list_for_each_entry(req, &imx_ep->queue, queue) { + if (&req->req == usb_req) + break; + } + if (&req->req != usb_req) { + local_irq_restore(flags); + return -EINVAL; + } + + done(imx_ep, req, -ECONNRESET); + + local_irq_restore(flags); + return 0; +} + +static int imx_ep_set_halt(struct usb_ep *usb_ep, int value) +{ + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + unsigned long flags; + + if (unlikely(!usb_ep || !EP_NO(imx_ep))) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + return -EINVAL; + } + + local_irq_save(flags); + + if ((imx_ep->bEndpointAddress & USB_DIR_IN) + && !list_empty(&imx_ep->queue)) { + local_irq_restore(flags); + return -EAGAIN; + } + + imx_ep_stall(imx_ep); + + local_irq_restore(flags); + + D_EPX(imx_ep->imx_usb->dev, "<%s> %s halt\n", __func__, usb_ep->name); + return 0; +} + +static int imx_ep_fifo_status(struct usb_ep *usb_ep) +{ + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + + if (!usb_ep) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + return -ENODEV; + } + + if (imx_ep->imx_usb->gadget.speed == USB_SPEED_UNKNOWN) + return 0; + else + return imx_fifo_bcount(imx_ep); +} + +static void imx_ep_fifo_flush(struct usb_ep *usb_ep) +{ + struct imx_ep_struct *imx_ep = container_of + (usb_ep, struct imx_ep_struct, ep); + unsigned long flags; + + local_irq_save(flags); + + if (!usb_ep || !EP_NO(imx_ep) || !list_empty(&imx_ep->queue)) { + D_ERR(imx_ep->imx_usb->dev, "<%s> bad ep\n", __func__); + local_irq_restore(flags); + return; + } + + /* toggle and halt bits stay unchanged */ + imx_flush(imx_ep); + + local_irq_restore(flags); +} + +static struct usb_ep_ops imx_ep_ops = { + .enable = imx_ep_enable, + .disable = imx_ep_disable, + + .alloc_request = imx_ep_alloc_request, + .free_request = imx_ep_free_request, + + .queue = imx_ep_queue, + .dequeue = imx_ep_dequeue, + + .set_halt = imx_ep_set_halt, + .fifo_status = imx_ep_fifo_status, + .fifo_flush = imx_ep_fifo_flush, +}; + +/******************************************************************************* + * USB endpoint control functions + ******************************************************************************* + */ + +void ep0_chg_stat(const char *label, + struct imx_udc_struct *imx_usb, enum ep0_state stat) +{ + D_EP0(imx_usb->dev, "<%s> from %15s to %15s\n", + label, state_name[imx_usb->ep0state], state_name[stat]); + + if (imx_usb->ep0state == stat) + return; + + imx_usb->ep0state = stat; +} + +static void usb_init_data(struct imx_udc_struct *imx_usb) +{ + struct imx_ep_struct *imx_ep; + u8 i; + + /* device/ep0 records init */ + INIT_LIST_HEAD(&imx_usb->gadget.ep_list); + INIT_LIST_HEAD(&imx_usb->gadget.ep0->ep_list); + ep0_chg_stat(__func__, imx_usb, EP0_IDLE); + + /* basic endpoint records init */ + for (i = 0; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + + if (i) { + list_add_tail(&imx_ep->ep.ep_list, + &imx_usb->gadget.ep_list); + imx_ep->stopped = 1; + } else + imx_ep->stopped = 0; + + INIT_LIST_HEAD(&imx_ep->queue); + } +} + +static void udc_stop_activity(struct imx_udc_struct *imx_usb, + struct usb_gadget_driver *driver) +{ + struct imx_ep_struct *imx_ep; + int i; + + if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN) + driver = NULL; + + /* prevent new request submissions, kill any outstanding requests */ + for (i = 1; i < IMX_USB_NB_EP; i++) { + imx_ep = &imx_usb->imx_ep[i]; + imx_flush(imx_ep); + imx_ep->stopped = 1; + imx_ep_irq_disable(imx_ep); + nuke(imx_ep, -ESHUTDOWN); + } + + imx_usb->cfg = 0; + imx_usb->intf = 0; + imx_usb->alt = 0; + + if (driver) + driver->disconnect(&imx_usb->gadget); +} + +/******************************************************************************* + * Interrupt handlers + ******************************************************************************* + */ + +static irqreturn_t imx_udc_irq(int irq, void *dev) +{ + struct imx_udc_struct *imx_usb = dev; + struct usb_ctrlrequest u; + int temp, cfg, intf, alt; + int intr = __raw_readl(imx_usb->base + USB_INTR); + + if (intr & (INTR_WAKEUP | INTR_SUSPEND | INTR_RESUME | INTR_RESET_START + | INTR_RESET_STOP | INTR_CFG_CHG)) { + dump_intr(__func__, intr, imx_usb->dev); + dump_usb_stat(__func__, imx_usb); + } + + if (!imx_usb->driver) { + /*imx_udc_disable(imx_usb);*/ + goto end_irq; + } + + if (intr & INTR_WAKEUP) { + if (imx_usb->gadget.speed == USB_SPEED_UNKNOWN + && imx_usb->driver && imx_usb->driver->resume) + imx_usb->driver->resume(&imx_usb->gadget); + imx_usb->set_config = 0; + imx_usb->gadget.speed = USB_SPEED_FULL; + } + + if (intr & INTR_SUSPEND) { + if (imx_usb->gadget.speed != USB_SPEED_UNKNOWN + && imx_usb->driver && imx_usb->driver->suspend) + imx_usb->driver->suspend(&imx_usb->gadget); + imx_usb->set_config = 0; + imx_usb->gadget.speed = USB_SPEED_UNKNOWN; + } + + if (intr & INTR_RESET_START) { + __raw_writel(intr, imx_usb->base + USB_INTR); + udc_stop_activity(imx_usb, imx_usb->driver); + imx_usb->set_config = 0; + imx_usb->gadget.speed = USB_SPEED_UNKNOWN; + } + + if (intr & INTR_RESET_STOP) + imx_usb->gadget.speed = USB_SPEED_FULL; + + if (intr & INTR_CFG_CHG) { + __raw_writel(INTR_CFG_CHG, imx_usb->base + USB_INTR); + temp = __raw_readl(imx_usb->base + USB_STAT); + cfg = (temp & STAT_CFG) >> 5; + intf = (temp & STAT_INTF) >> 3; + alt = temp & STAT_ALTSET; + + D_REQ(imx_usb->dev, + "<%s> orig config C=%d, I=%d, A=%d / " + "req config C=%d, I=%d, A=%d\n", + __func__, imx_usb->cfg, imx_usb->intf, imx_usb->alt, + cfg, intf, alt); + + if (cfg != 1 && cfg != 2) + goto end_irq; + + imx_usb->set_config = 0; + + /* Config setup */ + if (imx_usb->cfg != cfg) { + D_REQ(imx_usb->dev, "<%s> Change config start\n",__func__); + u.bRequest = USB_REQ_SET_CONFIGURATION; + u.bRequestType = USB_DIR_OUT | + USB_TYPE_STANDARD | + USB_RECIP_DEVICE; + u.wValue = cfg; + u.wIndex = 0; + u.wLength = 0; + imx_usb->cfg = cfg; + imx_usb->set_config = 1; + imx_usb->driver->setup(&imx_usb->gadget, &u); + imx_usb->set_config = 0; + D_REQ(imx_usb->dev, "<%s> Change config done\n",__func__); + + } + if (imx_usb->intf != intf || imx_usb->alt != alt) { + D_REQ(imx_usb->dev, "<%s> Change interface start\n",__func__); + u.bRequest = USB_REQ_SET_INTERFACE; + u.bRequestType = USB_DIR_OUT | + USB_TYPE_STANDARD | + USB_RECIP_INTERFACE; + u.wValue = alt; + u.wIndex = intf; + u.wLength = 0; + imx_usb->intf = intf; + imx_usb->alt = alt; + imx_usb->set_config = 1; + imx_usb->driver->setup(&imx_usb->gadget, &u); + imx_usb->set_config = 0; + D_REQ(imx_usb->dev, "<%s> Change interface done\n",__func__); + } + } + + if (intr & INTR_SOF) { + if (imx_usb->ep0state == EP0_IDLE) { + temp = __raw_readl(imx_usb->base + USB_CTRL); + __raw_writel(temp | CTRL_CMDOVER, imx_usb->base + USB_CTRL); + } + } + +end_irq: + __raw_writel(intr, imx_usb->base + USB_INTR); + return IRQ_HANDLED; +} + +static irqreturn_t imx_udc_ctrl_irq(int irq, void *dev) +{ + struct imx_udc_struct *imx_usb = dev; + int intr = __raw_readl(imx_usb->base + USB_EP_INTR(0)); + + dump_ep_intr(__func__, 0, intr, imx_usb->dev); + + if (!imx_usb->driver) { + __raw_writel(intr, imx_usb->base + USB_EP_INTR(0)); + return IRQ_HANDLED; + } + + /* DEVREQ IRQ has highest priority */ + if (intr & (EPINTR_DEVREQ | EPINTR_MDEVREQ)) + handle_ep0_devreq(imx_usb); + /* Seem i.MX is missing EOF interrupt sometimes. + * Therefore we monitor both EOF and FIFO_EMPTY interrups + * when transmiting, and both EOF and FIFO_FULL when + * receiving data. + */ + else if (intr & (EPINTR_EOF | EPINTR_FIFO_EMPTY | EPINTR_FIFO_FULL)) + handle_ep0(&imx_usb->imx_ep[0]); + + __raw_writel(intr, imx_usb->base + USB_EP_INTR(0)); + + return IRQ_HANDLED; +} + +static irqreturn_t imx_udc_bulk_irq(int irq, void *dev) +{ + struct imx_udc_struct *imx_usb = dev; + struct imx_ep_struct *imx_ep = &imx_usb->imx_ep[irq - USBD_INT0]; + int intr = __raw_readl(imx_usb->base + USB_EP_INTR(EP_NO(imx_ep))); + + dump_ep_intr(__func__, irq - USBD_INT0, intr, imx_usb->dev); + + if (!imx_usb->driver) { + __raw_writel(intr, imx_usb->base + USB_EP_INTR(EP_NO(imx_ep))); + return IRQ_HANDLED; + } + + handle_ep(imx_ep); + + __raw_writel(intr, imx_usb->base + USB_EP_INTR(EP_NO(imx_ep))); + + return IRQ_HANDLED; +} + +irq_handler_t intr_handler(int i) +{ + switch (i) { + case 0: + return imx_udc_ctrl_irq; + case 1: + case 2: + case 3: + case 4: + case 5: + return imx_udc_bulk_irq; + default: + return imx_udc_irq; + } +} + +/******************************************************************************* + * Static defined IMX UDC structure + ******************************************************************************* + */ + +static const struct usb_gadget_ops imx_udc_ops = { + .get_frame = imx_udc_get_frame, + .wakeup = imx_udc_wakeup, +}; + +static struct imx_udc_struct controller = { + .gadget = { + .ops = &imx_udc_ops, + .ep0 = &controller.imx_ep[0].ep, + .name = driver_name, + .dev = { + .bus_id = "gadget", + }, + }, + + .imx_ep[0] = { + .ep = { + .name = ep0name, + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = 0, + .bmAttributes = USB_ENDPOINT_XFER_CONTROL, + }, + .imx_ep[1] = { + .ep = { + .name = "ep1in-bulk", + .ops = &imx_ep_ops, + .maxpacket = 64, + }, + .imx_usb = &controller, + .fifosize = 64, + .bEndpointAddress = USB_DIR_IN | 1, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .imx_ep[2] = { + .ep = { + .name = "ep2out-bulk", + .ops = &imx_ep_ops, + .maxpacket = 64, + }, + .imx_usb = &controller, + .fifosize = 64, + .bEndpointAddress = USB_DIR_OUT | 2, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .imx_ep[3] = { + .ep = { + .name = "ep3out-bulk", + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = USB_DIR_OUT | 3, + .bmAttributes = USB_ENDPOINT_XFER_BULK, + }, + .imx_ep[4] = { + .ep = { + .name = "ep4in-int", + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = USB_DIR_IN | 4, + .bmAttributes = USB_ENDPOINT_XFER_INT, + }, + .imx_ep[5] = { + .ep = { + .name = "ep5out-int", + .ops = &imx_ep_ops, + .maxpacket = 32, + }, + .imx_usb = &controller, + .fifosize = 32, + .bEndpointAddress = USB_DIR_OUT | 5, + .bmAttributes = USB_ENDPOINT_XFER_INT, + }, +}; + +/******************************************************************************* + * USB gadged driver functions + ******************************************************************************* + */ +int usb_gadget_register_driver(struct usb_gadget_driver *driver) +{ + struct imx_udc_struct *imx_usb = &controller; + int retval; + + if (!driver + || driver->speed < USB_SPEED_FULL + || !driver->bind + || !driver->disconnect + || !driver->setup) + return -EINVAL; + if (!imx_usb) + return -ENODEV; + if (imx_usb->driver) + return -EBUSY; + + /* first hook up the driver ... */ + imx_usb->driver = driver; + imx_usb->gadget.dev.driver = &driver->driver; + + retval = device_add(&imx_usb->gadget.dev); + if (retval) + goto fail; + retval = driver->bind(&imx_usb->gadget); + if (retval) { + D_ERR(imx_usb->dev, "<%s> bind to driver %s --> error %d\n", + __func__, driver->driver.name, retval); + device_del(&imx_usb->gadget.dev); + + goto fail; + } + + D_INI(imx_usb->dev, "<%s> registered gadget driver '%s'\n", + __func__, driver->driver.name); + + imx_udc_enable(imx_usb); + + return 0; +fail: + imx_usb->driver = NULL; + imx_usb->gadget.dev.driver = NULL; + return retval; +} +EXPORT_SYMBOL(usb_gadget_register_driver); + +int usb_gadget_unregister_driver(struct usb_gadget_driver *driver) +{ + struct imx_udc_struct *imx_usb = &controller; + + if (!imx_usb) + return -ENODEV; + if (!driver || driver != imx_usb->driver || !driver->unbind) + return -EINVAL; + + udc_stop_activity(imx_usb, driver); + imx_udc_disable(imx_usb); + + driver->unbind(&imx_usb->gadget); + imx_usb->gadget.dev.driver = NULL; + imx_usb->driver = NULL; + + device_del(&imx_usb->gadget.dev); + + D_INI(imx_usb->dev, "<%s> unregistered gadget driver '%s'\n", + __func__, driver->driver.name); + + return 0; +} +EXPORT_SYMBOL(usb_gadget_unregister_driver); + +/******************************************************************************* + * Module functions + ******************************************************************************* + */ + +static int __init imx_udc_probe(struct platform_device *pdev) +{ + struct imx_udc_struct *imx_usb = &controller; + struct resource *res; + struct imxusb_platform_data *pdata; + struct clk *clk; + void __iomem *base; + int ret = 0; + int i, res_size; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "can't get device resources\n"); + return -ENODEV; + } + + pdata = pdev->dev.platform_data; + if (!pdata) { + dev_err(&pdev->dev, "driver needs platform data\n"); + return -ENODEV; + } + + res_size = res->end - res->start + 1; + if (!request_mem_region(res->start, res_size, res->name)) { + dev_err(&pdev->dev, "can't allocate %d bytes at %d address\n", + res_size, res->start); + return -ENOMEM; + } + + if (pdata->init) { + ret = pdata->init(&pdev->dev); + if (ret) + goto fail0; + } + + base = ioremap(res->start, res_size); + if (!base) { + dev_err(&pdev->dev, "ioremap failed\n"); + ret = -EIO; + goto fail1; + } + + clk = clk_get(NULL, "usbd_clk"); + if (IS_ERR(clk)) { + ret = PTR_ERR(clk); + dev_err(&pdev->dev, "can't get USB clock\n"); + goto fail2; + } + clk_enable(clk); + + if (clk_get_rate(clk) != 48000000) { + D_INI(&pdev->dev, + "Bad USB clock (%d Hz), changing to 48000000 Hz\n", + (int)clk_get_rate(clk)); + if (clk_set_rate(clk, 48000000)) { + dev_err(&pdev->dev, + "Unable to set correct USB clock (48MHz)\n"); + ret = -EIO; + goto fail3; + } + } + + for (i = 0; i < IMX_USB_NB_EP + 1; i++) { + imx_usb->usbd_int[i] = platform_get_irq(pdev, i); + if (imx_usb->usbd_int[i] < 0) { + dev_err(&pdev->dev, "can't get irq number\n"); + ret = -ENODEV; + goto fail3; + } + } + + for (i = 0; i < IMX_USB_NB_EP + 1; i++) { + ret = request_irq(imx_usb->usbd_int[i], intr_handler(i), + IRQF_DISABLED, driver_name, imx_usb); + if (ret) { + dev_err(&pdev->dev, "can't get irq %i, err %d\n", + imx_usb->usbd_int[i], ret); + for (--i; i >= 0; i--) + free_irq(imx_usb->usbd_int[i], imx_usb); + goto fail3; + } + } + + imx_usb->res = res; + imx_usb->base = base; + imx_usb->clk = clk; + imx_usb->dev = &pdev->dev; + + device_initialize(&imx_usb->gadget.dev); + + imx_usb->gadget.dev.parent = &pdev->dev; + imx_usb->gadget.dev.dma_mask = pdev->dev.dma_mask; + + platform_set_drvdata(pdev, imx_usb); + + usb_init_data(imx_usb); + imx_udc_init(imx_usb); + + return 0; + +fail3: + clk_put(clk); + clk_disable(clk); +fail2: + iounmap(base); +fail1: + if (pdata->exit) + pdata->exit(&pdev->dev); +fail0: + release_mem_region(res->start, res_size); + return ret; +} + +static int __exit imx_udc_remove(struct platform_device *pdev) +{ + struct imx_udc_struct *imx_usb = platform_get_drvdata(pdev); + struct imxusb_platform_data *pdata = pdev->dev.platform_data; + int i; + + imx_udc_disable(imx_usb); + + for (i = 0; i < IMX_USB_NB_EP + 1; i++) + free_irq(imx_usb->usbd_int[i], imx_usb); + + clk_put(imx_usb->clk); + clk_disable(imx_usb->clk); + iounmap(imx_usb->base); + + release_mem_region(imx_usb->res->start, + imx_usb->res->end - imx_usb->res->start + 1); + + if (pdata->exit) + pdata->exit(&pdev->dev); + + platform_set_drvdata(pdev, NULL); + + return 0; +} + +/*----------------------------------------------------------------------------*/ + +#ifdef CONFIG_PM +#define imx_udc_suspend NULL +#define imx_udc_resume NULL +#else +#define imx_udc_suspend NULL +#define imx_udc_resume NULL +#endif + +/*----------------------------------------------------------------------------*/ + +static struct platform_driver udc_driver = { + .driver = { + .name = driver_name, + .owner = THIS_MODULE, + }, + .remove = __exit_p(imx_udc_remove), + .suspend = imx_udc_suspend, + .resume = imx_udc_resume, +}; + +static int __init udc_init(void) +{ + return platform_driver_probe(&udc_driver, imx_udc_probe); +} +module_init(udc_init); + +static void __exit udc_exit(void) +{ + platform_driver_unregister(&udc_driver); +} +module_exit(udc_exit); + +MODULE_DESCRIPTION("IMX USB Device Controller driver"); +MODULE_AUTHOR("Darius Augulis <augulis.darius@gmail.com>"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:imx_udc"); diff --git a/drivers/usb/gadget/imx_udc.h b/drivers/usb/gadget/imx_udc.h new file mode 100644 index 000000000000..850076937d8d --- /dev/null +++ b/drivers/usb/gadget/imx_udc.h @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2005 Mike Lee(eemike@gmail.com) + * + * This udc driver is now under testing and code is based on pxa2xx_udc.h + * Please use it with your own risk! + * + * 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. + */ + +#ifndef __LINUX_USB_GADGET_IMX_H +#define __LINUX_USB_GADGET_IMX_H + +#include <linux/types.h> + +/* Helper macros */ +#define EP_NO(ep) ((ep->bEndpointAddress) & ~USB_DIR_IN) /* IN:1, OUT:0 */ +#define EP_DIR(ep) ((ep->bEndpointAddress) & USB_DIR_IN ? 1 : 0) +#define irq_to_ep(irq) (((irq) >= USBD_INT0) || ((irq) <= USBD_INT6) ? ((irq) - USBD_INT0) : (USBD_INT6)) /*should not happen*/ +#define ep_to_irq(ep) (EP_NO((ep)) + USBD_INT0) +#define IMX_USB_NB_EP 6 + +/* Driver structures */ +struct imx_request { + struct usb_request req; + struct list_head queue; + unsigned int in_use; +}; + +enum ep0_state { + EP0_IDLE, + EP0_IN_DATA_PHASE, + EP0_OUT_DATA_PHASE, + EP0_CONFIG, + EP0_STALL, +}; + +struct imx_ep_struct { + struct usb_ep ep; + struct imx_udc_struct *imx_usb; + struct list_head queue; + unsigned char stopped; + unsigned char fifosize; + unsigned char bEndpointAddress; + unsigned char bmAttributes; +}; + +struct imx_udc_struct { + struct usb_gadget gadget; + struct usb_gadget_driver *driver; + struct device *dev; + struct imx_ep_struct imx_ep[IMX_USB_NB_EP]; + struct clk *clk; + enum ep0_state ep0state; + struct resource *res; + void __iomem *base; + unsigned char set_config; + int cfg, + intf, + alt, + usbd_int[7]; +}; + +/* USB registers */ +#define USB_FRAME (0x00) /* USB frame */ +#define USB_SPEC (0x04) /* USB Spec */ +#define USB_STAT (0x08) /* USB Status */ +#define USB_CTRL (0x0C) /* USB Control */ +#define USB_DADR (0x10) /* USB Desc RAM addr */ +#define USB_DDAT (0x14) /* USB Desc RAM/EP buffer data */ +#define USB_INTR (0x18) /* USB interrupt */ +#define USB_MASK (0x1C) /* USB Mask */ +#define USB_ENAB (0x24) /* USB Enable */ +#define USB_EP_STAT(x) (0x30 + (x*0x30)) /* USB status/control */ +#define USB_EP_INTR(x) (0x34 + (x*0x30)) /* USB interrupt */ +#define USB_EP_MASK(x) (0x38 + (x*0x30)) /* USB mask */ +#define USB_EP_FDAT(x) (0x3C + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT0(x) (0x3C + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT1(x) (0x3D + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT2(x) (0x3E + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FDAT3(x) (0x3F + (x*0x30)) /* USB FIFO data */ +#define USB_EP_FSTAT(x) (0x40 + (x*0x30)) /* USB FIFO status */ +#define USB_EP_FCTRL(x) (0x44 + (x*0x30)) /* USB FIFO control */ +#define USB_EP_LRFP(x) (0x48 + (x*0x30)) /* USB last read frame pointer */ +#define USB_EP_LWFP(x) (0x4C + (x*0x30)) /* USB last write frame pointer */ +#define USB_EP_FALRM(x) (0x50 + (x*0x30)) /* USB FIFO alarm */ +#define USB_EP_FRDP(x) (0x54 + (x*0x30)) /* USB FIFO read pointer */ +#define USB_EP_FWRP(x) (0x58 + (x*0x30)) /* USB FIFO write pointer */ +/* USB Control Register Bit Fields.*/ +#define CTRL_CMDOVER (1<<6) /* UDC status */ +#define CTRL_CMDERROR (1<<5) /* UDC status */ +#define CTRL_FE_ENA (1<<3) /* Enable Font End logic */ +#define CTRL_UDC_RST (1<<2) /* UDC reset */ +#define CTRL_AFE_ENA (1<<1) /* Analog Font end enable */ +#define CTRL_RESUME (1<<0) /* UDC resume */ +/* USB Status Register Bit Fields.*/ +#define STAT_RST (1<<8) +#define STAT_SUSP (1<<7) +#define STAT_CFG (3<<5) +#define STAT_INTF (3<<3) +#define STAT_ALTSET (7<<0) +/* USB Interrupt Status/Mask Registers Bit fields */ +#define INTR_WAKEUP (1<<31) /* Wake up Interrupt */ +#define INTR_MSOF (1<<7) /* Missed Start of Frame */ +#define INTR_SOF (1<<6) /* Start of Frame */ +#define INTR_RESET_STOP (1<<5) /* Reset Signaling stop */ +#define INTR_RESET_START (1<<4) /* Reset Signaling start */ +#define INTR_RESUME (1<<3) /* Suspend to resume */ +#define INTR_SUSPEND (1<<2) /* Active to suspend */ +#define INTR_FRAME_MATCH (1<<1) /* Frame matched */ +#define INTR_CFG_CHG (1<<0) /* Configuration change occurred */ +/* USB Enable Register Bit Fields.*/ +#define ENAB_RST (1<<31) /* Reset USB modules */ +#define ENAB_ENAB (1<<30) /* Enable USB modules*/ +#define ENAB_SUSPEND (1<<29) /* Suspend USB modules */ +#define ENAB_ENDIAN (1<<28) /* Endian of USB modules */ +#define ENAB_PWRMD (1<<0) /* Power mode of USB modules */ +/* USB Descriptor Ram Address Register bit fields */ +#define DADR_CFG (1<<31) /* Configuration */ +#define DADR_BSY (1<<30) /* Busy status */ +#define DADR_DADR (0x1FF) /* Descriptor Ram Address */ +/* USB Descriptor RAM/Endpoint Buffer Data Register bit fields */ +#define DDAT_DDAT (0xFF) /* Descriptor Endpoint Buffer */ +/* USB Endpoint Status Register bit fields */ +#define EPSTAT_BCOUNT (0x7F<<16) /* Endpoint FIFO byte count */ +#define EPSTAT_SIP (1<<8) /* Endpoint setup in progress */ +#define EPSTAT_DIR (1<<7) /* Endpoint transfer direction */ +#define EPSTAT_MAX (3<<5) /* Endpoint Max packet size */ +#define EPSTAT_TYP (3<<3) /* Endpoint type */ +#define EPSTAT_ZLPS (1<<2) /* Send zero length packet */ +#define EPSTAT_FLUSH (1<<1) /* Endpoint FIFO Flush */ +#define EPSTAT_STALL (1<<0) /* Force stall */ +/* USB Endpoint FIFO Status Register bit fields */ +#define FSTAT_FRAME_STAT (0xF<<24) /* Frame status bit [0-3] */ +#define FSTAT_ERR (1<<22) /* FIFO error */ +#define FSTAT_UF (1<<21) /* FIFO underflow */ +#define FSTAT_OF (1<<20) /* FIFO overflow */ +#define FSTAT_FR (1<<19) /* FIFO frame ready */ +#define FSTAT_FULL (1<<18) /* FIFO full */ +#define FSTAT_ALRM (1<<17) /* FIFO alarm */ +#define FSTAT_EMPTY (1<<16) /* FIFO empty */ +/* USB Endpoint FIFO Control Register bit fields */ +#define FCTRL_WFR (1<<29) /* Write frame end */ +/* USB Endpoint Interrupt Status Regsiter bit fields */ +#define EPINTR_FIFO_FULL (1<<8) /* fifo full */ +#define EPINTR_FIFO_EMPTY (1<<7) /* fifo empty */ +#define EPINTR_FIFO_ERROR (1<<6) /* fifo error */ +#define EPINTR_FIFO_HIGH (1<<5) /* fifo high */ +#define EPINTR_FIFO_LOW (1<<4) /* fifo low */ +#define EPINTR_MDEVREQ (1<<3) /* multi Device request */ +#define EPINTR_EOT (1<<2) /* fifo end of transfer */ +#define EPINTR_DEVREQ (1<<1) /* Device request */ +#define EPINTR_EOF (1<<0) /* fifo end of frame */ + +/* Debug macros */ +#ifdef DEBUG + +/* #define DEBUG_REQ */ +/* #define DEBUG_TRX */ +/* #define DEBUG_INIT */ +/* #define DEBUG_EP0 */ +/* #define DEBUG_EPX */ +/* #define DEBUG_IRQ */ +/* #define DEBUG_EPIRQ */ +/* #define DEBUG_DUMP */ +#define DEBUG_ERR + +#ifdef DEBUG_REQ + #define D_REQ(dev, args...) dev_dbg(dev, ## args) +#else + #define D_REQ(dev, args...) do {} while (0) +#endif /* DEBUG_REQ */ + +#ifdef DEBUG_TRX + #define D_TRX(dev, args...) dev_dbg(dev, ## args) +#else + #define D_TRX(dev, args...) do {} while (0) +#endif /* DEBUG_TRX */ + +#ifdef DEBUG_INIT + #define D_INI(dev, args...) dev_dbg(dev, ## args) +#else + #define D_INI(dev, args...) do {} while (0) +#endif /* DEBUG_INIT */ + +#ifdef DEBUG_EP0 + static const char *state_name[] = { + "EP0_IDLE", + "EP0_IN_DATA_PHASE", + "EP0_OUT_DATA_PHASE", + "EP0_CONFIG", + "EP0_STALL" + }; + #define D_EP0(dev, args...) dev_dbg(dev, ## args) +#else + #define D_EP0(dev, args...) do {} while (0) +#endif /* DEBUG_EP0 */ + +#ifdef DEBUG_EPX + #define D_EPX(dev, args...) dev_dbg(dev, ## args) +#else + #define D_EPX(dev, args...) do {} while (0) +#endif /* DEBUG_EP0 */ + +#ifdef DEBUG_IRQ + static void dump_intr(const char *label, int irqreg, struct device *dev) + { + dev_dbg(dev, "<%s> USB_INTR=[%s%s%s%s%s%s%s%s%s]\n", label, + (irqreg & INTR_WAKEUP) ? " wake" : "", + (irqreg & INTR_MSOF) ? " msof" : "", + (irqreg & INTR_SOF) ? " sof" : "", + (irqreg & INTR_RESUME) ? " resume" : "", + (irqreg & INTR_SUSPEND) ? " suspend" : "", + (irqreg & INTR_RESET_STOP) ? " noreset" : "", + (irqreg & INTR_RESET_START) ? " reset" : "", + (irqreg & INTR_FRAME_MATCH) ? " fmatch" : "", + (irqreg & INTR_CFG_CHG) ? " config" : ""); + } +#else + #define dump_intr(x, y, z) do {} while (0) +#endif /* DEBUG_IRQ */ + +#ifdef DEBUG_EPIRQ + static void dump_ep_intr(const char *label, int nr, int irqreg, struct device *dev) + { + dev_dbg(dev, "<%s> EP%d_INTR=[%s%s%s%s%s%s%s%s%s]\n", label, nr, + (irqreg & EPINTR_FIFO_FULL) ? " full" : "", + (irqreg & EPINTR_FIFO_EMPTY) ? " fempty" : "", + (irqreg & EPINTR_FIFO_ERROR) ? " ferr" : "", + (irqreg & EPINTR_FIFO_HIGH) ? " fhigh" : "", + (irqreg & EPINTR_FIFO_LOW) ? " flow" : "", + (irqreg & EPINTR_MDEVREQ) ? " mreq" : "", + (irqreg & EPINTR_EOF) ? " eof" : "", + (irqreg & EPINTR_DEVREQ) ? " devreq" : "", + (irqreg & EPINTR_EOT) ? " eot" : ""); + } +#else + #define dump_ep_intr(x, y, z, i) do {} while (0) +#endif /* DEBUG_IRQ */ + +#ifdef DEBUG_DUMP + static void dump_usb_stat(const char *label, struct imx_udc_struct *imx_usb) + { + int temp = __raw_readl(imx_usb->base + USB_STAT); + + dev_dbg(imx_usb->dev, + "<%s> USB_STAT=[%s%s CFG=%d, INTF=%d, ALTR=%d]\n", label, + (temp & STAT_RST) ? " reset" : "", + (temp & STAT_SUSP) ? " suspend" : "", + (temp & STAT_CFG) >> 5, + (temp & STAT_INTF) >> 3, + (temp & STAT_ALTSET)); + } + + static void dump_ep_stat(const char *label, struct imx_ep_struct *imx_ep) + { + int temp = __raw_readl(imx_ep->imx_usb->base + USB_EP_INTR(EP_NO(imx_ep))); + + dev_dbg(imx_ep->imx_usb->dev, + "<%s> EP%d_INTR=[%s%s%s%s%s%s%s%s%s]\n", label, EP_NO(imx_ep), + (temp & EPINTR_FIFO_FULL) ? " full" : "", + (temp & EPINTR_FIFO_EMPTY) ? " fempty" : "", + (temp & EPINTR_FIFO_ERROR) ? " ferr" : "", + (temp & EPINTR_FIFO_HIGH) ? " fhigh" : "", + (temp & EPINTR_FIFO_LOW) ? " flow" : "", + (temp & EPINTR_MDEVREQ) ? " mreq" : "", + (temp & EPINTR_EOF) ? " eof" : "", + (temp & EPINTR_DEVREQ) ? " devreq" : "", + (temp & EPINTR_EOT) ? " eot" : ""); + + temp = __raw_readl(imx_ep->imx_usb->base + USB_EP_STAT(EP_NO(imx_ep))); + + dev_dbg(imx_ep->imx_usb->dev, + "<%s> EP%d_STAT=[%s%s bcount=%d]\n", label, EP_NO(imx_ep), + (temp & EPSTAT_SIP) ? " sip" : "", + (temp & EPSTAT_STALL) ? " stall" : "", + (temp & EPSTAT_BCOUNT) >> 16); + + temp = __raw_readl(imx_ep->imx_usb->base + USB_EP_FSTAT(EP_NO(imx_ep))); + + dev_dbg(imx_ep->imx_usb->dev, + "<%s> EP%d_FSTAT=[%s%s%s%s%s%s%s]\n", label, EP_NO(imx_ep), + (temp & FSTAT_ERR) ? " ferr" : "", + (temp & FSTAT_UF) ? " funder" : "", + (temp & FSTAT_OF) ? " fover" : "", + (temp & FSTAT_FR) ? " fready" : "", + (temp & FSTAT_FULL) ? " ffull" : "", + (temp & FSTAT_ALRM) ? " falarm" : "", + (temp & FSTAT_EMPTY) ? " fempty" : ""); + } + + static void dump_req(const char *label, struct imx_ep_struct *imx_ep, struct usb_request *req) + { + int i; + + if (!req || !req->buf) { + dev_dbg(imx_ep->imx_usb->dev, "<%s> req or req buf is free\n", label); + return; + } + + if ((!EP_NO(imx_ep) && imx_ep->imx_usb->ep0state == EP0_IN_DATA_PHASE) + || (EP_NO(imx_ep) && EP_DIR(imx_ep))) { + + dev_dbg(imx_ep->imx_usb->dev, "<%s> request dump <", label); + for (i = 0; i < req->length; i++) + printk("%02x-", *((u8 *)req->buf + i)); + printk(">\n"); + } + } + +#else + #define dump_ep_stat(x, y) do {} while (0) + #define dump_usb_stat(x, y) do {} while (0) + #define dump_req(x, y, z) do {} while (0) +#endif /* DEBUG_DUMP */ + +#ifdef DEBUG_ERR + #define D_ERR(dev, args...) dev_dbg(dev, ## args) +#else + #define D_ERR(dev, args...) do {} while (0) +#endif + +#else + #define D_REQ(dev, args...) do {} while (0) + #define D_TRX(dev, args...) do {} while (0) + #define D_INI(dev, args...) do {} while (0) + #define D_EP0(dev, args...) do {} while (0) + #define D_EPX(dev, args...) do {} while (0) + #define dump_ep_intr(x, y, z, i) do {} while (0) + #define dump_intr(x, y, z) do {} while (0) + #define dump_ep_stat(x, y) do {} while (0) + #define dump_usb_stat(x, y) do {} while (0) + #define dump_req(x, y, z) do {} while (0) + #define D_ERR(dev, args...) do {} while (0) +#endif /* DEBUG */ + +#endif /* __LINUX_USB_GADGET_IMX_H */ diff --git a/drivers/usb/gadget/m66592-udc.c b/drivers/usb/gadget/m66592-udc.c index 3a8879ec2061..43dcf9e1af6b 100644 --- a/drivers/usb/gadget/m66592-udc.c +++ b/drivers/usb/gadget/m66592-udc.c @@ -1546,8 +1546,6 @@ static void nop_completion(struct usb_ep *ep, struct usb_request *r) { } -#define resource_len(r) (((r)->end - (r)->start) + 1) - static int __init m66592_probe(struct platform_device *pdev) { struct resource *res; @@ -1560,11 +1558,10 @@ static int __init m66592_probe(struct platform_device *pdev) int ret = 0; int i; - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - (char *)udc_name); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENODEV; - pr_err("platform_get_resource_byname error.\n"); + pr_err("platform_get_resource error.\n"); goto clean_up; } @@ -1575,7 +1572,7 @@ static int __init m66592_probe(struct platform_device *pdev) goto clean_up; } - reg = ioremap(res->start, resource_len(res)); + reg = ioremap(res->start, resource_size(res)); if (reg == NULL) { ret = -ENOMEM; pr_err("ioremap error.\n"); diff --git a/drivers/usb/gadget/net2280.c b/drivers/usb/gadget/net2280.c index 8ae70de2c37d..12c6d83b218c 100644 --- a/drivers/usb/gadget/net2280.c +++ b/drivers/usb/gadget/net2280.c @@ -669,7 +669,7 @@ fill_dma_desc (struct net2280_ep *ep, struct net2280_request *req, int valid) /* 2280 may be polling VALID_BIT through ep->dma->dmadesc */ wmb (); - td->dmacount = cpu_to_le32p (&dmacount); + td->dmacount = cpu_to_le32(dmacount); } static const u32 dmactl_default = diff --git a/drivers/usb/gadget/omap_udc.c b/drivers/usb/gadget/omap_udc.c index 34e9e393f929..57d9641c6bf8 100644 --- a/drivers/usb/gadget/omap_udc.c +++ b/drivers/usb/gadget/omap_udc.c @@ -3006,7 +3006,7 @@ cleanup1: cleanup0: if (xceiv) - put_device(xceiv->dev); + otg_put_transceiver(xceiv); if (cpu_is_omap16xx() || cpu_is_omap24xx()) { clk_disable(hhc_clk); @@ -3034,7 +3034,7 @@ static int __exit omap_udc_remove(struct platform_device *pdev) pullup_disable(udc); if (udc->transceiver) { - put_device(udc->transceiver->dev); + otg_put_transceiver(udc->transceiver); udc->transceiver = NULL; } omap_writew(0, UDC_SYSCON1); diff --git a/drivers/usb/gadget/pxa25x_udc.c b/drivers/usb/gadget/pxa25x_udc.c index 697a0ca349bf..9b36205c5759 100644 --- a/drivers/usb/gadget/pxa25x_udc.c +++ b/drivers/usb/gadget/pxa25x_udc.c @@ -2198,7 +2198,7 @@ static int __init pxa25x_udc_probe(struct platform_device *pdev) udc_disable(dev); udc_reinit(dev); - dev->vbus = is_vbus_present(); + dev->vbus = !!is_vbus_present(); /* irq setup after old hardware state is cleaned up */ retval = request_irq(irq, pxa25x_udc_irq, diff --git a/drivers/usb/gadget/pxa27x_udc.c b/drivers/usb/gadget/pxa27x_udc.c index 65110d02a206..990f40f988d4 100644 --- a/drivers/usb/gadget/pxa27x_udc.c +++ b/drivers/usb/gadget/pxa27x_udc.c @@ -430,7 +430,6 @@ static void pio_irq_enable(struct pxa_ep *ep) /** * pio_irq_disable - Disables irq generation for one endpoint * @ep: udc endpoint - * @index: endpoint number */ static void pio_irq_disable(struct pxa_ep *ep) { @@ -586,7 +585,6 @@ static void inc_ep_stats_reqs(struct pxa_ep *ep, int is_in) * inc_ep_stats_bytes - Update ep stats counts * @ep: physical endpoint * @count: bytes transfered on endpoint - * @req: usb request * @is_in: ep direction (USB_DIR_IN or 0) */ static void inc_ep_stats_bytes(struct pxa_ep *ep, int count, int is_in) diff --git a/drivers/usb/gadget/s3c2410_udc.c b/drivers/usb/gadget/s3c2410_udc.c index c7e255636803..9a2b8920532d 100644 --- a/drivers/usb/gadget/s3c2410_udc.c +++ b/drivers/usb/gadget/s3c2410_udc.c @@ -36,6 +36,7 @@ #include <linux/interrupt.h> #include <linux/platform_device.h> #include <linux/clk.h> +#include <linux/gpio.h> #include <linux/debugfs.h> #include <linux/seq_file.h> @@ -51,7 +52,6 @@ #include <mach/irqs.h> #include <mach/hardware.h> -#include <mach/regs-gpio.h> #include <plat/regs-udc.h> #include <plat/udc.h> @@ -1510,11 +1510,7 @@ static irqreturn_t s3c2410_udc_vbus_irq(int irq, void *_dev) dprintk(DEBUG_NORMAL, "%s()\n", __func__); - /* some cpus cannot read from an line configured to IRQ! */ - s3c2410_gpio_cfgpin(udc_info->vbus_pin, S3C2410_GPIO_INPUT); - value = s3c2410_gpio_getpin(udc_info->vbus_pin); - s3c2410_gpio_cfgpin(udc_info->vbus_pin, S3C2410_GPIO_SFN2); - + value = gpio_get_value(udc_info->vbus_pin) ? 1 : 0; if (udc_info->vbus_pin_inverted) value = !value; @@ -1802,7 +1798,7 @@ static int s3c2410_udc_probe(struct platform_device *pdev) struct s3c2410_udc *udc = &memory; struct device *dev = &pdev->dev; int retval; - unsigned int irq; + int irq; dev_dbg(dev, "%s()\n", __func__); @@ -1861,7 +1857,7 @@ static int s3c2410_udc_probe(struct platform_device *pdev) /* irq setup after old hardware state is cleaned up */ retval = request_irq(IRQ_USBD, s3c2410_udc_irq, - IRQF_DISABLED, gadget_name, udc); + IRQF_DISABLED, gadget_name, udc); if (retval != 0) { dev_err(dev, "cannot get irq %i, err %d\n", IRQ_USBD, retval); @@ -1872,17 +1868,28 @@ static int s3c2410_udc_probe(struct platform_device *pdev) dev_dbg(dev, "got irq %i\n", IRQ_USBD); if (udc_info && udc_info->vbus_pin > 0) { - irq = s3c2410_gpio_getirq(udc_info->vbus_pin); + retval = gpio_request(udc_info->vbus_pin, "udc vbus"); + if (retval < 0) { + dev_err(dev, "cannot claim vbus pin\n"); + goto err_int; + } + + irq = gpio_to_irq(udc_info->vbus_pin); + if (irq < 0) { + dev_err(dev, "no irq for gpio vbus pin\n"); + goto err_gpio_claim; + } + retval = request_irq(irq, s3c2410_udc_vbus_irq, IRQF_DISABLED | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING | IRQF_SHARED, gadget_name, udc); if (retval != 0) { - dev_err(dev, "can't get vbus irq %i, err %d\n", + dev_err(dev, "can't get vbus irq %d, err %d\n", irq, retval); retval = -EBUSY; - goto err_int; + goto err_gpio_claim; } dev_dbg(dev, "got irq %i\n", irq); @@ -1902,6 +1909,9 @@ static int s3c2410_udc_probe(struct platform_device *pdev) return 0; +err_gpio_claim: + if (udc_info && udc_info->vbus_pin > 0) + gpio_free(udc_info->vbus_pin); err_int: free_irq(IRQ_USBD, udc); err_map: @@ -1927,7 +1937,7 @@ static int s3c2410_udc_remove(struct platform_device *pdev) debugfs_remove(udc->regs_info); if (udc_info && udc_info->vbus_pin > 0) { - irq = s3c2410_gpio_getirq(udc_info->vbus_pin); + irq = gpio_to_irq(udc_info->vbus_pin); free_irq(irq, udc); } diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index f3a75a929e0a..2b476b6b3d4d 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -96,6 +96,19 @@ config USB_EHCI_HCD_PPC_OF Enables support for the USB controller present on the PowerPC OpenFirmware platform bus. +config USB_OXU210HP_HCD + tristate "OXU210HP HCD support" + depends on USB + ---help--- + The OXU210HP is an USB host/OTG/device controller. Enable this + option if your board has this chip. If unsure, say N. + + This driver does not support isochronous transfers and doesn't + implement OTG nor USB device controllers. + + To compile this driver as a module, choose M here: the + module will be called oxu210hp-hcd. + config USB_ISP116X_HCD tristate "ISP116X HCD support" depends on USB diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 23be22224044..e5f3f20787e4 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_USB_WHCI_HCD) += whci/ obj-$(CONFIG_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o +obj-$(CONFIG_USB_OXU210HP_HCD) += oxu210hp-hcd.o obj-$(CONFIG_USB_ISP116X_HCD) += isp116x-hcd.o obj-$(CONFIG_USB_OHCI_HCD) += ohci-hcd.o obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 0cb53ca8d343..7f4ace73d44a 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -455,9 +455,7 @@ static void qh_lines ( (scratch >> 16) & 0x7fff, scratch, td->urb); - if (temp < 0) - temp = 0; - else if (size < temp) + if (size < temp) temp = size; size -= temp; next += temp; @@ -466,9 +464,7 @@ static void qh_lines ( } temp = snprintf (next, size, "\n"); - if (temp < 0) - temp = 0; - else if (size < temp) + if (size < temp) temp = size; size -= temp; next += temp; diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 218f9660d7ee..97a53a48a3d8 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -194,6 +194,7 @@ static int ehci_bus_resume (struct usb_hcd *hcd) u32 temp; u32 power_okay; int i; + u8 resume_needed = 0; if (time_before (jiffies, ehci->next_statechange)) msleep(5); @@ -228,7 +229,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) /* Some controller/firmware combinations need a delay during which * they set up the port statuses. See Bugzilla #8190. */ - mdelay(8); + spin_unlock_irq(&ehci->lock); + msleep(8); + spin_lock_irq(&ehci->lock); /* manually resume the ports we suspended during bus_suspend() */ i = HCS_N_PORTS (ehci->hcs_params); @@ -236,12 +239,21 @@ static int ehci_bus_resume (struct usb_hcd *hcd) temp = ehci_readl(ehci, &ehci->regs->port_status [i]); temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); if (test_bit(i, &ehci->bus_suspended) && - (temp & PORT_SUSPEND)) + (temp & PORT_SUSPEND)) { temp |= PORT_RESUME; + resume_needed = 1; + } ehci_writel(ehci, temp, &ehci->regs->port_status [i]); } + + /* msleep for 20ms only if code is trying to resume port */ + if (resume_needed) { + spin_unlock_irq(&ehci->lock); + msleep(20); + spin_lock_irq(&ehci->lock); + } + i = HCS_N_PORTS (ehci->hcs_params); - mdelay (20); while (i--) { temp = ehci_readl(ehci, &ehci->regs->port_status [i]); if (test_bit(i, &ehci->bus_suspended) && @@ -422,8 +434,15 @@ static int check_reset_complete ( port_status &= ~PORT_RWC_BITS; ehci_writel(ehci, port_status, status_reg); - } else + /* ensure 440EPX ohci controller state is operational */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 1); + } else { ehci_dbg (ehci, "port %d high speed\n", index + 1); + /* ensure 440EPx ohci controller state is suspended */ + if (ehci->has_amcc_usb23) + set_ohci_hcfs(ehci, 0); + } return port_status; } diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 36864f958444..bdc6e86e1f8b 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -219,15 +219,19 @@ static int ehci_pci_setup(struct usb_hcd *hcd) /* Serial Bus Release Number is at PCI 0x60 offset */ pci_read_config_byte(pdev, 0x60, &ehci->sbrn); - /* Workaround current PCI init glitch: wakeup bits aren't - * being set from PCI PM capability. + /* Keep this around for a while just in case some EHCI + * implementation uses legacy PCI PM support. This test + * can be removed on 17 Dec 2009 if the dev_warn() hasn't + * been triggered by then. */ if (!device_can_wakeup(&pdev->dev)) { u16 port_wake; pci_read_config_word(pdev, 0x62, &port_wake); - if (port_wake & 0x0001) + if (port_wake & 0x0001) { + dev_warn(&pdev->dev, "Enabling legacy PCI PM\n"); device_init_wakeup(&pdev->dev, 1); + } } #ifdef CONFIG_USB_SUSPEND @@ -428,6 +432,8 @@ static struct pci_driver ehci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, + .suspend_late = usb_hcd_pci_suspend_late, + .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif .shutdown = usb_hcd_pci_shutdown, diff --git a/drivers/usb/host/ehci-ppc-of.c b/drivers/usb/host/ehci-ppc-of.c index b018deed2e8f..ef732b704f53 100644 --- a/drivers/usb/host/ehci-ppc-of.c +++ b/drivers/usb/host/ehci-ppc-of.c @@ -107,11 +107,13 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) { struct device_node *dn = op->node; struct usb_hcd *hcd; - struct ehci_hcd *ehci; + struct ehci_hcd *ehci = NULL; struct resource res; int irq; int rv; + struct device_node *np; + if (usb_disabled()) return -ENODEV; @@ -149,6 +151,20 @@ ehci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) } ehci = hcd_to_ehci(hcd); + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + /* claim we really affected by usb23 erratum */ + if (!of_address_to_resource(np, 0, &res)) + ehci->ohci_hcctrl_reg = ioremap(res.start + + OHCI_HCCTRL_OFFSET, OHCI_HCCTRL_LEN); + else + pr_debug(__FILE__ ": no ohci offset in fdt\n"); + if (!ehci->ohci_hcctrl_reg) { + pr_debug(__FILE__ ": ioremap for ohci hcctrl failed\n"); + } else { + ehci->has_amcc_usb23 = 1; + } + } if (of_get_property(dn, "big-endian", NULL)) { ehci->big_endian_mmio = 1; @@ -181,6 +197,9 @@ err_ioremap: irq_dispose_mapping(irq); err_irq: release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + + if (ehci->has_amcc_usb23) + iounmap(ehci->ohci_hcctrl_reg); err_rmr: usb_put_hcd(hcd); @@ -191,6 +210,11 @@ err_rmr: static int ehci_hcd_ppc_of_remove(struct of_device *op) { struct usb_hcd *hcd = dev_get_drvdata(&op->dev); + struct ehci_hcd *ehci = hcd_to_ehci(hcd); + + struct device_node *np; + struct resource res; + dev_set_drvdata(&op->dev, NULL); dev_dbg(&op->dev, "stopping PPC-OF USB Controller\n"); @@ -201,6 +225,25 @@ static int ehci_hcd_ppc_of_remove(struct of_device *op) irq_dispose_mapping(hcd->irq); release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + /* use request_mem_region to test if the ohci driver is loaded. if so + * ensure the ohci core is operational. + */ + if (ehci->has_amcc_usb23) { + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ohci-440epx"); + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) + if (!request_mem_region(res.start, + 0x4, hcd_name)) + set_ohci_hcfs(ehci, 1); + else + release_mem_region(res.start, 0x4); + else + pr_debug(__FILE__ ": no ohci offset in fdt\n"); + of_node_put(np); + } + + iounmap(ehci->ohci_hcctrl_reg); + } usb_put_hcd(hcd); return 0; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index c7d4b5a06bdb..fb7054ccf4fc 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -120,6 +120,16 @@ struct ehci_hcd { /* one per controller */ unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned big_endian_mmio:1; unsigned big_endian_desc:1; + unsigned has_amcc_usb23:1; + + /* required for usb32 quirk */ + #define OHCI_CTRL_HCFS (3 << 6) + #define OHCI_USB_OPER (2 << 6) + #define OHCI_USB_SUSPEND (3 << 6) + + #define OHCI_HCCTRL_OFFSET 0x4 + #define OHCI_HCCTRL_LEN 0x4 + __hc32 *ohci_hcctrl_reg; u8 sbrn; /* packed release number */ @@ -636,6 +646,30 @@ static inline void ehci_writel(const struct ehci_hcd *ehci, #endif } +/* + * On certain ppc-44x SoC there is a HW issue, that could only worked around with + * explicit suspend/operate of OHCI. This function hereby makes sense only on that arch. + * Other common bits are dependant on has_amcc_usb23 quirk flag. + */ +#ifdef CONFIG_44x +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ + u32 hc_control; + + hc_control = (readl_be(ehci->ohci_hcctrl_reg) & ~OHCI_CTRL_HCFS); + if (operational) + hc_control |= OHCI_USB_OPER; + else + hc_control |= OHCI_USB_SUSPEND; + + writel_be(hc_control, ehci->ohci_hcctrl_reg); + (void) readl_be(ehci->ohci_hcctrl_reg); +} +#else +static inline void set_ohci_hcfs(struct ehci_hcd *ehci, int operational) +{ } +#endif + /*-------------------------------------------------------------------------*/ /* diff --git a/drivers/usb/host/isp1760-hcd.c b/drivers/usb/host/isp1760-hcd.c index 8017f1cf78e2..b899f1a59c26 100644 --- a/drivers/usb/host/isp1760-hcd.c +++ b/drivers/usb/host/isp1760-hcd.c @@ -435,14 +435,13 @@ static int isp1760_hc_setup(struct usb_hcd *hcd) /* * PORT 1 Control register of the ISP1760 is the OTG control - * register on ISP1761. + * register on ISP1761. Since there is no OTG or device controller + * support in this driver, we use port 1 as a "normal" USB host port on + * both chips. */ - if (!(priv->devflags & ISP1760_FLAG_ISP1761) && - !(priv->devflags & ISP1760_FLAG_PORT1_DIS)) { - isp1760_writel(PORT1_POWER | PORT1_INIT2, - hcd->regs + HC_PORT1_CTRL); - mdelay(10); - } + isp1760_writel(PORT1_POWER | PORT1_INIT2, + hcd->regs + HC_PORT1_CTRL); + mdelay(10); priv->hcs_params = isp1760_readl(hcd->regs + HC_HCSPARAMS); diff --git a/drivers/usb/host/isp1760-hcd.h b/drivers/usb/host/isp1760-hcd.h index 4377277667d9..a9daea587962 100644 --- a/drivers/usb/host/isp1760-hcd.h +++ b/drivers/usb/host/isp1760-hcd.h @@ -135,7 +135,6 @@ typedef void (packet_enqueue)(struct usb_hcd *hcd, struct isp1760_qh *qh, * indicate the most "atypical" case, so that a devflags of 0 is * a sane default configuration. */ -#define ISP1760_FLAG_PORT1_DIS 0x00000001 /* Port 1 disabled */ #define ISP1760_FLAG_BUS_WIDTH_16 0x00000002 /* 16-bit data bus width */ #define ISP1760_FLAG_OTG_EN 0x00000004 /* Port 1 supports OTG */ #define ISP1760_FLAG_ANALOG_OC 0x00000008 /* Analog overcurrent */ diff --git a/drivers/usb/host/isp1760-if.c b/drivers/usb/host/isp1760-if.c index b87ca7cf4b37..4cf7ca428b33 100644 --- a/drivers/usb/host/isp1760-if.c +++ b/drivers/usb/host/isp1760-if.c @@ -60,9 +60,6 @@ static int of_isp1760_probe(struct of_device *dev, if (of_device_is_compatible(dp, "nxp,usb-isp1761")) devflags |= ISP1760_FLAG_ISP1761; - if (of_get_property(dp, "port1-disable", NULL) != NULL) - devflags |= ISP1760_FLAG_PORT1_DIS; - /* Some systems wire up only 16 of the 32 data lines */ prop = of_get_property(dp, "bus-width", NULL); if (prop && *prop == 16) @@ -129,23 +126,23 @@ static struct of_platform_driver isp1760_of_driver = { #endif #ifdef CONFIG_PCI -static u32 nxp_pci_io_base; -static u32 iolength; -static u32 pci_mem_phy0; -static u32 length; -static u8 __iomem *chip_addr; -static u8 __iomem *iobase; - static int __devinit isp1761_pci_probe(struct pci_dev *dev, const struct pci_device_id *id) { u8 latency, limit; __u32 reg_data; int retry_count; - int length; - int status = 1; struct usb_hcd *hcd; unsigned int devflags = 0; + int ret_status = 0; + + resource_size_t pci_mem_phy0; + resource_size_t memlength; + + u8 __iomem *chip_addr; + u8 __iomem *iobase; + resource_size_t nxp_pci_io_base; + resource_size_t iolength; if (usb_disabled()) return -ENODEV; @@ -168,26 +165,30 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev, iobase = ioremap_nocache(nxp_pci_io_base, iolength); if (!iobase) { printk(KERN_ERR "ioremap #1\n"); - release_mem_region(nxp_pci_io_base, iolength); - return -ENOMEM; + ret_status = -ENOMEM; + goto cleanup1; } /* Grab the PLX PCI shared memory of the ISP 1761 we need */ pci_mem_phy0 = pci_resource_start(dev, 3); - length = pci_resource_len(dev, 3); - - if (length < 0xffff) { - printk(KERN_ERR "memory length for this resource is less than " - "required\n"); - release_mem_region(nxp_pci_io_base, iolength); - iounmap(iobase); - return -ENOMEM; + memlength = pci_resource_len(dev, 3); + if (memlength < 0xffff) { + printk(KERN_ERR "memory length for this resource is wrong\n"); + ret_status = -ENOMEM; + goto cleanup2; } - if (!request_mem_region(pci_mem_phy0, length, "ISP-PCI")) { + if (!request_mem_region(pci_mem_phy0, memlength, "ISP-PCI")) { printk(KERN_ERR "host controller already in use\n"); - release_mem_region(nxp_pci_io_base, iolength); - iounmap(iobase); - return -EBUSY; + ret_status = -EBUSY; + goto cleanup2; + } + + /* map available memory */ + chip_addr = ioremap_nocache(pci_mem_phy0,memlength); + if (!chip_addr) { + printk(KERN_ERR "Error ioremap failed\n"); + ret_status = -ENOMEM; + goto cleanup3; } /* bad pci latencies can contribute to overruns */ @@ -210,39 +211,54 @@ static int __devinit isp1761_pci_probe(struct pci_dev *dev, * */ writel(0xface, chip_addr + HC_SCRATCH_REG); udelay(100); - reg_data = readl(chip_addr + HC_SCRATCH_REG); + reg_data = readl(chip_addr + HC_SCRATCH_REG) & 0x0000ffff; retry_count--; } + iounmap(chip_addr); + /* Host Controller presence is detected by writing to scratch register * and reading back and checking the contents are same or not */ if (reg_data != 0xFACE) { dev_err(&dev->dev, "scratch register mismatch %x\n", reg_data); - goto clean; + ret_status = -ENOMEM; + goto cleanup3; } pci_set_master(dev); - status = readl(iobase + 0x68); - status |= 0x900; - writel(status, iobase + 0x68); + /* configure PLX PCI chip to pass interrupts */ +#define PLX_INT_CSR_REG 0x68 + reg_data = readl(iobase + PLX_INT_CSR_REG); + reg_data |= 0x900; + writel(reg_data, iobase + PLX_INT_CSR_REG); dev->dev.dma_mask = NULL; - hcd = isp1760_register(pci_mem_phy0, length, dev->irq, + hcd = isp1760_register(pci_mem_phy0, memlength, dev->irq, IRQF_SHARED | IRQF_DISABLED, &dev->dev, dev_name(&dev->dev), devflags); - if (!IS_ERR(hcd)) { - pci_set_drvdata(dev, hcd); - return 0; + if (IS_ERR(hcd)) { + ret_status = -ENODEV; + goto cleanup3; } -clean: - status = -ENODEV; + + /* done with PLX IO access */ + iounmap(iobase); + release_mem_region(nxp_pci_io_base, iolength); + + pci_set_drvdata(dev, hcd); + return 0; + +cleanup3: + release_mem_region(pci_mem_phy0, memlength); +cleanup2: iounmap(iobase); - release_mem_region(pci_mem_phy0, length); +cleanup1: release_mem_region(nxp_pci_io_base, iolength); - return status; + return ret_status; } + static void isp1761_pci_remove(struct pci_dev *dev) { struct usb_hcd *hcd; @@ -255,12 +271,6 @@ static void isp1761_pci_remove(struct pci_dev *dev) usb_put_hcd(hcd); pci_disable_device(dev); - - iounmap(iobase); - iounmap(chip_addr); - - release_mem_region(nxp_pci_io_base, iolength); - release_mem_region(pci_mem_phy0, length); } static void isp1761_pci_shutdown(struct pci_dev *dev) @@ -268,12 +278,16 @@ static void isp1761_pci_shutdown(struct pci_dev *dev) printk(KERN_ERR "ips1761_pci_shutdown\n"); } -static const struct pci_device_id isp1760_plx [] = { { - /* handle any USB 2.0 EHCI controller */ - PCI_DEVICE_CLASS(((PCI_CLASS_BRIDGE_OTHER << 8) | (0x06 << 16)), ~0), - .driver_data = 0, -}, -{ /* end: all zeroes */ } +static const struct pci_device_id isp1760_plx [] = { + { + .class = PCI_CLASS_BRIDGE_OTHER << 8, + .class_mask = ~0, + .vendor = PCI_VENDOR_ID_PLX, + .device = 0x5406, + .subvendor = PCI_VENDOR_ID_PLX, + .subdevice = 0x9054, + }, + { } }; MODULE_DEVICE_TABLE(pci, isp1760_plx); diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index 8aa3f4556a32..65a9609f4ad6 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -589,13 +589,15 @@ static int ohci_run (struct ohci_hcd *ohci) /* also: power/overcurrent flags in roothub.a */ } - /* Reset USB nearly "by the book". RemoteWakeupConnected was - * saved if boot firmware (BIOS/SMM/...) told us it's connected, - * or if bus glue did the same (e.g. for PCI add-in cards with - * PCI PM support). + /* Reset USB nearly "by the book". RemoteWakeupConnected has + * to be checked in case boot firmware (BIOS/SMM/...) has set up + * wakeup in a way the bus isn't aware of (e.g., legacy PCI PM). + * If the bus glue detected wakeup capability then it should + * already be enabled. Either way, if wakeup should be enabled + * but isn't, we'll enable it now. */ if ((ohci->hc_control & OHCI_CTRL_RWC) != 0 - && !device_may_wakeup(hcd->self.controller)) + && !device_can_wakeup(hcd->self.controller)) device_init_wakeup(hcd->self.controller, 1); switch (ohci->hc_control & OHCI_CTRL_HCFS) { diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index a9c2ae36c7ad..8b28ae7865ba 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -355,9 +355,9 @@ static int __devinit ohci_pci_start (struct usb_hcd *hcd) /* RWC may not be set for add-in PCI cards, since boot * firmware probably ignored them. This transfers PCI - * PM wakeup capabilities (once the PCI layer is fixed). + * PM wakeup capabilities. */ - if (device_may_wakeup(&pdev->dev)) + if (device_can_wakeup(&pdev->dev)) ohci->hc_control |= OHCI_CTRL_RWC; } #endif /* CONFIG_PM */ @@ -487,6 +487,8 @@ static struct pci_driver ohci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, + .suspend_late = usb_hcd_pci_suspend_late, + .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif diff --git a/drivers/usb/host/ohci-pnx4008.c b/drivers/usb/host/ohci-pnx4008.c index e306ca6aef3d..100bf3d8437c 100644 --- a/drivers/usb/host/ohci-pnx4008.c +++ b/drivers/usb/host/ohci-pnx4008.c @@ -106,65 +106,34 @@ extern int ocpi_enable(void); static struct clk *usb_clk; -static int isp1301_probe(struct i2c_adapter *adap); -static int isp1301_detach(struct i2c_client *client); - static const unsigned short normal_i2c[] = { ISP1301_I2C_ADDR, ISP1301_I2C_ADDR + 1, I2C_CLIENT_END }; -static const unsigned short dummy_i2c_addrlist[] = { I2C_CLIENT_END }; - -static struct i2c_client_address_data addr_data = { - .normal_i2c = normal_i2c, - .probe = dummy_i2c_addrlist, - .ignore = dummy_i2c_addrlist, -}; - -struct i2c_driver isp1301_driver = { - .driver = { - .name = "isp1301_pnx", - }, - .attach_adapter = isp1301_probe, - .detach_client = isp1301_detach, -}; -static int isp1301_attach(struct i2c_adapter *adap, int addr, int kind) +static int isp1301_probe(struct i2c_client *client, + const struct i2c_device_id *id) { - struct i2c_client *c; - int err; - - c = kzalloc(sizeof(*c), GFP_KERNEL); - if (!c) - return -ENOMEM; - - strlcpy(c->name, "isp1301_pnx", I2C_NAME_SIZE); - c->flags = 0; - c->addr = addr; - c->adapter = adap; - c->driver = &isp1301_driver; - - err = i2c_attach_client(c); - if (err) { - kfree(c); - return err; - } - - isp1301_i2c_client = c; - return 0; } -static int isp1301_probe(struct i2c_adapter *adap) +static int isp1301_remove(struct i2c_client *client) { - return i2c_probe(adap, &addr_data, isp1301_attach); -} - -static int isp1301_detach(struct i2c_client *client) -{ - i2c_detach_client(client); - kfree(isp1301_i2c_client); return 0; } +const struct i2c_device_id isp1301_id[] = { + { "isp1301_pnx", 0 }, + { } +}; + +struct i2c_driver isp1301_driver = { + .driver = { + .name = "isp1301_pnx", + }, + .probe = isp1301_probe, + .remove = isp1301_remove, + .id_table = isp1301_id, +}; + static void i2c_write(u8 buf, u8 subaddr) { char tmpbuf[2]; @@ -328,6 +297,8 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev) struct usb_hcd *hcd = 0; struct ohci_hcd *ohci; const struct hc_driver *driver = &ohci_pnx4008_hc_driver; + struct i2c_adapter *i2c_adap; + struct i2c_board_info i2c_info; int ret = 0, irq; @@ -351,9 +322,20 @@ static int __devinit usb_hcd_pnx4008_probe(struct platform_device *pdev) ret = i2c_add_driver(&isp1301_driver); if (ret < 0) { - err("failed to connect I2C to ISP1301 USB Transceiver"); + err("failed to add ISP1301 driver"); goto out; } + i2c_adap = i2c_get_adapter(2); + memset(&i2c_info, 0, sizeof(struct i2c_board_info)); + strlcpy(i2c_info.name, "isp1301_pnx", I2C_NAME_SIZE); + isp1301_i2c_client = i2c_new_probed_device(i2c_adap, &i2c_info, + normal_i2c); + i2c_put_adapter(i2c_adap); + if (!isp1301_i2c_client) { + err("failed to connect I2C to ISP1301 USB Transceiver"); + ret = -ENODEV; + goto out_i2c_driver; + } isp1301_configure(); @@ -429,6 +411,9 @@ out3: out2: clk_put(usb_clk); out1: + i2c_unregister_client(isp1301_i2c_client); + isp1301_i2c_client = NULL; +out_i2c_driver: i2c_del_driver(&isp1301_driver); out: return ret; @@ -445,6 +430,8 @@ static int usb_hcd_pnx4008_remove(struct platform_device *pdev) pnx4008_unset_usb_bits(); clk_disable(usb_clk); clk_put(usb_clk); + i2c_unregister_client(isp1301_i2c_client); + isp1301_i2c_client = NULL; i2c_del_driver(&isp1301_driver); platform_set_drvdata(pdev, NULL); diff --git a/drivers/usb/host/ohci-ppc-of.c b/drivers/usb/host/ohci-ppc-of.c index 7ac53264ead3..68a301710297 100644 --- a/drivers/usb/host/ohci-ppc-of.c +++ b/drivers/usb/host/ohci-ppc-of.c @@ -91,6 +91,7 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) int rv; int is_bigendian; + struct device_node *np; if (usb_disabled()) return -ENODEV; @@ -147,6 +148,30 @@ ohci_hcd_ppc_of_probe(struct of_device *op, const struct of_device_id *match) if (rv == 0) return 0; + /* by now, 440epx is known to show usb_23 erratum */ + np = of_find_compatible_node(NULL, NULL, "ibm,usb-ehci-440epx"); + + /* Work around - At this point ohci_run has executed, the + * controller is running, everything, the root ports, etc., is + * set up. If the ehci driver is loaded, put the ohci core in + * the suspended state. The ehci driver will bring it out of + * suspended state when / if a non-high speed USB device is + * attached to the USB Host port. If the ehci driver is not + * loaded, do nothing. request_mem_region is used to test if + * the ehci driver is loaded. + */ + if (np != NULL) { + if (!of_address_to_resource(np, 0, &res)) { + if (!request_mem_region(res.start, 0x4, hcd_name)) { + writel_be((readl_be(&ohci->regs->control) | + OHCI_USB_SUSPEND), &ohci->regs->control); + (void) readl_be(&ohci->regs->control); + } else + release_mem_region(res.start, 0x4); + } else + pr_debug(__FILE__ ": cannot get ehci offset from fdt\n"); + } + iounmap(hcd->regs); err_ioremap: irq_dispose_mapping(irq); diff --git a/drivers/usb/host/ohci-tmio.c b/drivers/usb/host/ohci-tmio.c index f9f134af0bd1..8dabe8e31d8c 100644 --- a/drivers/usb/host/ohci-tmio.c +++ b/drivers/usb/host/ohci-tmio.c @@ -201,7 +201,7 @@ static int __devinit ohci_hcd_tmio_drv_probe(struct platform_device *dev) if (!cell) return -EINVAL; - hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev->dev.bus_id); + hcd = usb_create_hcd(&ohci_tmio_hc_driver, &dev->dev, dev_name(&dev->dev)); if (!hcd) { ret = -ENOMEM; goto err_usb_create_hcd; diff --git a/drivers/usb/host/oxu210hp-hcd.c b/drivers/usb/host/oxu210hp-hcd.c new file mode 100644 index 000000000000..75548f7c716b --- /dev/null +++ b/drivers/usb/host/oxu210hp-hcd.c @@ -0,0 +1,3985 @@ +/* + * Copyright (c) 2008 Rodolfo Giometti <giometti@linux.it> + * Copyright (c) 2008 Eurotech S.p.A. <info@eurtech.it> + * + * This code is *strongly* based on EHCI-HCD code by David Brownell since + * the chip is a quasi-EHCI compatible. + * + * 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/pci.h> +#include <linux/dmapool.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/reboot.h> +#include <linux/usb.h> +#include <linux/moduleparam.h> +#include <linux/dma-mapping.h> +#include <linux/io.h> + +#include "../core/hcd.h" + +#include <asm/irq.h> +#include <asm/system.h> +#include <asm/unaligned.h> + +#include <linux/irq.h> +#include <linux/platform_device.h> + +#include "oxu210hp.h" + +#define DRIVER_VERSION "0.0.50" + +/* + * Main defines + */ + +#define oxu_dbg(oxu, fmt, args...) \ + dev_dbg(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_err(oxu, fmt, args...) \ + dev_err(oxu_to_hcd(oxu)->self.controller , fmt , ## args) +#define oxu_info(oxu, fmt, args...) \ + dev_info(oxu_to_hcd(oxu)->self.controller , fmt , ## args) + +static inline struct usb_hcd *oxu_to_hcd(struct oxu_hcd *oxu) +{ + return container_of((void *) oxu, struct usb_hcd, hcd_priv); +} + +static inline struct oxu_hcd *hcd_to_oxu(struct usb_hcd *hcd) +{ + return (struct oxu_hcd *) (hcd->hcd_priv); +} + +/* + * Debug stuff + */ + +#undef OXU_URB_TRACE +#undef OXU_VERBOSE_DEBUG + +#ifdef OXU_VERBOSE_DEBUG +#define oxu_vdbg oxu_dbg +#else +#define oxu_vdbg(oxu, fmt, args...) /* Nop */ +#endif + +#ifdef DEBUG + +static int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ + return scnprintf(buf, len, "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", status, + (status & STS_ASS) ? " Async" : "", + (status & STS_PSS) ? " Periodic" : "", + (status & STS_RECL) ? " Recl" : "", + (status & STS_HALT) ? " Halt" : "", + (status & STS_IAA) ? " IAA" : "", + (status & STS_FATAL) ? " FATAL" : "", + (status & STS_FLR) ? " FLR" : "", + (status & STS_PCD) ? " PCD" : "", + (status & STS_ERR) ? " ERR" : "", + (status & STS_INT) ? " INT" : "" + ); +} + +static int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ + return scnprintf(buf, len, "%s%sintrenable %02x%s%s%s%s%s%s", + label, label[0] ? " " : "", enable, + (enable & STS_IAA) ? " IAA" : "", + (enable & STS_FATAL) ? " FATAL" : "", + (enable & STS_FLR) ? " FLR" : "", + (enable & STS_PCD) ? " PCD" : "", + (enable & STS_ERR) ? " ERR" : "", + (enable & STS_INT) ? " INT" : "" + ); +} + +static const char *const fls_strings[] = + { "1024", "512", "256", "??" }; + +static int dbg_command_buf(char *buf, unsigned len, + const char *label, u32 command) +{ + return scnprintf(buf, len, + "%s%scommand %06x %s=%d ithresh=%d%s%s%s%s period=%s%s %s", + label, label[0] ? " " : "", command, + (command & CMD_PARK) ? "park" : "(park)", + CMD_PARK_CNT(command), + (command >> 16) & 0x3f, + (command & CMD_LRESET) ? " LReset" : "", + (command & CMD_IAAD) ? " IAAD" : "", + (command & CMD_ASE) ? " Async" : "", + (command & CMD_PSE) ? " Periodic" : "", + fls_strings[(command >> 2) & 0x3], + (command & CMD_RESET) ? " Reset" : "", + (command & CMD_RUN) ? "RUN" : "HALT" + ); +} + +static int dbg_port_buf(char *buf, unsigned len, const char *label, + int port, u32 status) +{ + char *sig; + + /* signaling state */ + switch (status & (3 << 10)) { + case 0 << 10: + sig = "se0"; + break; + case 1 << 10: + sig = "k"; /* low speed */ + break; + case 2 << 10: + sig = "j"; + break; + default: + sig = "?"; + break; + } + + return scnprintf(buf, len, + "%s%sport %d status %06x%s%s sig=%s%s%s%s%s%s%s%s%s%s", + label, label[0] ? " " : "", port, status, + (status & PORT_POWER) ? " POWER" : "", + (status & PORT_OWNER) ? " OWNER" : "", + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_OCC) ? " OCC" : "", + (status & PORT_OC) ? " OC" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : "" + ); +} + +#else + +static inline int __attribute__((__unused__)) +dbg_status_buf(char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_command_buf(char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_intr_buf(char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __attribute__((__unused__)) +dbg_port_buf(char *buf, unsigned len, const char *label, int port, u32 status) +{ return 0; } + +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(oxu, label, status) { \ + char _buf[80]; \ + dbg_status_buf(_buf, sizeof _buf, label, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_cmd(oxu, label, command) { \ + char _buf[80]; \ + dbg_command_buf(_buf, sizeof _buf, label, command); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +#define dbg_port(oxu, label, port, status) { \ + char _buf[80]; \ + dbg_port_buf(_buf, sizeof _buf, label, port, status); \ + oxu_dbg(oxu, "%s\n", _buf); \ +} + +/* + * Module parameters + */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh; /* 0 to 6 */ +module_param(log2_irq_thresh, int, S_IRUGO); +MODULE_PARM_DESC(log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* Initial park setting: slower than hw default */ +static unsigned park; +module_param(park, uint, S_IRUGO); +MODULE_PARM_DESC(park, "park setting; 1-3 back-to-back async packets"); + +/* For flakey hardware, ignore overcurrent indicators */ +static int ignore_oc; +module_param(ignore_oc, bool, S_IRUGO); +MODULE_PARM_DESC(ignore_oc, "ignore bogus hardware overcurrent indications"); + + +static void ehci_work(struct oxu_hcd *oxu); +static int oxu_hub_control(struct usb_hcd *hcd, + u16 typeReq, u16 wValue, u16 wIndex, + char *buf, u16 wLength); + +/* + * Local functions + */ + +/* Low level read/write registers functions */ +static inline u32 oxu_readl(void *base, u32 reg) +{ + return readl(base + reg); +} + +static inline void oxu_writel(void *base, u32 reg, u32 val) +{ + writel(val, base + reg); +} + +static inline void timer_action_done(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + clear_bit(action, &oxu->actions); +} + +static inline void timer_action(struct oxu_hcd *oxu, + enum ehci_timer_action action) +{ + if (!test_and_set_bit(action, &oxu->actions)) { + unsigned long t; + + switch (action) { + case TIMER_IAA_WATCHDOG: + t = EHCI_IAA_JIFFIES; + break; + case TIMER_IO_WATCHDOG: + t = EHCI_IO_JIFFIES; + break; + case TIMER_ASYNC_OFF: + t = EHCI_ASYNC_JIFFIES; + break; + case TIMER_ASYNC_SHRINK: + default: + t = EHCI_SHRINK_JIFFIES; + break; + } + t += jiffies; + /* all timings except IAA watchdog can be overridden. + * async queue SHRINK often precedes IAA. while it's ready + * to go OFF neither can matter, and afterwards the IO + * watchdog stops unless there's still periodic traffic. + */ + if (action != TIMER_IAA_WATCHDOG + && t > oxu->watchdog.expires + && timer_pending(&oxu->watchdog)) + return; + mod_timer(&oxu->watchdog, t); + } +} + +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + * + * That last failure should_only happen in cases like physical cardbus eject + * before driver shutdown. But it also seems to be caused by bugs in cardbus + * bridge shutdown: shutting down the bridge before the devices using it. + */ +static int handshake(struct oxu_hcd *oxu, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = readl(ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay(1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* Force HC to halt state from unknown (EHCI spec section 2.3) */ +static int ehci_halt(struct oxu_hcd *oxu) +{ + u32 temp = readl(&oxu->regs->status); + + /* disable any irqs left enabled by previous code */ + writel(0, &oxu->regs->intr_enable); + + if ((temp & STS_HALT) != 0) + return 0; + + temp = readl(&oxu->regs->command); + temp &= ~CMD_RUN; + writel(temp, &oxu->regs->command); + return handshake(oxu, &oxu->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* Put TDI/ARC silicon into EHCI mode */ +static void tdi_reset(struct oxu_hcd *oxu) +{ + u32 __iomem *reg_ptr; + u32 tmp; + + reg_ptr = (u32 __iomem *)(((u8 __iomem *)oxu->regs) + 0x68); + tmp = readl(reg_ptr); + tmp |= 0x3; + writel(tmp, reg_ptr); +} + +/* Reset a non-running (STS_HALT == 1) controller */ +static int ehci_reset(struct oxu_hcd *oxu) +{ + int retval; + u32 command = readl(&oxu->regs->command); + + command |= CMD_RESET; + dbg_cmd(oxu, "reset", command); + writel(command, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + oxu->next_statechange = jiffies; + retval = handshake(oxu, &oxu->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + tdi_reset(oxu); + + return retval; +} + +/* Idle the controller (from running) */ +static void ehci_quiesce(struct oxu_hcd *oxu) +{ + u32 temp; + +#ifdef DEBUG + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + BUG(); +#endif + + /* wait for any schedule enables/disables to take effect */ + temp = readl(&oxu->regs->command) << 10; + temp &= STS_ASS | STS_PSS; + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + temp, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } + + /* then disable anything that's still active */ + temp = readl(&oxu->regs->command); + temp &= ~(CMD_ASE | CMD_IAAD | CMD_PSE); + writel(temp, &oxu->regs->command); + + /* hardware can take 16 microframes to turn off ... */ + if (handshake(oxu, &oxu->regs->status, STS_ASS | STS_PSS, + 0, 16 * 125) != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return; + } +} + +static int check_reset_complete(struct oxu_hcd *oxu, int index, + u32 __iomem *status_reg, int port_status) +{ + if (!(port_status & PORT_CONNECT)) { + oxu->reset_done[index] = 0; + return port_status; + } + + /* if reset finished and it's still not enabled -- handoff */ + if (!(port_status & PORT_PE)) { + oxu_dbg(oxu, "Failed to enable port %d on root hub TT\n", + index+1); + return port_status; + } else + oxu_dbg(oxu, "port %d high speed\n", index + 1); + + return port_status; +} + +static void ehci_hub_descriptor(struct oxu_hcd *oxu, + struct usb_hub_descriptor *desc) +{ + int ports = HCS_N_PORTS(oxu->hcs_params); + u16 temp; + + desc->bDescriptorType = 0x29; + desc->bPwrOn2PwrGood = 10; /* oxu 1.0, 2.3.9 says 20ms max */ + desc->bHubContrCurrent = 0; + + desc->bNbrPorts = ports; + temp = 1 + (ports / 8); + desc->bDescLength = 7 + 2 * temp; + + /* two bitmaps: ports removable, and usb 1.0 legacy PortPwrCtrlMask */ + memset(&desc->bitmap[0], 0, temp); + memset(&desc->bitmap[temp], 0xff, temp); + + temp = 0x0008; /* per-port overcurrent reporting */ + if (HCS_PPC(oxu->hcs_params)) + temp |= 0x0001; /* per-port power control */ + else + temp |= 0x0002; /* no power switching */ + desc->wHubCharacteristics = (__force __u16)cpu_to_le16(temp); +} + + +/* Allocate an OXU210HP on-chip memory data buffer + * + * An on-chip memory data buffer is required for each OXU210HP USB transfer. + * Each transfer descriptor has one or more on-chip memory data buffers. + * + * Data buffers are allocated from a fix sized pool of data blocks. + * To minimise fragmentation and give reasonable memory utlisation, + * data buffers are allocated with sizes the power of 2 multiples of + * the block size, starting on an address a multiple of the allocated size. + * + * FIXME: callers of this function require a buffer to be allocated for + * len=0. This is a waste of on-chip memory and should be fix. Then this + * function should be changed to not allocate a buffer for len=0. + */ +static int oxu_buf_alloc(struct oxu_hcd *oxu, struct ehci_qtd *qtd, int len) +{ + int n_blocks; /* minium blocks needed to hold len */ + int a_blocks; /* blocks allocated */ + int i, j; + + /* Don't allocte bigger than supported */ + if (len > BUFFER_SIZE * BUFFER_NUM) { + oxu_err(oxu, "buffer too big (%d)\n", len); + return -ENOMEM; + } + + spin_lock(&oxu->mem_lock); + + /* Number of blocks needed to hold len */ + n_blocks = (len + BUFFER_SIZE - 1) / BUFFER_SIZE; + + /* Round the number of blocks up to the power of 2 */ + for (a_blocks = 1; a_blocks < n_blocks; a_blocks <<= 1) + ; + + /* Find a suitable available data buffer */ + for (i = 0; i < BUFFER_NUM; + i += max(a_blocks, (int)oxu->db_used[i])) { + + /* Check all the required blocks are available */ + for (j = 0; j < a_blocks; j++) + if (oxu->db_used[i + j]) + break; + + if (j != a_blocks) + continue; + + /* Allocate blocks found! */ + qtd->buffer = (void *) &oxu->mem->db_pool[i]; + qtd->buffer_dma = virt_to_phys(qtd->buffer); + + qtd->qtd_buffer_len = BUFFER_SIZE * a_blocks; + oxu->db_used[i] = a_blocks; + + spin_unlock(&oxu->mem_lock); + + return 0; + } + + /* Failed */ + + spin_unlock(&oxu->mem_lock); + + return -ENOMEM; +} + +static void oxu_buf_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = (qtd->buffer - (void *) &oxu->mem->db_pool[0]) + / BUFFER_SIZE; + oxu->db_used[index] = 0; + qtd->qtd_buffer_len = 0; + qtd->buffer_dma = 0; + qtd->buffer = NULL; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static inline void ehci_qtd_init(struct ehci_qtd *qtd, dma_addr_t dma) +{ + memset(qtd, 0, sizeof *qtd); + qtd->qtd_dma = dma; + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); +} + +static inline void oxu_qtd_free(struct oxu_hcd *oxu, struct ehci_qtd *qtd) +{ + int index; + + if (qtd->buffer) + oxu_buf_free(oxu, qtd); + + spin_lock(&oxu->mem_lock); + + index = qtd - &oxu->mem->qtd_pool[0]; + oxu->qtd_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static struct ehci_qtd *ehci_qtd_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qtd *qtd = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QTD_NUM; i++) + if (!oxu->qtd_used[i]) + break; + + if (i < QTD_NUM) { + qtd = (struct ehci_qtd *) &oxu->mem->qtd_pool[i]; + memset(qtd, 0, sizeof *qtd); + + qtd->hw_token = cpu_to_le32(QTD_STS_HALT); + qtd->hw_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END; + INIT_LIST_HEAD(&qtd->qtd_list); + + qtd->qtd_dma = virt_to_phys(qtd); + + oxu->qtd_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return qtd; +} + +static void oxu_qh_free(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = qh - &oxu->mem->qh_pool[0]; + oxu->qh_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static void qh_destroy(struct kref *kref) +{ + struct ehci_qh *qh = container_of(kref, struct ehci_qh, kref); + struct oxu_hcd *oxu = qh->oxu; + + /* clean qtds first, and know this is not linked */ + if (!list_empty(&qh->qtd_list) || qh->qh_next.ptr) { + oxu_dbg(oxu, "unused qh not empty!\n"); + BUG(); + } + if (qh->dummy) + oxu_qtd_free(oxu, qh->dummy); + oxu_qh_free(oxu, qh); +} + +static struct ehci_qh *oxu_qh_alloc(struct oxu_hcd *oxu) +{ + int i; + struct ehci_qh *qh = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < QHEAD_NUM; i++) + if (!oxu->qh_used[i]) + break; + + if (i < QHEAD_NUM) { + qh = (struct ehci_qh *) &oxu->mem->qh_pool[i]; + memset(qh, 0, sizeof *qh); + + kref_init(&qh->kref); + qh->oxu = oxu; + qh->qh_dma = virt_to_phys(qh); + INIT_LIST_HEAD(&qh->qtd_list); + + /* dummy td enables safe urb queuing */ + qh->dummy = ehci_qtd_alloc(oxu); + if (qh->dummy == NULL) { + oxu_dbg(oxu, "no dummy td\n"); + oxu->qh_used[i] = 0; + + return NULL; + } + + oxu->qh_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return qh; +} + +/* to share a qh (cpu threads, or hc) */ +static inline struct ehci_qh *qh_get(struct ehci_qh *qh) +{ + kref_get(&qh->kref); + return qh; +} + +static inline void qh_put(struct ehci_qh *qh) +{ + kref_put(&qh->kref, qh_destroy); +} + +static void oxu_murb_free(struct oxu_hcd *oxu, struct oxu_murb *murb) +{ + int index; + + spin_lock(&oxu->mem_lock); + + index = murb - &oxu->murb_pool[0]; + oxu->murb_used[index] = 0; + + spin_unlock(&oxu->mem_lock); + + return; +} + +static struct oxu_murb *oxu_murb_alloc(struct oxu_hcd *oxu) + +{ + int i; + struct oxu_murb *murb = NULL; + + spin_lock(&oxu->mem_lock); + + for (i = 0; i < MURB_NUM; i++) + if (!oxu->murb_used[i]) + break; + + if (i < MURB_NUM) { + murb = &(oxu->murb_pool)[i]; + + oxu->murb_used[i] = 1; + } + + spin_unlock(&oxu->mem_lock); + + return murb; +} + +/* The queue heads and transfer descriptors are managed from pools tied + * to each of the "per device" structures. + * This is the initialisation and cleanup code. + */ +static void ehci_mem_cleanup(struct oxu_hcd *oxu) +{ + kfree(oxu->murb_pool); + oxu->murb_pool = NULL; + + if (oxu->async) + qh_put(oxu->async); + oxu->async = NULL; + + del_timer(&oxu->urb_timer); + + oxu->periodic = NULL; + + /* shadow periodic table */ + kfree(oxu->pshadow); + oxu->pshadow = NULL; +} + +/* Remember to add cleanup code (above) if you add anything here. + */ +static int ehci_mem_init(struct oxu_hcd *oxu, gfp_t flags) +{ + int i; + + for (i = 0; i < oxu->periodic_size; i++) + oxu->mem->frame_list[i] = EHCI_LIST_END; + for (i = 0; i < QHEAD_NUM; i++) + oxu->qh_used[i] = 0; + for (i = 0; i < QTD_NUM; i++) + oxu->qtd_used[i] = 0; + + oxu->murb_pool = kcalloc(MURB_NUM, sizeof(struct oxu_murb), flags); + if (!oxu->murb_pool) + goto fail; + + for (i = 0; i < MURB_NUM; i++) + oxu->murb_used[i] = 0; + + oxu->async = oxu_qh_alloc(oxu); + if (!oxu->async) + goto fail; + + oxu->periodic = (__le32 *) &oxu->mem->frame_list; + oxu->periodic_dma = virt_to_phys(oxu->periodic); + + for (i = 0; i < oxu->periodic_size; i++) + oxu->periodic[i] = EHCI_LIST_END; + + /* software shadow of hardware table */ + oxu->pshadow = kcalloc(oxu->periodic_size, sizeof(void *), flags); + if (oxu->pshadow != NULL) + return 0; + +fail: + oxu_dbg(oxu, "couldn't init memory\n"); + ehci_mem_cleanup(oxu); + return -ENOMEM; +} + +/* Fill a qtd, returning how much of the buffer we were able to queue up. + */ +static int qtd_fill(struct ehci_qtd *qtd, dma_addr_t buf, size_t len, + int token, int maxpacket) +{ + int i, count; + u64 addr = buf; + + /* one buffer entry per 4K ... first might be short or unaligned */ + qtd->hw_buf[0] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[0] = cpu_to_le32((u32)(addr >> 32)); + count = 0x1000 - (buf & 0x0fff); /* rest of that page */ + if (likely(len < count)) /* ... iff needed */ + count = len; + else { + buf += 0x1000; + buf &= ~0x0fff; + + /* per-qtd limit: from 16K to 20K (best alignment) */ + for (i = 1; count < len && i < 5; i++) { + addr = buf; + qtd->hw_buf[i] = cpu_to_le32((u32)addr); + qtd->hw_buf_hi[i] = cpu_to_le32((u32)(addr >> 32)); + buf += 0x1000; + if ((count + 0x1000) < len) + count += 0x1000; + else + count = len; + } + + /* short packets may only terminate transfers */ + if (count != len) + count -= (count % maxpacket); + } + qtd->hw_token = cpu_to_le32((count << 16) | token); + qtd->length = count; + + return count; +} + +static inline void qh_update(struct oxu_hcd *oxu, + struct ehci_qh *qh, struct ehci_qtd *qtd) +{ + /* writes to an active overlay are unsafe */ + BUG_ON(qh->qh_state != QH_STATE_IDLE); + + qh->hw_qtd_next = QTD_NEXT(qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END; + + /* Except for control endpoints, we make hardware maintain data + * toggle (like OHCI) ... here (re)initialize the toggle in the QH, + * and set the pseudo-toggle in udev. Only usb_clear_halt() will + * ever clear it. + */ + if (!(qh->hw_info1 & cpu_to_le32(1 << 14))) { + unsigned is_out, epnum; + + is_out = !(qtd->hw_token & cpu_to_le32(1 << 8)); + epnum = (le32_to_cpup(&qh->hw_info1) >> 8) & 0x0f; + if (unlikely(!usb_gettoggle(qh->dev, epnum, is_out))) { + qh->hw_token &= ~__constant_cpu_to_le32(QTD_TOGGLE); + usb_settoggle(qh->dev, epnum, is_out, 1); + } + } + + /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ + wmb(); + qh->hw_token &= __constant_cpu_to_le32(QTD_TOGGLE | QTD_STS_PING); +} + +/* If it weren't for a common silicon quirk (writing the dummy into the qh + * overlay, so qh->hw_token wrongly becomes inactive/halted), only fault + * recovery (including urb dequeue) would need software changes to a QH... + */ +static void qh_refresh(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *qtd; + + if (list_empty(&qh->qtd_list)) + qtd = qh->dummy; + else { + qtd = list_entry(qh->qtd_list.next, + struct ehci_qtd, qtd_list); + /* first qtd may already be partially processed */ + if (cpu_to_le32(qtd->qtd_dma) == qh->hw_current) + qtd = NULL; + } + + if (qtd) + qh_update(oxu, qh, qtd); +} + +static void qtd_copy_status(struct oxu_hcd *oxu, struct urb *urb, + size_t length, u32 token) +{ + /* count IN/OUT bytes, not SETUP (even short packets) */ + if (likely(QTD_PID(token) != 2)) + urb->actual_length += length - QTD_LENGTH(token); + + /* don't modify error codes */ + if (unlikely(urb->status != -EINPROGRESS)) + return; + + /* force cleanup after short read; not always an error */ + if (unlikely(IS_SHORT_READ(token))) + urb->status = -EREMOTEIO; + + /* serious "can't proceed" faults reported by the hardware */ + if (token & QTD_STS_HALT) { + if (token & QTD_STS_BABBLE) { + /* FIXME "must" disable babbling device's port too */ + urb->status = -EOVERFLOW; + } else if (token & QTD_STS_MMF) { + /* fs/ls interrupt xfer missed the complete-split */ + urb->status = -EPROTO; + } else if (token & QTD_STS_DBE) { + urb->status = (QTD_PID(token) == 1) /* IN ? */ + ? -ENOSR /* hc couldn't read data */ + : -ECOMM; /* hc couldn't write data */ + } else if (token & QTD_STS_XACT) { + /* timeout, bad crc, wrong PID, etc; retried */ + if (QTD_CERR(token)) + urb->status = -EPIPE; + else { + oxu_dbg(oxu, "devpath %s ep%d%s 3strikes\n", + urb->dev->devpath, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out"); + urb->status = -EPROTO; + } + /* CERR nonzero + no errors + halt --> stall */ + } else if (QTD_CERR(token)) + urb->status = -EPIPE; + else /* unknown */ + urb->status = -EPROTO; + + oxu_vdbg(oxu, "dev%d ep%d%s qtd token %08x --> status %d\n", + usb_pipedevice(urb->pipe), + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + token, urb->status); + } +} + +static void ehci_urb_done(struct oxu_hcd *oxu, struct urb *urb) +__releases(oxu->lock) +__acquires(oxu->lock) +{ + if (likely(urb->hcpriv != NULL)) { + struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; + + /* S-mask in a QH means it's an interrupt urb */ + if ((qh->hw_info2 & __constant_cpu_to_le32(QH_SMASK)) != 0) { + + /* ... update hc-wide periodic stats (for usbfs) */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs--; + } + qh_put(qh); + } + + urb->hcpriv = NULL; + switch (urb->status) { + case -EINPROGRESS: /* success */ + urb->status = 0; + default: /* fault */ + break; + case -EREMOTEIO: /* fault or normal */ + if (!(urb->transfer_flags & URB_SHORT_NOT_OK)) + urb->status = 0; + break; + case -ECONNRESET: /* canceled */ + case -ENOENT: + break; + } + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s status %d len %d/%d\n", + __func__, urb->dev->devpath, urb, + usb_pipeendpoint(urb->pipe), + usb_pipein(urb->pipe) ? "in" : "out", + urb->status, + urb->actual_length, urb->transfer_buffer_length); +#endif + + /* complete() can reenter this HCD */ + spin_unlock(&oxu->lock); + usb_hcd_giveback_urb(oxu_to_hcd(oxu), urb, urb->status); + spin_lock(&oxu->lock); +} + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); +static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh); + +static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh); +static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh); + +#define HALT_BIT __constant_cpu_to_le32(QTD_STS_HALT) + +/* Process and free completed qtds for a qh, returning URBs to drivers. + * Chases up to qh->hw_current. Returns number of completions called, + * indicating how much "real" work we did. + */ +static unsigned qh_completions(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + struct ehci_qtd *last = NULL, *end = qh->dummy; + struct list_head *entry, *tmp; + int stopped; + unsigned count = 0; + int do_status = 0; + u8 state; + struct oxu_murb *murb = NULL; + + if (unlikely(list_empty(&qh->qtd_list))) + return count; + + /* completions (or tasks on other cpus) must never clobber HALT + * till we've gone through and cleaned everything up, even when + * they add urbs to this qh's queue or mark them for unlinking. + * + * NOTE: unlinking expects to be done in queue order. + */ + state = qh->qh_state; + qh->qh_state = QH_STATE_COMPLETING; + stopped = (state == QH_STATE_IDLE); + + /* remove de-activated QTDs from front of queue. + * after faults (including short reads), cleanup this urb + * then let the queue advance. + * if queue is stopped, handles unlinks. + */ + list_for_each_safe(entry, tmp, &qh->qtd_list) { + struct ehci_qtd *qtd; + struct urb *urb; + u32 token = 0; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + urb = qtd->urb; + + /* Clean up any state from previous QTD ...*/ + if (last) { + if (likely(last->urb != urb)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + } + oxu_qtd_free(oxu, last); + last = NULL; + } + + /* ignore urbs submitted during completions we reported */ + if (qtd == end) + break; + + /* hardware copies qtd out of qh overlay */ + rmb(); + token = le32_to_cpu(qtd->hw_token); + + /* always clean up qtds the hc de-activated */ + if ((token & QTD_STS_ACTIVE) == 0) { + + if ((token & QTD_STS_HALT) != 0) { + stopped = 1; + + /* magic dummy for some short reads; qh won't advance. + * that silicon quirk can kick in with this dummy too. + */ + } else if (IS_SHORT_READ(token) && + !(qtd->hw_alt_next & EHCI_LIST_END)) { + stopped = 1; + goto halt; + } + + /* stop scanning when we reach qtds the hc is using */ + } else if (likely(!stopped && + HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) { + break; + + } else { + stopped = 1; + + if (unlikely(!HC_IS_RUNNING(oxu_to_hcd(oxu)->state))) + urb->status = -ESHUTDOWN; + + /* ignore active urbs unless some previous qtd + * for the urb faulted (including short read) or + * its urb was canceled. we may patch qh or qtds. + */ + if (likely(urb->status == -EINPROGRESS)) + continue; + + /* issue status after short control reads */ + if (unlikely(do_status != 0) + && QTD_PID(token) == 0 /* OUT */) { + do_status = 0; + continue; + } + + /* token in overlay may be most current */ + if (state == QH_STATE_IDLE + && cpu_to_le32(qtd->qtd_dma) + == qh->hw_current) + token = le32_to_cpu(qh->hw_token); + + /* force halt for unlinked or blocked qh, so we'll + * patch the qh later and so that completions can't + * activate it while we "know" it's stopped. + */ + if ((HALT_BIT & qh->hw_token) == 0) { +halt: + qh->hw_token |= HALT_BIT; + wmb(); + } + } + + /* Remove it from the queue */ + qtd_copy_status(oxu, urb->complete ? + urb : ((struct oxu_murb *) urb)->main, + qtd->length, token); + if ((usb_pipein(qtd->urb->pipe)) && + (NULL != qtd->transfer_buffer)) + memcpy(qtd->transfer_buffer, qtd->buffer, qtd->length); + do_status = (urb->status == -EREMOTEIO) + && usb_pipecontrol(urb->pipe); + + if (stopped && qtd->qtd_list.prev != &qh->qtd_list) { + last = list_entry(qtd->qtd_list.prev, + struct ehci_qtd, qtd_list); + last->hw_next = qtd->hw_next; + } + list_del(&qtd->qtd_list); + last = qtd; + } + + /* last urb's completion might still need calling */ + if (likely(last != NULL)) { + if (last->urb->complete == NULL) { + murb = (struct oxu_murb *) last->urb; + last->urb = murb->main; + if (murb->last) { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_murb_free(oxu, murb); + } else { + ehci_urb_done(oxu, last->urb); + count++; + } + oxu_qtd_free(oxu, last); + } + + /* restore original state; caller must unlink or relink */ + qh->qh_state = state; + + /* be sure the hardware's done with the qh before refreshing + * it after fault cleanup, or recovering from silicon wrongly + * overlaying the dummy qtd (which reduces DMA chatter). + */ + if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END) { + switch (state) { + case QH_STATE_IDLE: + qh_refresh(oxu, qh); + break; + case QH_STATE_LINKED: + /* should be rare for periodic transfers, + * except maybe high bandwidth ... + */ + if ((__constant_cpu_to_le32(QH_SMASK) + & qh->hw_info2) != 0) { + intr_deschedule(oxu, qh); + (void) qh_schedule(oxu, qh); + } else + unlink_async(oxu, qh); + break; + /* otherwise, unlink already started */ + } + } + + return count; +} + +/* High bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) +/* ... and packet size, for any kind of endpoint descriptor */ +#define max_packet(wMaxPacketSize) ((wMaxPacketSize) & 0x07ff) + +/* Reverse of qh_urb_transaction: free a list of TDs. + * used for cleanup after errors, before HC sees an URB's TDs. + */ +static void qtd_list_free(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list) +{ + struct list_head *entry, *temp; + + list_for_each_safe(entry, temp, qtd_list) { + struct ehci_qtd *qtd; + + qtd = list_entry(entry, struct ehci_qtd, qtd_list); + list_del(&qtd->qtd_list); + oxu_qtd_free(oxu, qtd); + } +} + +/* Create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head *qh_urb_transaction(struct oxu_hcd *oxu, + struct urb *urb, + struct list_head *head, + gfp_t flags) +{ + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, maxpacket; + int is_input; + u32 token; + void *transfer_buf = NULL; + int ret; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + return NULL; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + + len = urb->transfer_buffer_length; + is_input = usb_pipein(urb->pipe); + if (!urb->transfer_buffer && urb->transfer_buffer_length && is_input) + urb->transfer_buffer = phys_to_virt(urb->transfer_dma); + + if (usb_pipecontrol(urb->pipe)) { + /* SETUP pid */ + ret = oxu_buf_alloc(oxu, qtd, sizeof(struct usb_ctrlrequest)); + if (ret) + goto cleanup; + + qtd_fill(qtd, qtd->buffer_dma, sizeof(struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + memcpy(qtd->buffer, qtd->urb->setup_packet, + sizeof(struct usb_ctrlrequest)); + + /* ... and always at least one more pid */ + token ^= QTD_TOGGLE; + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* for zero length DATA stages, STATUS is always IN */ + if (len == 0) + token |= (1 /* "in" */ << 8); + } + + /* + * Data transfer stage: buffer setup + */ + + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + + buf = qtd->buffer_dma; + transfer_buf = urb->transfer_buffer; + + if (!is_input) + memcpy(qtd->buffer, qtd->urb->transfer_buffer, len); + + if (is_input) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + + maxpacket = max_packet(usb_maxpacket(urb->dev, urb->pipe, !is_input)); + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(qtd, buf, len, token, maxpacket); + qtd->transfer_buffer = transfer_buf; + len -= this_qtd_len; + buf += this_qtd_len; + transfer_buf += this_qtd_len; + if (is_input) + qtd->hw_alt_next = oxu->async->hw_alt_next; + + /* qh makes control packets use qtd toggle; maybe switch it */ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely(len <= 0)) + break; + + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + if (likely(len > 0)) { + ret = oxu_buf_alloc(oxu, qtd, len); + if (ret) + goto cleanup; + } + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + } + + /* unless the bulk/interrupt caller wants a chance to clean + * up after short reads, hc should advance qh past this urb + */ + if (likely((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol(urb->pipe))) + qtd->hw_alt_next = EHCI_LIST_END; + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + if (likely(urb->transfer_buffer_length != 0)) { + int one_more = 0; + + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + token ^= 0x0100; /* "in" <--> "out" */ + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + qtd_prev = qtd; + qtd = ehci_qtd_alloc(oxu); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + + /* never any data in such packets */ + qtd_fill(qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + qtd->hw_token |= __constant_cpu_to_le32(QTD_IOC); + return head; + +cleanup: + qtd_list_free(oxu, urb, head); + return NULL; +} + +/* Each QH holds a qtd list; a QH is used for everything except iso. + * + * For interrupt urbs, the scheduler must set the microframe scheduling + * mask(s) each time the QH gets scheduled. For highspeed, that's + * just one microframe in the s-mask. For split interrupt transactions + * there are additional complications: c-mask, maybe FSTNs. + */ +static struct ehci_qh *qh_make(struct oxu_hcd *oxu, + struct urb *urb, gfp_t flags) +{ + struct ehci_qh *qh = oxu_qh_alloc(oxu); + u32 info1 = 0, info2 = 0; + int is_input, type; + int maxp = 0; + + if (!qh) + return qh; + + /* + * init endpoint/device data for this QH + */ + info1 |= usb_pipeendpoint(urb->pipe) << 8; + info1 |= usb_pipedevice(urb->pipe) << 0; + + is_input = usb_pipein(urb->pipe); + type = usb_pipetype(urb->pipe); + maxp = usb_maxpacket(urb->dev, urb->pipe, !is_input); + + /* Compute interrupt scheduling parameters just once, and save. + * - allowing for high bandwidth, how many nsec/uframe are used? + * - split transactions need a second CSPLIT uframe; same question + * - splits also need a schedule gap (for full/low speed I/O) + * - qh has a polling interval + * + * For control/bulk requests, the HC or TT handles these. + */ + if (type == PIPE_INTERRUPT) { + qh->usecs = NS_TO_US(usb_calc_bus_time(USB_SPEED_HIGH, + is_input, 0, + hb_mult(maxp) * max_packet(maxp))); + qh->start = NO_FRAME; + + if (urb->dev->speed == USB_SPEED_HIGH) { + qh->c_usecs = 0; + qh->gap_uf = 0; + + qh->period = urb->interval >> 3; + if (qh->period == 0 && urb->interval != 1) { + /* NOTE interval 2 or 4 uframes could work. + * But interval 1 scheduling is simpler, and + * includes high bandwidth. + */ + dbg("intr period %d uframes, NYET!", + urb->interval); + goto done; + } + } else { + struct usb_tt *tt = urb->dev->tt; + int think_time; + + /* gap is f(FS/LS transfer times) */ + qh->gap_uf = 1 + usb_calc_bus_time(urb->dev->speed, + is_input, 0, maxp) / (125 * 1000); + + /* FIXME this just approximates SPLIT/CSPLIT times */ + if (is_input) { /* SPLIT, gap, CSPLIT+DATA */ + qh->c_usecs = qh->usecs + HS_USECS(0); + qh->usecs = HS_USECS(1); + } else { /* SPLIT+DATA, gap, CSPLIT */ + qh->usecs += HS_USECS(1); + qh->c_usecs = HS_USECS(0); + } + + think_time = tt ? tt->think_time : 0; + qh->tt_usecs = NS_TO_US(think_time + + usb_calc_bus_time(urb->dev->speed, + is_input, 0, max_packet(maxp))); + qh->period = urb->interval; + } + } + + /* support for tt scheduling, and access to toggles */ + qh->dev = urb->dev; + + /* using TT? */ + switch (urb->dev->speed) { + case USB_SPEED_LOW: + info1 |= (1 << 12); /* EPS "low" */ + /* FALL THROUGH */ + + case USB_SPEED_FULL: + /* EPS 0 means "full" */ + if (type != PIPE_INTERRUPT) + info1 |= (EHCI_TUNE_RL_TT << 28); + if (type == PIPE_CONTROL) { + info1 |= (1 << 27); /* for TT */ + info1 |= 1 << 14; /* toggle from qtd */ + } + info1 |= maxp << 16; + + info2 |= (EHCI_TUNE_MULT_TT << 30); + info2 |= urb->dev->ttport << 23; + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets c-mask } */ + + break; + + case USB_SPEED_HIGH: /* no TT involved */ + info1 |= (2 << 12); /* EPS "high" */ + if (type == PIPE_CONTROL) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 64 << 16; /* usb2 fixed maxpacket */ + info1 |= 1 << 14; /* toggle from qtd */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else if (type == PIPE_BULK) { + info1 |= (EHCI_TUNE_RL_HS << 28); + info1 |= 512 << 16; /* usb2 fixed maxpacket */ + info2 |= (EHCI_TUNE_MULT_HS << 30); + } else { /* PIPE_INTERRUPT */ + info1 |= max_packet(maxp) << 16; + info2 |= hb_mult(maxp) << 30; + } + break; + default: + dbg("bogus dev %p speed %d", urb->dev, urb->dev->speed); +done: + qh_put(qh); + return NULL; + } + + /* NOTE: if (PIPE_INTERRUPT) { scheduler sets s-mask } */ + + /* init as live, toggle clear, advance to dummy */ + qh->qh_state = QH_STATE_IDLE; + qh->hw_info1 = cpu_to_le32(info1); + qh->hw_info2 = cpu_to_le32(info2); + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), !is_input, 1); + qh_refresh(oxu, qh); + return qh; +} + +/* Move qh (and its qtds) onto async queue; maybe enable queue. + */ +static void qh_link_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + __le32 dma = QH_NEXT(qh->qh_dma); + struct ehci_qh *head; + + /* (re)start the async schedule? */ + head = oxu->async; + timer_action_done(oxu, TIMER_ASYNC_OFF); + if (!head->qh_next.qh) { + u32 cmd = readl(&oxu->regs->command); + + if (!(cmd & CMD_ASE)) { + /* in case a clear of CMD_ASE didn't take yet */ + (void)handshake(oxu, &oxu->regs->status, + STS_ASS, 0, 150); + cmd |= CMD_ASE | CMD_RUN; + writel(cmd, &oxu->regs->command); + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + /* posted write need not be known to HC yet ... */ + } + } + + /* clear halt and/or toggle; and maybe recover from silicon quirk */ + if (qh->qh_state == QH_STATE_IDLE) + qh_refresh(oxu, qh); + + /* splice right after start */ + qh->qh_next = head->qh_next; + qh->hw_next = head->hw_next; + wmb(); + + head->qh_next.qh = qh; + head->hw_next = dma; + + qh->qh_state = QH_STATE_LINKED; + /* qtd completions reported later by interrupt */ +} + +#define QH_ADDR_MASK __constant_cpu_to_le32(0x7f) + +/* + * For control/bulk/interrupt, return QH with these TDs appended. + * Allocates and initializes the QH if necessary. + * Returns null if it can't allocate a QH it needs to. + * If the QH has TDs (urbs) already, that's great. + */ +static struct ehci_qh *qh_append_tds(struct oxu_hcd *oxu, + struct urb *urb, struct list_head *qtd_list, + int epnum, void **ptr) +{ + struct ehci_qh *qh = NULL; + + qh = (struct ehci_qh *) *ptr; + if (unlikely(qh == NULL)) { + /* can't sleep here, we have oxu->lock... */ + qh = qh_make(oxu, urb, GFP_ATOMIC); + *ptr = qh; + } + if (likely(qh != NULL)) { + struct ehci_qtd *qtd; + + if (unlikely(list_empty(qtd_list))) + qtd = NULL; + else + qtd = list_entry(qtd_list->next, struct ehci_qtd, + qtd_list); + + /* control qh may need patching ... */ + if (unlikely(epnum == 0)) { + + /* usb_reset_device() briefly reverts to address 0 */ + if (usb_pipedevice(urb->pipe) == 0) + qh->hw_info1 &= ~QH_ADDR_MASK; + } + + /* just one way to queue requests: swap with the dummy qtd. + * only hc or qh_refresh() ever modify the overlay. + */ + if (likely(qtd != NULL)) { + struct ehci_qtd *dummy; + dma_addr_t dma; + __le32 token; + + /* to avoid racing the HC, use the dummy td instead of + * the first td of our list (becomes new dummy). both + * tds stay deactivated until we're done, when the + * HC is allowed to fetch the old dummy (4.10.2). + */ + token = qtd->hw_token; + qtd->hw_token = HALT_BIT; + wmb(); + dummy = qh->dummy; + + dma = dummy->qtd_dma; + *dummy = *qtd; + dummy->qtd_dma = dma; + + list_del(&qtd->qtd_list); + list_add(&dummy->qtd_list, qtd_list); + list_splice(qtd_list, qh->qtd_list.prev); + + ehci_qtd_init(qtd, qtd->qtd_dma); + qh->dummy = qtd; + + /* hc must see the new dummy at list end */ + dma = qtd->qtd_dma; + qtd = list_entry(qh->qtd_list.prev, + struct ehci_qtd, qtd_list); + qtd->hw_next = QTD_NEXT(dma); + + /* let the hc process these next qtds */ + dummy->hw_token = (token & ~(0x80)); + wmb(); + dummy->hw_token = token; + + urb->hcpriv = qh_get(qh); + } + } + return qh; +} + +static int submit_async(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + struct ehci_qtd *qtd; + int epnum; + unsigned long flags; + struct ehci_qh *qh = NULL; + int rc = 0; + + qtd = list_entry(qtd_list->next, struct ehci_qtd, qtd_list); + epnum = urb->ep->desc.bEndpointAddress; + +#ifdef OXU_URB_TRACE + oxu_dbg(oxu, "%s %s urb %p ep%d%s len %d, qtd %p [qh %p]\n", + __func__, urb->dev->devpath, urb, + epnum & 0x0f, (epnum & USB_DIR_IN) ? "in" : "out", + urb->transfer_buffer_length, + qtd, urb->ep->hcpriv); +#endif + + spin_lock_irqsave(&oxu->lock, flags); + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, + &oxu_to_hcd(oxu)->flags))) { + rc = -ESHUTDOWN; + goto done; + } + + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + if (unlikely(qh == NULL)) { + rc = -ENOMEM; + goto done; + } + + /* Control/bulk operations through TTs don't need scheduling, + * the HC and TT handle it when the TT has a buffer ready. + */ + if (likely(qh->qh_state == QH_STATE_IDLE)) + qh_link_async(oxu, qh_get(qh)); +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (unlikely(qh == NULL)) + qtd_list_free(oxu, urb, qtd_list); + return rc; +} + +/* The async qh for the qtds being reclaimed are now unlinked from the HC */ + +static void end_unlink_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh = oxu->reclaim; + struct ehci_qh *next; + + timer_action_done(oxu, TIMER_IAA_WATCHDOG); + + qh->qh_state = QH_STATE_IDLE; + qh->qh_next.qh = NULL; + qh_put(qh); /* refcount from reclaim */ + + /* other unlink(s) may be pending (in QH_STATE_UNLINK_WAIT) */ + next = qh->reclaim; + oxu->reclaim = next; + oxu->reclaim_ready = 0; + qh->reclaim = NULL; + + qh_completions(oxu, qh); + + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + qh_link_async(oxu, qh); + else { + qh_put(qh); /* refcount from async list */ + + /* it's not free to turn the async schedule on/off; leave it + * active but idle for a while once it empties. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) + && oxu->async->qh_next.qh == NULL) + timer_action(oxu, TIMER_ASYNC_OFF); + } + + if (next) { + oxu->reclaim = NULL; + start_unlink_async(oxu, next); + } +} + +/* makes sure the async qh will become idle */ +/* caller must own oxu->lock */ + +static void start_unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int cmd = readl(&oxu->regs->command); + struct ehci_qh *prev; + +#ifdef DEBUG + assert_spin_locked(&oxu->lock); + if (oxu->reclaim || (qh->qh_state != QH_STATE_LINKED + && qh->qh_state != QH_STATE_UNLINK_WAIT)) + BUG(); +#endif + + /* stop async schedule right now? */ + if (unlikely(qh == oxu->async)) { + /* can't get here without STS_ASS set */ + if (oxu_to_hcd(oxu)->state != HC_STATE_HALT + && !oxu->reclaim) { + /* ... and CMD_IAAD clear */ + writel(cmd & ~CMD_ASE, &oxu->regs->command); + wmb(); + /* handshake later, if we need to */ + timer_action_done(oxu, TIMER_ASYNC_OFF); + } + return; + } + + qh->qh_state = QH_STATE_UNLINK; + oxu->reclaim = qh = qh_get(qh); + + prev = oxu->async; + while (prev->qh_next.qh != qh) + prev = prev->qh_next.qh; + + prev->hw_next = qh->hw_next; + prev->qh_next = qh->qh_next; + wmb(); + + if (unlikely(oxu_to_hcd(oxu)->state == HC_STATE_HALT)) { + /* if (unlikely(qh->reclaim != 0)) + * this will recurse, probably not much + */ + end_unlink_async(oxu); + return; + } + + oxu->reclaim_ready = 0; + cmd |= CMD_IAAD; + writel(cmd, &oxu->regs->command); + (void) readl(&oxu->regs->command); + timer_action(oxu, TIMER_IAA_WATCHDOG); +} + +static void scan_async(struct oxu_hcd *oxu) +{ + struct ehci_qh *qh; + enum ehci_timer_action action = TIMER_IO_WATCHDOG; + + if (!++(oxu->stamp)) + oxu->stamp++; + timer_action_done(oxu, TIMER_ASYNC_SHRINK); +rescan: + qh = oxu->async->qh_next.qh; + if (likely(qh != NULL)) { + do { + /* clean any finished work for this qh */ + if (!list_empty(&qh->qtd_list) + && qh->stamp != oxu->stamp) { + int temp; + + /* unlinks could happen here; completion + * reporting drops the lock. rescan using + * the latest schedule, but don't rescan + * qhs we already finished (no looping). + */ + qh = qh_get(qh); + qh->stamp = oxu->stamp; + temp = qh_completions(oxu, qh); + qh_put(qh); + if (temp != 0) + goto rescan; + } + + /* unlink idle entries, reducing HC PCI usage as well + * as HCD schedule-scanning costs. delay for any qh + * we just scanned, there's a not-unusual case that it + * doesn't stay idle for long. + * (plus, avoids some kind of re-activation race.) + */ + if (list_empty(&qh->qtd_list)) { + if (qh->stamp == oxu->stamp) + action = TIMER_ASYNC_SHRINK; + else if (!oxu->reclaim + && qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); + } + + qh = qh->qh_next.qh; + } while (qh); + } + if (action == TIMER_ASYNC_SHRINK) + timer_action(oxu, TIMER_ASYNC_SHRINK); +} + +/* + * periodic_next_shadow - return "next" pointer on shadow list + * @periodic: host pointer to qh/itd/sitd + * @tag: hardware tag for type of this record + */ +static union ehci_shadow *periodic_next_shadow(union ehci_shadow *periodic, + __le32 tag) +{ + switch (tag) { + default: + case Q_TYPE_QH: + return &periodic->qh->qh_next; + } +} + +/* caller must hold oxu->lock */ +static void periodic_unlink(struct oxu_hcd *oxu, unsigned frame, void *ptr) +{ + union ehci_shadow *prev_p = &oxu->pshadow[frame]; + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow here = *prev_p; + + /* find predecessor of "ptr"; hw and shadow lists are in sync */ + while (here.ptr && here.ptr != ptr) { + prev_p = periodic_next_shadow(prev_p, Q_NEXT_TYPE(*hw_p)); + hw_p = here.hw_next; + here = *prev_p; + } + /* an interrupt entry (at list end) could have been shared */ + if (!here.ptr) + return; + + /* update shadow and hardware lists ... the old "next" pointers + * from ptr may still be in use, the caller updates them. + */ + *prev_p = *periodic_next_shadow(&here, Q_NEXT_TYPE(*hw_p)); + *hw_p = *here.hw_next; +} + +/* how many of the uframe's 125 usecs are allocated? */ +static unsigned short periodic_usecs(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe) +{ + __le32 *hw_p = &oxu->periodic[frame]; + union ehci_shadow *q = &oxu->pshadow[frame]; + unsigned usecs = 0; + + while (q->ptr) { + switch (Q_NEXT_TYPE(*hw_p)) { + case Q_TYPE_QH: + default: + /* is it in the S-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << uframe)) + usecs += q->qh->usecs; + /* ... or C-mask? */ + if (q->qh->hw_info2 & cpu_to_le32(1 << (8 + uframe))) + usecs += q->qh->c_usecs; + hw_p = &q->qh->hw_next; + q = &q->qh->qh_next; + break; + } + } +#ifdef DEBUG + if (usecs > 100) + oxu_err(oxu, "uframe %d sched overrun: %d usecs\n", + frame * 8 + uframe, usecs); +#endif + return usecs; +} + +static int enable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did clearing PSE did take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, 0, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return status; + } + + cmd = readl(&oxu->regs->command) | CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... PSS happens later */ + oxu_to_hcd(oxu)->state = HC_STATE_RUNNING; + + /* make sure ehci_work scans these */ + oxu->next_uframe = readl(&oxu->regs->frame_index) + % (oxu->periodic_size << 3); + return 0; +} + +static int disable_periodic(struct oxu_hcd *oxu) +{ + u32 cmd; + int status; + + /* did setting PSE not take effect yet? + * takes effect only at frame boundaries... + */ + status = handshake(oxu, &oxu->regs->status, STS_PSS, STS_PSS, 9 * 125); + if (status != 0) { + oxu_to_hcd(oxu)->state = HC_STATE_HALT; + return status; + } + + cmd = readl(&oxu->regs->command) & ~CMD_PSE; + writel(cmd, &oxu->regs->command); + /* posted write ... */ + + oxu->next_uframe = -1; + return 0; +} + +/* periodic schedule slots have iso tds (normal or split) first, then a + * sparse tree for active interrupt transfers. + * + * this just links in a qh; caller guarantees uframe masks are set right. + * no FSTN support (yet; oxu 0.96+) + */ +static int qh_link_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period = qh->period; + + dev_dbg(&qh->dev->dev, + "link qh%d-%04x/%p start %d [%d/%d us]\n", + period, le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* high bandwidth, or otherwise every microframe */ + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) { + union ehci_shadow *prev = &oxu->pshadow[i]; + __le32 *hw_p = &oxu->periodic[i]; + union ehci_shadow here = *prev; + __le32 type = 0; + + /* skip the iso nodes at list head */ + while (here.ptr) { + type = Q_NEXT_TYPE(*hw_p); + if (type == Q_TYPE_QH) + break; + prev = periodic_next_shadow(prev, type); + hw_p = &here.qh->hw_next; + here = *prev; + } + + /* sorting each branch by period (slow-->fast) + * enables sharing interior tree nodes + */ + while (here.ptr && qh != here.qh) { + if (qh->period > here.qh->period) + break; + prev = &here.qh->qh_next; + hw_p = &here.qh->hw_next; + here = *prev; + } + /* link in this qh, unless some earlier pass did that */ + if (qh != here.qh) { + qh->qh_next = here; + if (here.qh) + qh->hw_next = *hw_p; + wmb(); + prev->qh = qh; + *hw_p = QH_NEXT(qh->qh_dma); + } + } + qh->qh_state = QH_STATE_LINKED; + qh_get(qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated += qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + /* maybe enable periodic schedule processing */ + if (!oxu->periodic_sched++) + return enable_periodic(oxu); + + return 0; +} + +static void qh_unlink_periodic(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned i; + unsigned period; + + /* FIXME: + * IF this isn't high speed + * and this qh is active in the current uframe + * (and overlay token SplitXstate is false?) + * THEN + * qh->hw_info1 |= __constant_cpu_to_le32(1 << 7 "ignore"); + */ + + /* high bandwidth, or otherwise part of every microframe */ + period = qh->period; + if (period == 0) + period = 1; + + for (i = qh->start; i < oxu->periodic_size; i += period) + periodic_unlink(oxu, i, qh); + + /* update per-qh bandwidth for usbfs */ + oxu_to_hcd(oxu)->self.bandwidth_allocated -= qh->period + ? ((qh->usecs + qh->c_usecs) / qh->period) + : (qh->usecs * 8); + + dev_dbg(&qh->dev->dev, + "unlink qh%d-%04x/%p start %d [%d/%d us]\n", + qh->period, + le32_to_cpup(&qh->hw_info2) & (QH_CMASK | QH_SMASK), + qh, qh->start, qh->usecs, qh->c_usecs); + + /* qh->qh_next still "live" to HC */ + qh->qh_state = QH_STATE_UNLINK; + qh->qh_next.ptr = NULL; + qh_put(qh); + + /* maybe turn off periodic schedule */ + oxu->periodic_sched--; + if (!oxu->periodic_sched) + (void) disable_periodic(oxu); +} + +static void intr_deschedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + unsigned wait; + + qh_unlink_periodic(oxu, qh); + + /* simple/paranoid: always delay, expecting the HC needs to read + * qh->hw_next or finish a writeback after SPLIT/CSPLIT ... and + * expect khubd to clean up after any CSPLITs we won't issue. + * active high speed queues may need bigger delays... + */ + if (list_empty(&qh->qtd_list) + || (__constant_cpu_to_le32(QH_CMASK) & qh->hw_info2) != 0) + wait = 2; + else + wait = 55; /* worst case: 3 * 1024 */ + + udelay(wait); + qh->qh_state = QH_STATE_IDLE; + qh->hw_next = EHCI_LIST_END; + wmb(); +} + +static int check_period(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + unsigned period, unsigned usecs) +{ + int claimed; + + /* complete split running into next frame? + * given FSTN support, we could sometimes check... + */ + if (uframe >= 8) + return 0; + + /* + * 80% periodic == 100 usec/uframe available + * convert "usecs we need" to "max already claimed" + */ + usecs = 100 - usecs; + + /* we "know" 2 and 4 uframe intervals were rejected; so + * for period 0, check _every_ microframe in the schedule. + */ + if (unlikely(period == 0)) { + do { + for (uframe = 0; uframe < 7; uframe++) { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } + } while ((frame += 1) < oxu->periodic_size); + + /* just check the specified uframe, at that period */ + } else { + do { + claimed = periodic_usecs(oxu, frame, uframe); + if (claimed > usecs) + return 0; + } while ((frame += period) < oxu->periodic_size); + } + + return 1; +} + +static int check_intr_schedule(struct oxu_hcd *oxu, + unsigned frame, unsigned uframe, + const struct ehci_qh *qh, __le32 *c_maskp) +{ + int retval = -ENOSPC; + + if (qh->c_usecs && uframe >= 6) /* FSTN territory? */ + goto done; + + if (!check_period(oxu, frame, uframe, qh->period, qh->usecs)) + goto done; + if (!qh->c_usecs) { + retval = 0; + *c_maskp = 0; + goto done; + } + +done: + return retval; +} + +/* "first fit" scheduling policy used the first time through, + * or when the previous schedule slot can't be re-used. + */ +static int qh_schedule(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + int status; + unsigned uframe; + __le32 c_mask; + unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ + + qh_refresh(oxu, qh); + qh->hw_next = EHCI_LIST_END; + frame = qh->start; + + /* reuse the previous schedule slots, if we can */ + if (frame < qh->period) { + uframe = ffs(le32_to_cpup(&qh->hw_info2) & QH_SMASK); + status = check_intr_schedule(oxu, frame, --uframe, + qh, &c_mask); + } else { + uframe = 0; + c_mask = 0; + status = -ENOSPC; + } + + /* else scan the schedule to find a group of slots such that all + * uframes have enough periodic bandwidth available. + */ + if (status) { + /* "normal" case, uframing flexible except with splits */ + if (qh->period) { + frame = qh->period - 1; + do { + for (uframe = 0; uframe < 8; uframe++) { + status = check_intr_schedule(oxu, + frame, uframe, qh, + &c_mask); + if (status == 0) + break; + } + } while (status && frame--); + + /* qh->period == 0 means every uframe */ + } else { + frame = 0; + status = check_intr_schedule(oxu, 0, 0, qh, &c_mask); + } + if (status) + goto done; + qh->start = frame; + + /* reset S-frame and (maybe) C-frame masks */ + qh->hw_info2 &= __constant_cpu_to_le32(~(QH_CMASK | QH_SMASK)); + qh->hw_info2 |= qh->period + ? cpu_to_le32(1 << uframe) + : __constant_cpu_to_le32(QH_SMASK); + qh->hw_info2 |= c_mask; + } else + oxu_dbg(oxu, "reused qh %p schedule\n", qh); + + /* stuff into the periodic schedule */ + status = qh_link_periodic(oxu, qh); +done: + return status; +} + +static int intr_submit(struct oxu_hcd *oxu, struct urb *urb, + struct list_head *qtd_list, gfp_t mem_flags) +{ + unsigned epnum; + unsigned long flags; + struct ehci_qh *qh; + int status = 0; + struct list_head empty; + + /* get endpoint and transfer/schedule data */ + epnum = urb->ep->desc.bEndpointAddress; + + spin_lock_irqsave(&oxu->lock, flags); + + if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, + &oxu_to_hcd(oxu)->flags))) { + status = -ESHUTDOWN; + goto done; + } + + /* get qh and force any scheduling errors */ + INIT_LIST_HEAD(&empty); + qh = qh_append_tds(oxu, urb, &empty, epnum, &urb->ep->hcpriv); + if (qh == NULL) { + status = -ENOMEM; + goto done; + } + if (qh->qh_state == QH_STATE_IDLE) { + status = qh_schedule(oxu, qh); + if (status != 0) + goto done; + } + + /* then queue the urb's tds to the qh */ + qh = qh_append_tds(oxu, urb, qtd_list, epnum, &urb->ep->hcpriv); + BUG_ON(qh == NULL); + + /* ... update usbfs periodic stats */ + oxu_to_hcd(oxu)->self.bandwidth_int_reqs++; + +done: + spin_unlock_irqrestore(&oxu->lock, flags); + if (status) + qtd_list_free(oxu, urb, qtd_list); + + return status; +} + +static inline int itd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "iso support is missing!\n"); + return -ENOSYS; +} + +static inline int sitd_submit(struct oxu_hcd *oxu, struct urb *urb, + gfp_t mem_flags) +{ + oxu_dbg(oxu, "split iso support is missing!\n"); + return -ENOSYS; +} + +static void scan_periodic(struct oxu_hcd *oxu) +{ + unsigned frame, clock, now_uframe, mod; + unsigned modified; + + mod = oxu->periodic_size << 3; + + /* + * When running, scan from last scan point up to "now" + * else clean up by scanning everything that's left. + * Touches as few pages as possible: cache-friendly. + */ + now_uframe = oxu->next_uframe; + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + clock = readl(&oxu->regs->frame_index); + else + clock = now_uframe + mod - 1; + clock %= mod; + + for (;;) { + union ehci_shadow q, *q_p; + __le32 type, *hw_p; + unsigned uframes; + + /* don't scan past the live uframe */ + frame = now_uframe >> 3; + if (frame == (clock >> 3)) + uframes = now_uframe & 0x07; + else { + /* safe to scan the whole frame at once */ + now_uframe |= 0x07; + uframes = 8; + } + +restart: + /* scan each element in frame's queue for completions */ + q_p = &oxu->pshadow[frame]; + hw_p = &oxu->periodic[frame]; + q.ptr = q_p->ptr; + type = Q_NEXT_TYPE(*hw_p); + modified = 0; + + while (q.ptr != NULL) { + union ehci_shadow temp; + int live; + + live = HC_IS_RUNNING(oxu_to_hcd(oxu)->state); + switch (type) { + case Q_TYPE_QH: + /* handle any completions */ + temp.qh = qh_get(q.qh); + type = Q_NEXT_TYPE(q.qh->hw_next); + q = q.qh->qh_next; + modified = qh_completions(oxu, temp.qh); + if (unlikely(list_empty(&temp.qh->qtd_list))) + intr_deschedule(oxu, temp.qh); + qh_put(temp.qh); + break; + default: + dbg("corrupt type %d frame %d shadow %p", + type, frame, q.ptr); + q.ptr = NULL; + } + + /* assume completion callbacks modify the queue */ + if (unlikely(modified)) + goto restart; + } + + /* Stop when we catch up to the HC */ + + /* FIXME: this assumes we won't get lapped when + * latencies climb; that should be rare, but... + * detect it, and just go all the way around. + * FLR might help detect this case, so long as latencies + * don't exceed periodic_size msec (default 1.024 sec). + */ + + /* FIXME: likewise assumes HC doesn't halt mid-scan */ + + if (now_uframe == clock) { + unsigned now; + + if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) + break; + oxu->next_uframe = now_uframe; + now = readl(&oxu->regs->frame_index) % mod; + if (now_uframe == now) + break; + + /* rescan the rest of this frame, then ... */ + clock = now; + } else { + now_uframe++; + now_uframe %= mod; + } + } +} + +/* On some systems, leaving remote wakeup enabled prevents system shutdown. + * The firmware seems to think that powering off is a wakeup event! + * This routine turns off remote wakeup and everything else, on all ports. + */ +static void ehci_turn_off_all_ports(struct oxu_hcd *oxu) +{ + int port = HCS_N_PORTS(oxu->hcs_params); + + while (port--) + writel(PORT_RWC_BITS, &oxu->regs->port_status[port]); +} + +static void ehci_port_power(struct oxu_hcd *oxu, int is_on) +{ + unsigned port; + + if (!HCS_PPC(oxu->hcs_params)) + return; + + oxu_dbg(oxu, "...power%s ports...\n", is_on ? "up" : "down"); + for (port = HCS_N_PORTS(oxu->hcs_params); port > 0; ) + (void) oxu_hub_control(oxu_to_hcd(oxu), + is_on ? SetPortFeature : ClearPortFeature, + USB_PORT_FEAT_POWER, + port--, NULL, 0); + msleep(20); +} + +/* Called from some interrupts, timers, and so on. + * It calls driver completion functions, after dropping oxu->lock. + */ +static void ehci_work(struct oxu_hcd *oxu) +{ + timer_action_done(oxu, TIMER_IO_WATCHDOG); + if (oxu->reclaim_ready) + end_unlink_async(oxu); + + /* another CPU may drop oxu->lock during a schedule scan while + * it reports urb completions. this flag guards against bogus + * attempts at re-entrant schedule scanning. + */ + if (oxu->scanning) + return; + oxu->scanning = 1; + scan_async(oxu); + if (oxu->next_uframe != -1) + scan_periodic(oxu); + oxu->scanning = 0; + + /* the IO watchdog guards against hardware or driver bugs that + * misplace IRQs, and should let us run completely without IRQs. + * such lossage has been observed on both VT6202 and VT8235. + */ + if (HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && + (oxu->async->qh_next.ptr != NULL || + oxu->periodic_sched != 0)) + timer_action(oxu, TIMER_IO_WATCHDOG); +} + +static void unlink_async(struct oxu_hcd *oxu, struct ehci_qh *qh) +{ + /* if we need to use IAA and it's busy, defer */ + if (qh->qh_state == QH_STATE_LINKED + && oxu->reclaim + && HC_IS_RUNNING(oxu_to_hcd(oxu)->state)) { + struct ehci_qh *last; + + for (last = oxu->reclaim; + last->reclaim; + last = last->reclaim) + continue; + qh->qh_state = QH_STATE_UNLINK_WAIT; + last->reclaim = qh; + + /* bypass IAA if the hc can't care */ + } else if (!HC_IS_RUNNING(oxu_to_hcd(oxu)->state) && oxu->reclaim) + end_unlink_async(oxu); + + /* something else might have unlinked the qh by now */ + if (qh->qh_state == QH_STATE_LINKED) + start_unlink_async(oxu, qh); +} + +/* + * USB host controller methods + */ + +static irqreturn_t oxu210_hcd_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 status, pcd_status = 0; + int bh; + + spin_lock(&oxu->lock); + + status = readl(&oxu->regs->status); + + /* e.g. cardbus physical eject */ + if (status == ~(u32) 0) { + oxu_dbg(oxu, "device removed\n"); + goto dead; + } + + status &= INTR_MASK; + if (!status) { /* irq sharing? */ + spin_unlock(&oxu->lock); + return IRQ_NONE; + } + + /* clear (just) interrupts */ + writel(status, &oxu->regs->status); + readl(&oxu->regs->command); /* unblock posted write */ + bh = 0; + +#ifdef OXU_VERBOSE_DEBUG + /* unrequested/ignored: Frame List Rollover */ + dbg_status(oxu, "irq", status); +#endif + + /* INT, ERR, and IAA interrupt rates can be throttled */ + + /* normal [4.15.1.2] or error [4.15.1.1] completion */ + if (likely((status & (STS_INT|STS_ERR)) != 0)) + bh = 1; + + /* complete the unlinking of some qh [4.15.2.3] */ + if (status & STS_IAA) { + oxu->reclaim_ready = 1; + bh = 1; + } + + /* remote wakeup [4.3.1] */ + if (status & STS_PCD) { + unsigned i = HCS_N_PORTS(oxu->hcs_params); + pcd_status = status; + + /* resume root hub? */ + if (!(readl(&oxu->regs->command) & CMD_RUN)) + usb_hcd_resume_root_hub(hcd); + + while (i--) { + int pstatus = readl(&oxu->regs->port_status[i]); + + if (pstatus & PORT_OWNER) + continue; + if (!(pstatus & PORT_RESUME) + || oxu->reset_done[i] != 0) + continue; + + /* start 20 msec resume signaling from this port, + * and make khubd collect PORT_STAT_C_SUSPEND to + * stop that signaling. + */ + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + oxu_dbg(oxu, "port %d remote wakeup\n", i + 1); + mod_timer(&hcd->rh_timer, oxu->reset_done[i]); + } + } + + /* PCI errors [4.15.2.4] */ + if (unlikely((status & STS_FATAL) != 0)) { + /* bogus "fatal" IRQs appear on some chips... why? */ + status = readl(&oxu->regs->status); + dbg_cmd(oxu, "fatal", readl(&oxu->regs->command)); + dbg_status(oxu, "fatal", status); + if (status & STS_HALT) { + oxu_err(oxu, "fatal error\n"); +dead: + ehci_reset(oxu); + writel(0, &oxu->regs->configured_flag); + /* generic layer kills/unlinks all urbs, then + * uses oxu_stop to clean up the rest + */ + bh = 1; + } + } + + if (bh) + ehci_work(oxu); + spin_unlock(&oxu->lock); + if (pcd_status & STS_PCD) + usb_hcd_poll_rh_status(hcd); + return IRQ_HANDLED; +} + +static irqreturn_t oxu_irq(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret = IRQ_HANDLED; + + u32 status = oxu_readl(hcd->regs, OXU_CHIPIRQSTATUS); + u32 enable = oxu_readl(hcd->regs, OXU_CHIPIRQEN_SET); + + /* Disable all interrupt */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_CLR, enable); + + if ((oxu->is_otg && (status & OXU_USBOTGI)) || + (!oxu->is_otg && (status & OXU_USBSPHI))) + oxu210_hcd_irq(hcd); + else + ret = IRQ_NONE; + + /* Enable all interrupt back */ + oxu_writel(hcd->regs, OXU_CHIPIRQEN_SET, enable); + + return ret; +} + +static void oxu_watchdog(unsigned long param) +{ + struct oxu_hcd *oxu = (struct oxu_hcd *) param; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + + /* lost IAA irqs wedge things badly; seen with a vt8235 */ + if (oxu->reclaim) { + u32 status = readl(&oxu->regs->status); + if (status & STS_IAA) { + oxu_vdbg(oxu, "lost IAA\n"); + writel(STS_IAA, &oxu->regs->status); + oxu->reclaim_ready = 1; + } + } + + /* stop async processing after it's idled a bit */ + if (test_bit(TIMER_ASYNC_OFF, &oxu->actions)) + start_unlink_async(oxu, oxu->async); + + /* oxu could run by timer, without IRQs ... */ + ehci_work(oxu); + + spin_unlock_irqrestore(&oxu->lock, flags); +} + +/* One-time init, only for memory state. + */ +static int oxu_hcd_init(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int retval; + u32 hcc_params; + + spin_lock_init(&oxu->lock); + + init_timer(&oxu->watchdog); + oxu->watchdog.function = oxu_watchdog; + oxu->watchdog.data = (unsigned long) oxu; + + /* + * hw default: 1K periodic list heads, one per frame. + * periodic_size can shrink by USBCMD update if hcc_params allows. + */ + oxu->periodic_size = DEFAULT_I_TDPS; + retval = ehci_mem_init(oxu, GFP_KERNEL); + if (retval < 0) + return retval; + + /* controllers may cache some of the periodic schedule ... */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_ISOC_CACHE(hcc_params)) /* full frame cache */ + oxu->i_thresh = 8; + else /* N microframes cached */ + oxu->i_thresh = 2 + HCC_ISOC_THRES(hcc_params); + + oxu->reclaim = NULL; + oxu->reclaim_ready = 0; + oxu->next_uframe = -1; + + /* + * dedicate a qh for the async ring head, since we couldn't unlink + * a 'real' qh without stopping the async schedule [4.8]. use it + * as the 'reclamation list head' too. + * its dummy is used in hw_alt_next of many tds, to prevent the qh + * from automatically advancing to the next td after short reads. + */ + oxu->async->qh_next.qh = NULL; + oxu->async->hw_next = QH_NEXT(oxu->async->qh_dma); + oxu->async->hw_info1 = cpu_to_le32(QH_HEAD); + oxu->async->hw_token = cpu_to_le32(QTD_STS_HALT); + oxu->async->hw_qtd_next = EHCI_LIST_END; + oxu->async->qh_state = QH_STATE_LINKED; + oxu->async->hw_alt_next = QTD_NEXT(oxu->async->dummy->qtd_dma); + + /* clear interrupt enables, set irq latency */ + if (log2_irq_thresh < 0 || log2_irq_thresh > 6) + log2_irq_thresh = 0; + temp = 1 << (16 + log2_irq_thresh); + if (HCC_CANPARK(hcc_params)) { + /* HW default park == 3, on hardware that supports it (like + * NVidia and ALI silicon), maximizes throughput on the async + * schedule by avoiding QH fetches between transfers. + * + * With fast usb storage devices and NForce2, "park" seems to + * make problems: throughput reduction (!), data errors... + */ + if (park) { + park = min(park, (unsigned) 3); + temp |= CMD_PARK; + temp |= park << 8; + } + oxu_dbg(oxu, "park %d\n", park); + } + if (HCC_PGM_FRAMELISTLEN(hcc_params)) { + /* periodic schedule size can be smaller than default */ + temp &= ~(3 << 2); + temp |= (EHCI_TUNE_FLS << 2); + } + oxu->command = temp; + + return 0; +} + +/* Called during probe() after chip reset completes. + */ +static int oxu_reset(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ret; + + spin_lock_init(&oxu->mem_lock); + INIT_LIST_HEAD(&oxu->urb_list); + oxu->urb_len = 0; + + /* FIMXE */ + hcd->self.controller->dma_mask = 0UL; + + if (oxu->is_otg) { + oxu->caps = hcd->regs + OXU_OTG_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_OTG_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_SPH_MEM; + } else { + oxu->caps = hcd->regs + OXU_SPH_CAP_OFFSET; + oxu->regs = hcd->regs + OXU_SPH_CAP_OFFSET + \ + HC_LENGTH(readl(&oxu->caps->hc_capbase)); + + oxu->mem = hcd->regs + OXU_OTG_MEM; + } + + oxu->hcs_params = readl(&oxu->caps->hcs_params); + oxu->sbrn = 0x20; + + ret = oxu_hcd_init(hcd); + if (ret) + return ret; + + return 0; +} + +static int oxu_run(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int retval; + u32 temp, hcc_params; + + hcd->uses_new_polling = 1; + hcd->poll_rh = 0; + + /* EHCI spec section 4.1 */ + retval = ehci_reset(oxu); + if (retval != 0) { + ehci_mem_cleanup(oxu); + return retval; + } + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* hcc_params controls whether oxu->regs->segment must (!!!) + * be used; it constrains QH/ITD/SITD and QTD locations. + * pci_pool consistent memory always uses segment zero. + * streaming mappings for I/O buffers, like pci_map_single(), + * can return segments above 4GB, if the device allows. + * + * NOTE: the dma mask is visible through dma_supported(), so + * drivers can pass this info along ... like NETIF_F_HIGHDMA, + * Scsi_Host.highmem_io, and so forth. It's readonly to all + * host side drivers though. + */ + hcc_params = readl(&oxu->caps->hcc_params); + if (HCC_64BIT_ADDR(hcc_params)) + writel(0, &oxu->regs->segment); + + oxu->command &= ~(CMD_LRESET | CMD_IAAD | CMD_PSE | + CMD_ASE | CMD_RESET); + oxu->command |= CMD_RUN; + writel(oxu->command, &oxu->regs->command); + dbg_cmd(oxu, "init", oxu->command); + + /* + * Start, enabling full USB 2.0 functionality ... usb 1.1 devices + * are explicitly handed to companion controller(s), so no TT is + * involved with the root hub. (Except where one is integrated, + * and there's no companion controller unless maybe for USB OTG.) + */ + hcd->state = HC_STATE_RUNNING; + writel(FLAG_CF, &oxu->regs->configured_flag); + readl(&oxu->regs->command); /* unblock posted writes */ + + temp = HC_VERSION(readl(&oxu->caps->hc_capbase)); + oxu_info(oxu, "USB %x.%x started, quasi-EHCI %x.%02x, driver %s%s\n", + ((oxu->sbrn & 0xf0)>>4), (oxu->sbrn & 0x0f), + temp >> 8, temp & 0xff, DRIVER_VERSION, + ignore_oc ? ", overcurrent ignored" : ""); + + writel(INTR_MASK, &oxu->regs->intr_enable); /* Turn On Interrupts */ + + return 0; +} + +static void oxu_stop(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + /* Turn off port power on all root hub ports. */ + ehci_port_power(oxu, 0); + + /* no more interrupts ... */ + del_timer_sync(&oxu->watchdog); + + spin_lock_irq(&oxu->lock); + if (HC_IS_RUNNING(hcd->state)) + ehci_quiesce(oxu); + + ehci_reset(oxu); + writel(0, &oxu->regs->intr_enable); + spin_unlock_irq(&oxu->lock); + + /* let companion controllers work when we aren't */ + writel(0, &oxu->regs->configured_flag); + + /* root hub is shut down separately (first, when possible) */ + spin_lock_irq(&oxu->lock); + if (oxu->async) + ehci_work(oxu); + spin_unlock_irq(&oxu->lock); + ehci_mem_cleanup(oxu); + + dbg_status(oxu, "oxu_stop completed", readl(&oxu->regs->status)); +} + +/* Kick in for silicon on any bus (not just pci, etc). + * This forcibly disables dma and IRQs, helping kexec and other cases + * where the next system software may expect clean state. + */ +static void oxu_shutdown(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + (void) ehci_halt(oxu); + ehci_turn_off_all_ports(oxu); + + /* make BIOS/etc use companion controller during reboot */ + writel(0, &oxu->regs->configured_flag); + + /* unblock posted writes */ + readl(&oxu->regs->configured_flag); +} + +/* Non-error returns are a promise to giveback() the urb later + * we drop ownership so next owner (or urb unlink) can get it + * + * urb + dev is in hcd.self.controller.urb_list + * we're queueing TDs onto software and hardware lists + * + * hcd-specific init for hcpriv hasn't been done yet + * + * NOTE: control, bulk, and interrupt share the same code to append TDs + * to a (possibly active) QH, and the same QH scanning code. + */ +static int __oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct list_head qtd_list; + + INIT_LIST_HEAD(&qtd_list); + + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return submit_async(oxu, urb, &qtd_list, mem_flags); + + case PIPE_INTERRUPT: + if (!qh_urb_transaction(oxu, urb, &qtd_list, mem_flags)) + return -ENOMEM; + return intr_submit(oxu, urb, &qtd_list, mem_flags); + + case PIPE_ISOCHRONOUS: + if (urb->dev->speed == USB_SPEED_HIGH) + return itd_submit(oxu, urb, mem_flags); + else + return sitd_submit(oxu, urb, mem_flags); + } +} + +/* This function is responsible for breaking URBs with big data size + * into smaller size and processing small urbs in sequence. + */ +static int oxu_urb_enqueue(struct usb_hcd *hcd, struct urb *urb, + gfp_t mem_flags) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int num, rem; + int transfer_buffer_length; + void *transfer_buffer; + struct urb *murb; + int i, ret; + + /* If not bulk pipe just enqueue the URB */ + if (!usb_pipebulk(urb->pipe)) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Otherwise we should verify the USB transfer buffer size! */ + transfer_buffer = urb->transfer_buffer; + transfer_buffer_length = urb->transfer_buffer_length; + + num = urb->transfer_buffer_length / 4096; + rem = urb->transfer_buffer_length % 4096; + if (rem != 0) + num++; + + /* If URB is smaller than 4096 bytes just enqueue it! */ + if (num == 1) + return __oxu_urb_enqueue(hcd, urb, mem_flags); + + /* Ok, we have more job to do! :) */ + + for (i = 0; i < num - 1; i++) { + /* Get free micro URB poll till a free urb is recieved */ + + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = 4096; + murb->transfer_buffer = transfer_buffer + i * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 0; + + /* This loop is to guarantee urb to be processed when there's + * not enough resources at a particular time by retrying. + */ + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + } + + /* Last urb requires special handling */ + + /* Get free micro URB poll till a free urb is recieved */ + do { + murb = (struct urb *) oxu_murb_alloc(oxu); + if (!murb) + schedule(); + } while (!murb); + + /* Coping the urb */ + memcpy(murb, urb, sizeof(struct urb)); + + murb->transfer_buffer_length = rem > 0 ? rem : 4096; + murb->transfer_buffer = transfer_buffer + (num - 1) * 4096; + + /* Null pointer for the encodes that this is a micro urb */ + murb->complete = NULL; + + ((struct oxu_murb *) murb)->main = urb; + ((struct oxu_murb *) murb)->last = 1; + + do { + ret = __oxu_urb_enqueue(hcd, murb, mem_flags); + if (ret) + schedule(); + } while (ret); + + return ret; +} + +/* Remove from hardware lists. + * Completions normally happen asynchronously + */ +static int oxu_urb_dequeue(struct usb_hcd *hcd, struct urb *urb, int status) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + struct ehci_qh *qh; + unsigned long flags; + + spin_lock_irqsave(&oxu->lock, flags); + switch (usb_pipetype(urb->pipe)) { + case PIPE_CONTROL: + case PIPE_BULK: + default: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + unlink_async(oxu, qh); + break; + + case PIPE_INTERRUPT: + qh = (struct ehci_qh *) urb->hcpriv; + if (!qh) + break; + switch (qh->qh_state) { + case QH_STATE_LINKED: + intr_deschedule(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_IDLE: + qh_completions(oxu, qh); + break; + default: + oxu_dbg(oxu, "bogus qh %p state %d\n", + qh, qh->qh_state); + goto done; + } + + /* reschedule QH iff another request is queued */ + if (!list_empty(&qh->qtd_list) + && HC_IS_RUNNING(hcd->state)) { + int status; + + status = qh_schedule(oxu, qh); + spin_unlock_irqrestore(&oxu->lock, flags); + + if (status != 0) { + /* shouldn't happen often, but ... + * FIXME kill those tds' urbs + */ + err("can't reschedule qh %p, err %d", + qh, status); + } + return status; + } + break; + } +done: + spin_unlock_irqrestore(&oxu->lock, flags); + return 0; +} + +/* Bulk qh holds the data toggle */ +static void oxu_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *ep) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + unsigned long flags; + struct ehci_qh *qh, *tmp; + + /* ASSERT: any requests/urbs are being unlinked */ + /* ASSERT: nobody can be submitting urbs for this any more */ + +rescan: + spin_lock_irqsave(&oxu->lock, flags); + qh = ep->hcpriv; + if (!qh) + goto done; + + /* endpoints can be iso streams. for now, we don't + * accelerate iso completions ... so spin a while. + */ + if (qh->hw_info1 == 0) { + oxu_vdbg(oxu, "iso delay\n"); + goto idle_timeout; + } + + if (!HC_IS_RUNNING(hcd->state)) + qh->qh_state = QH_STATE_IDLE; + switch (qh->qh_state) { + case QH_STATE_LINKED: + for (tmp = oxu->async->qh_next.qh; + tmp && tmp != qh; + tmp = tmp->qh_next.qh) + continue; + /* periodic qh self-unlinks on empty */ + if (!tmp) + goto nogood; + unlink_async(oxu, qh); + /* FALL THROUGH */ + case QH_STATE_UNLINK: /* wait for hw to finish? */ +idle_timeout: + spin_unlock_irqrestore(&oxu->lock, flags); + schedule_timeout_uninterruptible(1); + goto rescan; + case QH_STATE_IDLE: /* fully unlinked */ + if (list_empty(&qh->qtd_list)) { + qh_put(qh); + break; + } + /* else FALL THROUGH */ + default: +nogood: + /* caller was supposed to have unlinked any requests; + * that's not our job. just leak this memory. + */ + oxu_err(oxu, "qh %p (#%02x) state %d%s\n", + qh, ep->desc.bEndpointAddress, qh->qh_state, + list_empty(&qh->qtd_list) ? "" : "(has tds)"); + break; + } + ep->hcpriv = NULL; +done: + spin_unlock_irqrestore(&oxu->lock, flags); + return; +} + +static int oxu_get_frame(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + + return (readl(&oxu->regs->frame_index) >> 3) % + oxu->periodic_size; +} + +/* Build "status change" packet (one or two bytes) from HC registers */ +static int oxu_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp, mask, status = 0; + int ports, i, retval = 1; + unsigned long flags; + + /* if !USB_SUSPEND, root hub timers won't get shut down ... */ + if (!HC_IS_RUNNING(hcd->state)) + return 0; + + /* init status to no-changes */ + buf[0] = 0; + ports = HCS_N_PORTS(oxu->hcs_params); + if (ports > 7) { + buf[1] = 0; + retval++; + } + + /* Some boards (mostly VIA?) report bogus overcurrent indications, + * causing massive log spam unless we completely ignore them. It + * may be relevant that VIA VT8235 controlers, where PORT_POWER is + * always set, seem to clear PORT_OCC and PORT_CSC when writing to + * PORT_POWER; that's surprising, but maybe within-spec. + */ + if (!ignore_oc) + mask = PORT_CSC | PORT_PEC | PORT_OCC; + else + mask = PORT_CSC | PORT_PEC; + + /* no hub change reports (bit 0) for now (power, ...) */ + + /* port N changes (bit N)? */ + spin_lock_irqsave(&oxu->lock, flags); + for (i = 0; i < ports; i++) { + temp = readl(&oxu->regs->port_status[i]); + + /* + * Return status information even for ports with OWNER set. + * Otherwise khubd wouldn't see the disconnect event when a + * high-speed device is switched over to the companion + * controller by the user. + */ + + if (!(temp & PORT_CONNECT)) + oxu->reset_done[i] = 0; + if ((temp & mask) != 0 || ((temp & PORT_RESUME) != 0 && + time_after_eq(jiffies, oxu->reset_done[i]))) { + if (i < 7) + buf[0] |= 1 << (i + 1); + else + buf[1] |= 1 << (i - 7); + status = STS_PCD; + } + } + /* FIXME autosuspend idle root hubs */ + spin_unlock_irqrestore(&oxu->lock, flags); + return status ? retval : 0; +} + +/* Returns the speed of a device attached to a port on the root hub. */ +static inline unsigned int oxu_port_speed(struct oxu_hcd *oxu, + unsigned int portsc) +{ + switch ((portsc >> 26) & 3) { + case 0: + return 0; + case 1: + return 1 << USB_PORT_FEAT_LOWSPEED; + case 2: + default: + return 1 << USB_PORT_FEAT_HIGHSPEED; + } +} + +#define PORT_WAKE_BITS (PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E) +static int oxu_hub_control(struct usb_hcd *hcd, u16 typeReq, + u16 wValue, u16 wIndex, char *buf, u16 wLength) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int ports = HCS_N_PORTS(oxu->hcs_params); + u32 __iomem *status_reg = &oxu->regs->port_status[wIndex - 1]; + u32 temp, status; + unsigned long flags; + int retval = 0; + unsigned selector; + + /* + * FIXME: support SetPortFeatures USB_PORT_FEAT_INDICATOR. + * HCS_INDICATOR may say we can change LEDs to off/amber/green. + * (track current state ourselves) ... blink for diagnostics, + * power, "this is the one", etc. EHCI spec supports this. + */ + + spin_lock_irqsave(&oxu->lock, flags); + switch (typeReq) { + case ClearHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + + /* + * Even if OWNER is set, so the port is owned by the + * companion controller, khubd needs to be able to clear + * the port-change status bits (especially + * USB_PORT_FEAT_C_CONNECTION). + */ + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + writel(temp & ~PORT_PE, status_reg); + break; + case USB_PORT_FEAT_C_ENABLE: + writel((temp & ~PORT_RWC_BITS) | PORT_PEC, status_reg); + break; + case USB_PORT_FEAT_SUSPEND: + if (temp & PORT_RESET) + goto error; + if (temp & PORT_SUSPEND) { + if ((temp & PORT_PE) == 0) + goto error; + /* resume signaling for 20 msec */ + temp &= ~(PORT_RWC_BITS | PORT_WAKE_BITS); + writel(temp | PORT_RESUME, status_reg); + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + } + break; + case USB_PORT_FEAT_C_SUSPEND: + /* we auto-clear this feature */ + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp & ~(PORT_RWC_BITS | PORT_POWER), + status_reg); + break; + case USB_PORT_FEAT_C_CONNECTION: + writel((temp & ~PORT_RWC_BITS) | PORT_CSC, status_reg); + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + writel((temp & ~PORT_RWC_BITS) | PORT_OCC, status_reg); + break; + case USB_PORT_FEAT_C_RESET: + /* GetPortStatus clears reset */ + break; + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted write */ + break; + case GetHubDescriptor: + ehci_hub_descriptor(oxu, (struct usb_hub_descriptor *) + buf); + break; + case GetHubStatus: + /* no hub-wide feature/status flags */ + memset(buf, 0, 4); + break; + case GetPortStatus: + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + status = 0; + temp = readl(status_reg); + + /* wPortChange bits */ + if (temp & PORT_CSC) + status |= 1 << USB_PORT_FEAT_C_CONNECTION; + if (temp & PORT_PEC) + status |= 1 << USB_PORT_FEAT_C_ENABLE; + if ((temp & PORT_OCC) && !ignore_oc) + status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + + /* whoever resumes must GetPortStatus to complete it!! */ + if (temp & PORT_RESUME) { + + /* Remote Wakeup received? */ + if (!oxu->reset_done[wIndex]) { + /* resume signaling for 20 msec */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(20); + /* check the port again */ + mod_timer(&oxu_to_hcd(oxu)->rh_timer, + oxu->reset_done[wIndex]); + } + + /* resume completed? */ + else if (time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= 1 << USB_PORT_FEAT_C_SUSPEND; + oxu->reset_done[wIndex] = 0; + + /* stop resume signaling */ + temp = readl(status_reg); + writel(temp & ~(PORT_RWC_BITS | PORT_RESUME), + status_reg); + retval = handshake(oxu, status_reg, + PORT_RESUME, 0, 2000 /* 2msec */); + if (retval != 0) { + oxu_err(oxu, + "port %d resume error %d\n", + wIndex + 1, retval); + goto error; + } + temp &= ~(PORT_SUSPEND|PORT_RESUME|(3<<10)); + } + } + + /* whoever resets must GetPortStatus to complete it!! */ + if ((temp & PORT_RESET) + && time_after_eq(jiffies, + oxu->reset_done[wIndex])) { + status |= 1 << USB_PORT_FEAT_C_RESET; + oxu->reset_done[wIndex] = 0; + + /* force reset to complete */ + writel(temp & ~(PORT_RWC_BITS | PORT_RESET), + status_reg); + /* REVISIT: some hardware needs 550+ usec to clear + * this bit; seems too long to spin routinely... + */ + retval = handshake(oxu, status_reg, + PORT_RESET, 0, 750); + if (retval != 0) { + oxu_err(oxu, "port %d reset error %d\n", + wIndex + 1, retval); + goto error; + } + + /* see what we found out */ + temp = check_reset_complete(oxu, wIndex, status_reg, + readl(status_reg)); + } + + /* transfer dedicated ports to the companion hc */ + if ((temp & PORT_CONNECT) && + test_bit(wIndex, &oxu->companion_ports)) { + temp &= ~PORT_RWC_BITS; + temp |= PORT_OWNER; + writel(temp, status_reg); + oxu_dbg(oxu, "port %d --> companion\n", wIndex + 1); + temp = readl(status_reg); + } + + /* + * Even if OWNER is set, there's no harm letting khubd + * see the wPortStatus values (they should all be 0 except + * for PORT_POWER anyway). + */ + + if (temp & PORT_CONNECT) { + status |= 1 << USB_PORT_FEAT_CONNECTION; + /* status may be from integrated TT */ + status |= oxu_port_speed(oxu, temp); + } + if (temp & PORT_PE) + status |= 1 << USB_PORT_FEAT_ENABLE; + if (temp & (PORT_SUSPEND|PORT_RESUME)) + status |= 1 << USB_PORT_FEAT_SUSPEND; + if (temp & PORT_OC) + status |= 1 << USB_PORT_FEAT_OVER_CURRENT; + if (temp & PORT_RESET) + status |= 1 << USB_PORT_FEAT_RESET; + if (temp & PORT_POWER) + status |= 1 << USB_PORT_FEAT_POWER; + +#ifndef OXU_VERBOSE_DEBUG + if (status & ~0xffff) /* only if wPortChange is interesting */ +#endif + dbg_port(oxu, "GetStatus", wIndex + 1, temp); + put_unaligned(cpu_to_le32(status), (__le32 *) buf); + break; + case SetHubFeature: + switch (wValue) { + case C_HUB_LOCAL_POWER: + case C_HUB_OVER_CURRENT: + /* no hub-wide feature/status flags */ + break; + default: + goto error; + } + break; + case SetPortFeature: + selector = wIndex >> 8; + wIndex &= 0xff; + if (!wIndex || wIndex > ports) + goto error; + wIndex--; + temp = readl(status_reg); + if (temp & PORT_OWNER) + break; + + temp &= ~PORT_RWC_BITS; + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + if ((temp & PORT_PE) == 0 + || (temp & PORT_RESET) != 0) + goto error; + if (device_may_wakeup(&hcd->self.root_hub->dev)) + temp |= PORT_WAKE_BITS; + writel(temp | PORT_SUSPEND, status_reg); + break; + case USB_PORT_FEAT_POWER: + if (HCS_PPC(oxu->hcs_params)) + writel(temp | PORT_POWER, status_reg); + break; + case USB_PORT_FEAT_RESET: + if (temp & PORT_RESUME) + goto error; + /* line status bits may report this as low speed, + * which can be fine if this root hub has a + * transaction translator built in. + */ + oxu_vdbg(oxu, "port %d reset\n", wIndex + 1); + temp |= PORT_RESET; + temp &= ~PORT_PE; + + /* + * caller must wait, then call GetPortStatus + * usb 2.0 spec says 50 ms resets on root + */ + oxu->reset_done[wIndex] = jiffies + + msecs_to_jiffies(50); + writel(temp, status_reg); + break; + + /* For downstream facing ports (these): one hub port is put + * into test mode according to USB2 11.24.2.13, then the hub + * must be reset (which for root hub now means rmmod+modprobe, + * or else system reboot). See EHCI 2.3.9 and 4.14 for info + * about the EHCI-specific stuff. + */ + case USB_PORT_FEAT_TEST: + if (!selector || selector > 5) + goto error; + ehci_quiesce(oxu); + ehci_halt(oxu); + temp |= selector << 16; + writel(temp, status_reg); + break; + + default: + goto error; + } + readl(&oxu->regs->command); /* unblock posted writes */ + break; + + default: +error: + /* "stall" on error */ + retval = -EPIPE; + } + spin_unlock_irqrestore(&oxu->lock, flags); + return retval; +} + +#ifdef CONFIG_PM + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + int port; + int mask; + + oxu_dbg(oxu, "suspend root hub\n"); + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + + port = HCS_N_PORTS(oxu->hcs_params); + spin_lock_irq(&oxu->lock); + + /* stop schedules, clean any completed work */ + if (HC_IS_RUNNING(hcd->state)) { + ehci_quiesce(oxu); + hcd->state = HC_STATE_QUIESCING; + } + oxu->command = readl(&oxu->regs->command); + if (oxu->reclaim) + oxu->reclaim_ready = 1; + ehci_work(oxu); + + /* Unlike other USB host controller types, EHCI doesn't have + * any notion of "global" or bus-wide suspend. The driver has + * to manually suspend all the active unsuspended ports, and + * then manually resume them in the bus_resume() routine. + */ + oxu->bus_suspended = 0; + while (port--) { + u32 __iomem *reg = &oxu->regs->port_status[port]; + u32 t1 = readl(reg) & ~PORT_RWC_BITS; + u32 t2 = t1; + + /* keep track of which ports we suspend */ + if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && + !(t1 & PORT_SUSPEND)) { + t2 |= PORT_SUSPEND; + set_bit(port, &oxu->bus_suspended); + } + + /* enable remote wakeup on all ports */ + if (device_may_wakeup(&hcd->self.root_hub->dev)) + t2 |= PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E; + else + t2 &= ~(PORT_WKOC_E|PORT_WKDISC_E|PORT_WKCONN_E); + + if (t1 != t2) { + oxu_vdbg(oxu, "port %d, %08x -> %08x\n", + port + 1, t1, t2); + writel(t2, reg); + } + } + + /* turn off now-idle HC */ + del_timer_sync(&oxu->watchdog); + ehci_halt(oxu); + hcd->state = HC_STATE_SUSPENDED; + + /* allow remote wakeup */ + mask = INTR_MASK; + if (!device_may_wakeup(&hcd->self.root_hub->dev)) + mask &= ~STS_PCD; + writel(mask, &oxu->regs->intr_enable); + readl(&oxu->regs->intr_enable); + + oxu->next_statechange = jiffies + msecs_to_jiffies(10); + spin_unlock_irq(&oxu->lock); + return 0; +} + +/* Caller has locked the root hub, and should reset/reinit on error */ +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + struct oxu_hcd *oxu = hcd_to_oxu(hcd); + u32 temp; + int i; + + if (time_before(jiffies, oxu->next_statechange)) + msleep(5); + spin_lock_irq(&oxu->lock); + + /* Ideally and we've got a real resume here, and no port's power + * was lost. (For PCI, that means Vaux was maintained.) But we + * could instead be restoring a swsusp snapshot -- so that BIOS was + * the last user of the controller, not reset/pm hardware keeping + * state we gave to it. + */ + temp = readl(&oxu->regs->intr_enable); + oxu_dbg(oxu, "resume root hub%s\n", temp ? "" : " after power loss"); + + /* at least some APM implementations will try to deliver + * IRQs right away, so delay them until we're ready. + */ + writel(0, &oxu->regs->intr_enable); + + /* re-init operational registers */ + writel(0, &oxu->regs->segment); + writel(oxu->periodic_dma, &oxu->regs->frame_list); + writel((u32) oxu->async->qh_dma, &oxu->regs->async_next); + + /* restore CMD_RUN, framelist size, and irq threshold */ + writel(oxu->command, &oxu->regs->command); + + /* Some controller/firmware combinations need a delay during which + * they set up the port statuses. See Bugzilla #8190. */ + mdelay(8); + + /* manually resume the ports we suspended during bus_suspend() */ + i = HCS_N_PORTS(oxu->hcs_params); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + temp &= ~(PORT_RWC_BITS + | PORT_WKOC_E | PORT_WKDISC_E | PORT_WKCONN_E); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + oxu->reset_done[i] = jiffies + msecs_to_jiffies(20); + temp |= PORT_RESUME; + } + writel(temp, &oxu->regs->port_status[i]); + } + i = HCS_N_PORTS(oxu->hcs_params); + mdelay(20); + while (i--) { + temp = readl(&oxu->regs->port_status[i]); + if (test_bit(i, &oxu->bus_suspended) && (temp & PORT_SUSPEND)) { + temp &= ~(PORT_RWC_BITS | PORT_RESUME); + writel(temp, &oxu->regs->port_status[i]); + oxu_vdbg(oxu, "resumed port %d\n", i + 1); + } + } + (void) readl(&oxu->regs->command); + + /* maybe re-activate the schedule(s) */ + temp = 0; + if (oxu->async->qh_next.qh) + temp |= CMD_ASE; + if (oxu->periodic_sched) + temp |= CMD_PSE; + if (temp) { + oxu->command |= temp; + writel(oxu->command, &oxu->regs->command); + } + + oxu->next_statechange = jiffies + msecs_to_jiffies(5); + hcd->state = HC_STATE_RUNNING; + + /* Now we can safely re-enable irqs */ + writel(INTR_MASK, &oxu->regs->intr_enable); + + spin_unlock_irq(&oxu->lock); + return 0; +} + +#else + +static int oxu_bus_suspend(struct usb_hcd *hcd) +{ + return 0; +} + +static int oxu_bus_resume(struct usb_hcd *hcd) +{ + return 0; +} + +#endif /* CONFIG_PM */ + +static const struct hc_driver oxu_hc_driver = { + .description = "oxu210hp_hcd", + .product_desc = "oxu210hp HCD", + .hcd_priv_size = sizeof(struct oxu_hcd), + + /* + * Generic hardware linkage + */ + .irq = oxu_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * Basic lifecycle operations + */ + .reset = oxu_reset, + .start = oxu_run, + .stop = oxu_stop, + .shutdown = oxu_shutdown, + + /* + * Managing i/o requests and associated device resources + */ + .urb_enqueue = oxu_urb_enqueue, + .urb_dequeue = oxu_urb_dequeue, + .endpoint_disable = oxu_endpoint_disable, + + /* + * Scheduling support + */ + .get_frame_number = oxu_get_frame, + + /* + * Root hub support + */ + .hub_status_data = oxu_hub_status_data, + .hub_control = oxu_hub_control, + .bus_suspend = oxu_bus_suspend, + .bus_resume = oxu_bus_resume, +}; + +/* + * Module stuff + */ + +static void oxu_configuration(struct platform_device *pdev, void *base) +{ + u32 tmp; + + /* Initialize top level registers. + * First write ever + */ + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + oxu_writel(base, OXU_SOFTRESET, OXU_SRESET); + oxu_writel(base, OXU_HOSTIFCONFIG, 0x0000037D); + + tmp = oxu_readl(base, OXU_PIOBURSTREADCTRL); + oxu_writel(base, OXU_PIOBURSTREADCTRL, tmp | 0x0040); + + oxu_writel(base, OXU_ASO, OXU_SPHPOEN | OXU_OVRCCURPUPDEN | + OXU_COMPARATOR | OXU_ASO_OP); + + tmp = oxu_readl(base, OXU_CLKCTRL_SET); + oxu_writel(base, OXU_CLKCTRL_SET, tmp | OXU_SYSCLKEN | OXU_USBOTGCLKEN); + + /* Clear all top interrupt enable */ + oxu_writel(base, OXU_CHIPIRQEN_CLR, 0xff); + + /* Clear all top interrupt status */ + oxu_writel(base, OXU_CHIPIRQSTATUS, 0xff); + + /* Enable all needed top interrupt except OTG SPH core */ + oxu_writel(base, OXU_CHIPIRQEN_SET, OXU_USBSPHLPWUI | OXU_USBOTGLPWUI); +} + +static int oxu_verify_id(struct platform_device *pdev, void *base) +{ + u32 id; + char *bo[] = { + "reserved", + "128-pin LQFP", + "84-pin TFBGA", + "reserved", + }; + + /* Read controller signature register to find a match */ + id = oxu_readl(base, OXU_DEVICEID); + dev_info(&pdev->dev, "device ID %x\n", id); + if ((id & OXU_REV_MASK) != (OXU_REV_2100 << OXU_REV_SHIFT)) + return -1; + + dev_info(&pdev->dev, "found device %x %s (%04x:%04x)\n", + id >> OXU_REV_SHIFT, + bo[(id & OXU_BO_MASK) >> OXU_BO_SHIFT], + (id & OXU_MAJ_REV_MASK) >> OXU_MAJ_REV_SHIFT, + (id & OXU_MIN_REV_MASK) >> OXU_MIN_REV_SHIFT); + + return 0; +} + +static const struct hc_driver oxu_hc_driver; +static struct usb_hcd *oxu_create(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq, int otg) +{ + struct device *dev = &pdev->dev; + + struct usb_hcd *hcd; + struct oxu_hcd *oxu; + int ret; + + /* Set endian mode and host mode */ + oxu_writel(base + (otg ? OXU_OTG_CORE_OFFSET : OXU_SPH_CORE_OFFSET), + OXU_USBMODE, + OXU_CM_HOST_ONLY | OXU_ES_LITTLE | OXU_VBPS); + + hcd = usb_create_hcd(&oxu_hc_driver, dev, + otg ? "oxu210hp_otg" : "oxu210hp_sph"); + if (!hcd) + return ERR_PTR(-ENOMEM); + + hcd->rsrc_start = memstart; + hcd->rsrc_len = memlen; + hcd->regs = base; + hcd->irq = irq; + hcd->state = HC_STATE_HALT; + + oxu = hcd_to_oxu(hcd); + oxu->is_otg = otg; + + ret = usb_add_hcd(hcd, irq, IRQF_SHARED); + if (ret < 0) + return ERR_PTR(ret); + + return hcd; +} + +static int oxu_init(struct platform_device *pdev, + unsigned long memstart, unsigned long memlen, + void *base, int irq) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + struct usb_hcd *hcd; + int ret; + + /* First time configuration at start up */ + oxu_configuration(pdev, base); + + ret = oxu_verify_id(pdev, base); + if (ret) { + dev_err(&pdev->dev, "no devices found!\n"); + return -ENODEV; + } + + /* Create the OTG controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 1); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create OTG controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_otg; + } + info->hcd[0] = hcd; + + /* Create the SPH host controller */ + hcd = oxu_create(pdev, memstart, memlen, base, irq, 0); + if (IS_ERR(hcd)) { + dev_err(&pdev->dev, "cannot create SPH controller!\n"); + ret = PTR_ERR(hcd); + goto error_create_sph; + } + info->hcd[1] = hcd; + + oxu_writel(base, OXU_CHIPIRQEN_SET, + oxu_readl(base, OXU_CHIPIRQEN_SET) | 3); + + return 0; + +error_create_sph: + usb_remove_hcd(info->hcd[0]); + usb_put_hcd(info->hcd[0]); + +error_create_otg: + return ret; +} + +static int oxu_drv_probe(struct platform_device *pdev) +{ + struct resource *res; + void *base; + unsigned long memstart, memlen; + int irq, ret; + struct oxu_info *info; + + if (usb_disabled()) + return -ENODEV; + + /* + * Get the platform resources + */ + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (!res) { + dev_err(&pdev->dev, + "no IRQ! Check %s setup!\n", dev_name(&pdev->dev)); + return -ENODEV; + } + irq = res->start; + dev_dbg(&pdev->dev, "IRQ resource %d\n", irq); + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + if (!res) { + dev_err(&pdev->dev, "no registers address! Check %s setup!\n", + dev_name(&pdev->dev)); + return -ENODEV; + } + memstart = res->start; + memlen = res->end - res->start + 1; + dev_dbg(&pdev->dev, "MEM resource %lx-%lx\n", memstart, memlen); + if (!request_mem_region(memstart, memlen, + oxu_hc_driver.description)) { + dev_dbg(&pdev->dev, "memory area already in use\n"); + return -EBUSY; + } + + ret = set_irq_type(irq, IRQF_TRIGGER_FALLING); + if (ret) { + dev_err(&pdev->dev, "error setting irq type\n"); + ret = -EFAULT; + goto error_set_irq_type; + } + + base = ioremap(memstart, memlen); + if (!base) { + dev_dbg(&pdev->dev, "error mapping memory\n"); + ret = -EFAULT; + goto error_ioremap; + } + + /* Allocate a driver data struct to hold useful info for both + * SPH & OTG devices + */ + info = kzalloc(sizeof(struct oxu_info), GFP_KERNEL); + if (!info) { + dev_dbg(&pdev->dev, "error allocating memory\n"); + ret = -EFAULT; + goto error_alloc; + } + platform_set_drvdata(pdev, info); + + ret = oxu_init(pdev, memstart, memlen, base, irq); + if (ret < 0) { + dev_dbg(&pdev->dev, "cannot init USB devices\n"); + goto error_init; + } + + dev_info(&pdev->dev, "devices enabled and running\n"); + platform_set_drvdata(pdev, info); + + return 0; + +error_init: + kfree(info); + platform_set_drvdata(pdev, NULL); + +error_alloc: + iounmap(base); + +error_set_irq_type: +error_ioremap: + release_mem_region(memstart, memlen); + + dev_err(&pdev->dev, "init %s fail, %d\n", dev_name(&pdev->dev), ret); + return ret; +} + +static void oxu_remove(struct platform_device *pdev, struct usb_hcd *hcd) +{ + usb_remove_hcd(hcd); + usb_put_hcd(hcd); +} + +static int oxu_drv_remove(struct platform_device *pdev) +{ + struct oxu_info *info = platform_get_drvdata(pdev); + unsigned long memstart = info->hcd[0]->rsrc_start, + memlen = info->hcd[0]->rsrc_len; + void *base = info->hcd[0]->regs; + + oxu_remove(pdev, info->hcd[0]); + oxu_remove(pdev, info->hcd[1]); + + iounmap(base); + release_mem_region(memstart, memlen); + + kfree(info); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static void oxu_drv_shutdown(struct platform_device *pdev) +{ + oxu_drv_remove(pdev); +} + +#if 0 +/* FIXME: TODO */ +static int oxu_drv_suspend(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} + +static int oxu_drv_resume(struct device *dev) +{ + struct platform_device *pdev = to_platform_device(dev); + struct usb_hcd *hcd = dev_get_drvdata(dev); + + return 0; +} +#else +#define oxu_drv_suspend NULL +#define oxu_drv_resume NULL +#endif + +static struct platform_driver oxu_driver = { + .probe = oxu_drv_probe, + .remove = oxu_drv_remove, + .shutdown = oxu_drv_shutdown, + .suspend = oxu_drv_suspend, + .resume = oxu_drv_resume, + .driver = { + .name = "oxu210hp-hcd", + .bus = &platform_bus_type + } +}; + +static int __init oxu_module_init(void) +{ + int retval = 0; + + retval = platform_driver_register(&oxu_driver); + if (retval < 0) + return retval; + + return retval; +} + +static void __exit oxu_module_cleanup(void) +{ + platform_driver_unregister(&oxu_driver); +} + +module_init(oxu_module_init); +module_exit(oxu_module_cleanup); + +MODULE_DESCRIPTION("Oxford OXU210HP HCD driver - ver. " DRIVER_VERSION); +MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/host/oxu210hp.h b/drivers/usb/host/oxu210hp.h new file mode 100644 index 000000000000..8910e271cc7d --- /dev/null +++ b/drivers/usb/host/oxu210hp.h @@ -0,0 +1,447 @@ +/* + * Host interface registers + */ + +#define OXU_DEVICEID 0x00 + #define OXU_REV_MASK 0xffff0000 + #define OXU_REV_SHIFT 16 + #define OXU_REV_2100 0x2100 + #define OXU_BO_SHIFT 8 + #define OXU_BO_MASK (0x3 << OXU_BO_SHIFT) + #define OXU_MAJ_REV_SHIFT 4 + #define OXU_MAJ_REV_MASK (0xf << OXU_MAJ_REV_SHIFT) + #define OXU_MIN_REV_SHIFT 0 + #define OXU_MIN_REV_MASK (0xf << OXU_MIN_REV_SHIFT) +#define OXU_HOSTIFCONFIG 0x04 +#define OXU_SOFTRESET 0x08 + #define OXU_SRESET (1 << 0) + +#define OXU_PIOBURSTREADCTRL 0x0C + +#define OXU_CHIPIRQSTATUS 0x10 +#define OXU_CHIPIRQEN_SET 0x14 +#define OXU_CHIPIRQEN_CLR 0x18 + #define OXU_USBSPHLPWUI 0x00000080 + #define OXU_USBOTGLPWUI 0x00000040 + #define OXU_USBSPHI 0x00000002 + #define OXU_USBOTGI 0x00000001 + +#define OXU_CLKCTRL_SET 0x1C + #define OXU_SYSCLKEN 0x00000008 + #define OXU_USBSPHCLKEN 0x00000002 + #define OXU_USBOTGCLKEN 0x00000001 + +#define OXU_ASO 0x68 + #define OXU_SPHPOEN 0x00000100 + #define OXU_OVRCCURPUPDEN 0x00000800 + #define OXU_ASO_OP (1 << 10) + #define OXU_COMPARATOR 0x000004000 + +#define OXU_USBMODE 0x1A8 + #define OXU_VBPS 0x00000020 + #define OXU_ES_LITTLE 0x00000000 + #define OXU_CM_HOST_ONLY 0x00000003 + +/* + * Proper EHCI structs & defines + */ + +/* Magic numbers that can affect system performance */ +#define EHCI_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define EHCI_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define EHCI_TUNE_RL_TT 0 +#define EHCI_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define EHCI_TUNE_MULT_TT 1 +#define EHCI_TUNE_FLS 2 /* (small) 256 frame schedule */ + +struct oxu_hcd; + +/* EHCI register interface, corresponds to EHCI Revision 0.95 specification */ + +/* Section 2.2 Host Controller Capability Registers */ +struct ehci_caps { + /* these fields are specified as 8 and 16 bit registers, + * but some hosts can't perform 8 or 16 bit PCI accesses. + */ + u32 hc_capbase; +#define HC_LENGTH(p) (((p)>>00)&0x00ff) /* bits 7:0 */ +#define HC_VERSION(p) (((p)>>16)&0xffff) /* bits 31:16 */ + u32 hcs_params; /* HCSPARAMS - offset 0x4 */ +#define HCS_DEBUG_PORT(p) (((p)>>20)&0xf) /* bits 23:20, debug port? */ +#define HCS_INDICATOR(p) ((p)&(1 << 16)) /* true: has port indicators */ +#define HCS_N_CC(p) (((p)>>12)&0xf) /* bits 15:12, #companion HCs */ +#define HCS_N_PCC(p) (((p)>>8)&0xf) /* bits 11:8, ports per CC */ +#define HCS_PORTROUTED(p) ((p)&(1 << 7)) /* true: port routing */ +#define HCS_PPC(p) ((p)&(1 << 4)) /* true: port power control */ +#define HCS_N_PORTS(p) (((p)>>0)&0xf) /* bits 3:0, ports on HC */ + + u32 hcc_params; /* HCCPARAMS - offset 0x8 */ +#define HCC_EXT_CAPS(p) (((p)>>8)&0xff) /* for pci extended caps */ +#define HCC_ISOC_CACHE(p) ((p)&(1 << 7)) /* true: can cache isoc frame */ +#define HCC_ISOC_THRES(p) (((p)>>4)&0x7) /* bits 6:4, uframes cached */ +#define HCC_CANPARK(p) ((p)&(1 << 2)) /* true: can park on async qh */ +#define HCC_PGM_FRAMELISTLEN(p) ((p)&(1 << 1)) /* true: periodic_size changes*/ +#define HCC_64BIT_ADDR(p) ((p)&(1)) /* true: can use 64-bit addr */ + u8 portroute[8]; /* nibbles for routing - offset 0xC */ +} __attribute__ ((packed)); + + +/* Section 2.3 Host Controller Operational Registers */ +struct ehci_regs { + /* USBCMD: offset 0x00 */ + u32 command; +/* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */ +#define CMD_PARK (1<<11) /* enable "park" on async qh */ +#define CMD_PARK_CNT(c) (((c)>>8)&3) /* how many transfers to park for */ +#define CMD_LRESET (1<<7) /* partial reset (no ports, etc) */ +#define CMD_IAAD (1<<6) /* "doorbell" interrupt async advance */ +#define CMD_ASE (1<<5) /* async schedule enable */ +#define CMD_PSE (1<<4) /* periodic schedule enable */ +/* 3:2 is periodic frame list size */ +#define CMD_RESET (1<<1) /* reset HC not bus */ +#define CMD_RUN (1<<0) /* start/stop HC */ + + /* USBSTS: offset 0x04 */ + u32 status; +#define STS_ASS (1<<15) /* Async Schedule Status */ +#define STS_PSS (1<<14) /* Periodic Schedule Status */ +#define STS_RECL (1<<13) /* Reclamation */ +#define STS_HALT (1<<12) /* Not running (any reason) */ +/* some bits reserved */ + /* these STS_* flags are also intr_enable bits (USBINTR) */ +#define STS_IAA (1<<5) /* Interrupted on async advance */ +#define STS_FATAL (1<<4) /* such as some PCI access errors */ +#define STS_FLR (1<<3) /* frame list rolled over */ +#define STS_PCD (1<<2) /* port change detect */ +#define STS_ERR (1<<1) /* "error" completion (overflow, ...) */ +#define STS_INT (1<<0) /* "normal" completion (short, ...) */ + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + + /* USBINTR: offset 0x08 */ + u32 intr_enable; + + /* FRINDEX: offset 0x0C */ + u32 frame_index; /* current microframe number */ + /* CTRLDSSEGMENT: offset 0x10 */ + u32 segment; /* address bits 63:32 if needed */ + /* PERIODICLISTBASE: offset 0x14 */ + u32 frame_list; /* points to periodic list */ + /* ASYNCLISTADDR: offset 0x18 */ + u32 async_next; /* address of next async queue head */ + + u32 reserved[9]; + + /* CONFIGFLAG: offset 0x40 */ + u32 configured_flag; +#define FLAG_CF (1<<0) /* true: we'll support "high speed" */ + + /* PORTSC: offset 0x44 */ + u32 port_status[0]; /* up to N_PORTS */ +/* 31:23 reserved */ +#define PORT_WKOC_E (1<<22) /* wake on overcurrent (enable) */ +#define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ +#define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ +/* 19:16 for port testing */ +#define PORT_LED_OFF (0<<14) +#define PORT_LED_AMBER (1<<14) +#define PORT_LED_GREEN (2<<14) +#define PORT_LED_MASK (3<<14) +#define PORT_OWNER (1<<13) /* true: companion hc owns this port */ +#define PORT_POWER (1<<12) /* true: has power (see PPC) */ +#define PORT_USB11(x) (((x)&(3<<10)) == (1<<10)) /* USB 1.1 device */ +/* 11:10 for detecting lowspeed devices (reset vs release ownership) */ +/* 9 reserved */ +#define PORT_RESET (1<<8) /* reset port */ +#define PORT_SUSPEND (1<<7) /* suspend port */ +#define PORT_RESUME (1<<6) /* resume it */ +#define PORT_OCC (1<<5) /* over current change */ +#define PORT_OC (1<<4) /* over current active */ +#define PORT_PEC (1<<3) /* port enable change */ +#define PORT_PE (1<<2) /* port enable */ +#define PORT_CSC (1<<1) /* connect status change */ +#define PORT_CONNECT (1<<0) /* device connected */ +#define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) +} __attribute__ ((packed)); + +/* Appendix C, Debug port ... intended for use with special "debug devices" + * that can help if there's no serial console. (nonstandard enumeration.) + */ +struct ehci_dbg_port { + u32 control; +#define DBGP_OWNER (1<<30) +#define DBGP_ENABLED (1<<28) +#define DBGP_DONE (1<<16) +#define DBGP_INUSE (1<<10) +#define DBGP_ERRCODE(x) (((x)>>7)&0x07) +# define DBGP_ERR_BAD 1 +# define DBGP_ERR_SIGNAL 2 +#define DBGP_ERROR (1<<6) +#define DBGP_GO (1<<5) +#define DBGP_OUT (1<<4) +#define DBGP_LEN(x) (((x)>>0)&0x0f) + u32 pids; +#define DBGP_PID_GET(x) (((x)>>16)&0xff) +#define DBGP_PID_SET(data, tok) (((data)<<8)|(tok)) + u32 data03; + u32 data47; + u32 address; +#define DBGP_EPADDR(dev, ep) (((dev)<<8)|(ep)) +} __attribute__ ((packed)); + + +#define QTD_NEXT(dma) cpu_to_le32((u32)dma) + +/* + * EHCI Specification 0.95 Section 3.5 + * QTD: describe data transfer components (buffer, direction, ...) + * See Fig 3-6 "Queue Element Transfer Descriptor Block Diagram". + * + * These are associated only with "QH" (Queue Head) structures, + * used with control, bulk, and interrupt transfers. + */ +struct ehci_qtd { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.5.1 */ + __le32 hw_alt_next; /* see EHCI 3.5.2 */ + __le32 hw_token; /* see EHCI 3.5.3 */ +#define QTD_TOGGLE (1 << 31) /* data toggle */ +#define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) +#define QTD_IOC (1 << 15) /* interrupt on complete */ +#define QTD_CERR(tok) (((tok)>>10) & 0x3) +#define QTD_PID(tok) (((tok)>>8) & 0x3) +#define QTD_STS_ACTIVE (1 << 7) /* HC may execute this */ +#define QTD_STS_HALT (1 << 6) /* halted on error */ +#define QTD_STS_DBE (1 << 5) /* data buffer error (in HC) */ +#define QTD_STS_BABBLE (1 << 4) /* device was babbling (qtd halted) */ +#define QTD_STS_XACT (1 << 3) /* device gave illegal response */ +#define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ +#define QTD_STS_STS (1 << 1) /* split transaction state */ +#define QTD_STS_PING (1 << 0) /* issue PING? */ + __le32 hw_buf[5]; /* see EHCI 3.5.4 */ + __le32 hw_buf_hi[5]; /* Appendix B */ + + /* the rest is HCD-private */ + dma_addr_t qtd_dma; /* qtd address */ + struct list_head qtd_list; /* sw qtd list */ + struct urb *urb; /* qtd's urb */ + size_t length; /* length of buffer */ + + u32 qtd_buffer_len; + void *buffer; + dma_addr_t buffer_dma; + void *transfer_buffer; + void *transfer_dma; +} __attribute__ ((aligned(32))); + +/* mask NakCnt+T in qh->hw_alt_next */ +#define QTD_MASK __constant_cpu_to_le32 (~0x1f) + +#define IS_SHORT_READ(token) (QTD_LENGTH(token) != 0 && QTD_PID(token) == 1) + +/* Type tag from {qh, itd, sitd, fstn}->hw_next */ +#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) + +/* values for that type tag */ +#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) + +/* next async queue entry, or pointer to interrupt/periodic QH */ +#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) + +/* for periodic/async schedules and qtd lists, mark end of list */ +#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ + +/* + * Entries in periodic shadow table are pointers to one of four kinds + * of data structure. That's dictated by the hardware; a type tag is + * encoded in the low bits of the hardware's periodic schedule. Use + * Q_NEXT_TYPE to get the tag. + * + * For entries in the async schedule, the type tag always says "qh". + */ +union ehci_shadow { + struct ehci_qh *qh; /* Q_TYPE_QH */ + __le32 *hw_next; /* (all types) */ + void *ptr; +}; + +/* + * EHCI Specification 0.95 Section 3.6 + * QH: describes control/bulk/interrupt endpoints + * See Fig 3-7 "Queue Head Structure Layout". + * + * These appear in both the async and (for interrupt) periodic schedules. + */ + +struct ehci_qh { + /* first part defined by EHCI spec */ + __le32 hw_next; /* see EHCI 3.6.1 */ + __le32 hw_info1; /* see EHCI 3.6.2 */ +#define QH_HEAD 0x00008000 + __le32 hw_info2; /* see EHCI 3.6.2 */ +#define QH_SMASK 0x000000ff +#define QH_CMASK 0x0000ff00 +#define QH_HUBADDR 0x007f0000 +#define QH_HUBPORT 0x3f800000 +#define QH_MULT 0xc0000000 + __le32 hw_current; /* qtd list - see EHCI 3.6.4 */ + + /* qtd overlay (hardware parts of a struct ehci_qtd) */ + __le32 hw_qtd_next; + __le32 hw_alt_next; + __le32 hw_token; + __le32 hw_buf[5]; + __le32 hw_buf_hi[5]; + + /* the rest is HCD-private */ + dma_addr_t qh_dma; /* address of qh */ + union ehci_shadow qh_next; /* ptr to qh; or periodic */ + struct list_head qtd_list; /* sw qtd list */ + struct ehci_qtd *dummy; + struct ehci_qh *reclaim; /* next to reclaim */ + + struct oxu_hcd *oxu; + struct kref kref; + unsigned stamp; + + u8 qh_state; +#define QH_STATE_LINKED 1 /* HC sees this */ +#define QH_STATE_UNLINK 2 /* HC may still see this */ +#define QH_STATE_IDLE 3 /* HC doesn't see this */ +#define QH_STATE_UNLINK_WAIT 4 /* LINKED and on reclaim q */ +#define QH_STATE_COMPLETING 5 /* don't touch token.HALT */ + + /* periodic schedule info */ + u8 usecs; /* intr bandwidth */ + u8 gap_uf; /* uframes split/csplit gap */ + u8 c_usecs; /* ... split completion bw */ + u16 tt_usecs; /* tt downstream bandwidth */ + unsigned short period; /* polling interval */ + unsigned short start; /* where polling starts */ +#define NO_FRAME ((unsigned short)~0) /* pick new start */ + struct usb_device *dev; /* access to TT */ +} __attribute__ ((aligned(32))); + +/* + * Proper OXU210HP structs + */ + +#define OXU_OTG_CORE_OFFSET 0x00400 +#define OXU_OTG_CAP_OFFSET (OXU_OTG_CORE_OFFSET + 0x100) +#define OXU_SPH_CORE_OFFSET 0x00800 +#define OXU_SPH_CAP_OFFSET (OXU_SPH_CORE_OFFSET + 0x100) + +#define OXU_OTG_MEM 0xE000 +#define OXU_SPH_MEM 0x16000 + +/* Only how many elements & element structure are specifies here. */ +/* 2 host controllers are enabled - total size <= 28 kbytes */ +#define DEFAULT_I_TDPS 1024 +#define QHEAD_NUM 16 +#define QTD_NUM 32 +#define SITD_NUM 8 +#define MURB_NUM 8 + +#define BUFFER_NUM 8 +#define BUFFER_SIZE 512 + +struct oxu_info { + struct usb_hcd *hcd[2]; +}; + +struct oxu_buf { + u8 buffer[BUFFER_SIZE]; +} __attribute__ ((aligned(BUFFER_SIZE))); + +struct oxu_onchip_mem { + struct oxu_buf db_pool[BUFFER_NUM]; + + u32 frame_list[DEFAULT_I_TDPS]; + struct ehci_qh qh_pool[QHEAD_NUM]; + struct ehci_qtd qtd_pool[QTD_NUM]; +} __attribute__ ((aligned(4 << 10))); + +#define EHCI_MAX_ROOT_PORTS 15 /* see HCS_N_PORTS */ + +struct oxu_murb { + struct urb urb; + struct urb *main; + u8 last; +}; + +struct oxu_hcd { /* one per controller */ + unsigned int is_otg:1; + + u8 qh_used[QHEAD_NUM]; + u8 qtd_used[QTD_NUM]; + u8 db_used[BUFFER_NUM]; + u8 murb_used[MURB_NUM]; + + struct oxu_onchip_mem __iomem *mem; + spinlock_t mem_lock; + + struct timer_list urb_timer; + + struct ehci_caps __iomem *caps; + struct ehci_regs __iomem *regs; + + __u32 hcs_params; /* cached register copy */ + spinlock_t lock; + + /* async schedule support */ + struct ehci_qh *async; + struct ehci_qh *reclaim; + unsigned reclaim_ready:1; + unsigned scanning:1; + + /* periodic schedule support */ + unsigned periodic_size; + __le32 *periodic; /* hw periodic table */ + dma_addr_t periodic_dma; + unsigned i_thresh; /* uframes HC might cache */ + + union ehci_shadow *pshadow; /* mirror hw periodic table */ + int next_uframe; /* scan periodic, start here */ + unsigned periodic_sched; /* periodic activity count */ + + /* per root hub port */ + unsigned long reset_done[EHCI_MAX_ROOT_PORTS]; + /* bit vectors (one bit per port) */ + unsigned long bus_suspended; /* which ports were + * already suspended at the + * start of a bus suspend + */ + unsigned long companion_ports;/* which ports are dedicated + * to the companion controller + */ + + struct timer_list watchdog; + unsigned long actions; + unsigned stamp; + unsigned long next_statechange; + u32 command; + + /* SILICON QUIRKS */ + struct list_head urb_list; /* this is the head to urb + * queue that didn't get enough + * resources + */ + struct oxu_murb *murb_pool; /* murb per split big urb */ + unsigned urb_len; + + u8 sbrn; /* packed release number */ +}; + +#define EHCI_IAA_JIFFIES (HZ/100) /* arbitrary; ~10 msec */ +#define EHCI_IO_JIFFIES (HZ/10) /* io watchdog > irq_thresh */ +#define EHCI_ASYNC_JIFFIES (HZ/20) /* async idle timeout */ +#define EHCI_SHRINK_JIFFIES (HZ/200) /* async qh unlink delay */ + +enum ehci_timer_action { + TIMER_IO_WATCHDOG, + TIMER_IAA_WATCHDOG, + TIMER_ASYNC_SHRINK, + TIMER_ASYNC_OFF, +}; + +#include <linux/oxu210hp.h> diff --git a/drivers/usb/host/pci-quirks.c b/drivers/usb/host/pci-quirks.c index ae6e70edd745..75b69847918e 100644 --- a/drivers/usb/host/pci-quirks.c +++ b/drivers/usb/host/pci-quirks.c @@ -172,9 +172,9 @@ static void __devinit quirk_usb_handoff_ohci(struct pci_dev *pdev) if (!mmio_resource_enabled(pdev, 0)) return; - base = ioremap_nocache(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - if (base == NULL) return; + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; /* On PA-RISC, PDC can leave IR set incorrectly; ignore it there. */ #ifndef __hppa__ @@ -221,9 +221,9 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) if (!mmio_resource_enabled(pdev, 0)) return; - base = ioremap_nocache(pci_resource_start(pdev, 0), - pci_resource_len(pdev, 0)); - if (base == NULL) return; + base = pci_ioremap_bar(pdev, 0); + if (base == NULL) + return; cap_length = readb(base); op_reg_base = base + cap_length; @@ -271,7 +271,7 @@ static void __devinit quirk_usb_disable_ehci(struct pci_dev *pdev) /* if boot firmware now owns EHCI, spin till * it hands it over. */ - msec = 5000; + msec = 1000; while ((cap & EHCI_USBLEGSUP_BIOS) && (msec > 0)) { tried_handoff = 1; msleep(10); diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c index c21f14e0666a..319041205b57 100644 --- a/drivers/usb/host/r8a66597-hcd.c +++ b/drivers/usb/host/r8a66597-hcd.c @@ -2275,7 +2275,6 @@ static int __init_or_module r8a66597_remove(struct platform_device *pdev) return 0; } -#define resource_len(r) (((r)->end - (r)->start) + 1) static int __init r8a66597_probe(struct platform_device *pdev) { #if defined(CONFIG_SUPERH_ON_CHIP_R8A66597) && defined(CONFIG_HAVE_CLK) @@ -2296,11 +2295,10 @@ static int __init r8a66597_probe(struct platform_device *pdev) goto clean_up; } - res = platform_get_resource_byname(pdev, IORESOURCE_MEM, - (char *)hcd_name); + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (!res) { ret = -ENODEV; - dev_err(&pdev->dev, "platform_get_resource_byname error.\n"); + dev_err(&pdev->dev, "platform_get_resource error.\n"); goto clean_up; } @@ -2315,7 +2313,7 @@ static int __init r8a66597_probe(struct platform_device *pdev) irq = ires->start; irq_trigger = ires->flags & IRQF_TRIGGER_MASK; - reg = ioremap(res->start, resource_len(res)); + reg = ioremap(res->start, resource_size(res)); if (reg == NULL) { ret = -ENOMEM; dev_err(&pdev->dev, "ioremap error.\n"); diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index cf5e4cf7ea42..4e221060f58c 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -942,6 +942,8 @@ static struct pci_driver uhci_pci_driver = { #ifdef CONFIG_PM .suspend = usb_hcd_pci_suspend, + .suspend_late = usb_hcd_pci_suspend_late, + .resume_early = usb_hcd_pci_resume_early, .resume = usb_hcd_pci_resume, #endif /* PM */ }; diff --git a/drivers/usb/image/microtek.c b/drivers/usb/image/microtek.c index 885867a86de8..4541dfcea88f 100644 --- a/drivers/usb/image/microtek.c +++ b/drivers/usb/image/microtek.c @@ -350,17 +350,16 @@ static int mts_scsi_abort(struct scsi_cmnd *srb) static int mts_scsi_host_reset(struct scsi_cmnd *srb) { struct mts_desc* desc = (struct mts_desc*)(srb->device->host->hostdata[0]); - int result, rc; + int result; MTS_DEBUG_GOT_HERE(); mts_debug_dump(desc); - rc = usb_lock_device_for_reset(desc->usb_dev, desc->usb_intf); - if (rc < 0) - return FAILED; - result = usb_reset_device(desc->usb_dev); - if (rc) + result = usb_lock_device_for_reset(desc->usb_dev, desc->usb_intf); + if (result == 0) { + result = usb_reset_device(desc->usb_dev); usb_unlock_device(desc->usb_dev); + } return result ? FAILED : SUCCESS; } diff --git a/drivers/usb/misc/berry_charge.c b/drivers/usb/misc/berry_charge.c index 24e2dc3148a4..c05a85bc5925 100644 --- a/drivers/usb/misc/berry_charge.c +++ b/drivers/usb/misc/berry_charge.c @@ -123,6 +123,11 @@ static int berry_probe(struct usb_interface *intf, { struct usb_device *udev = interface_to_usbdev(intf); + if (udev->bus_mA < 500) { + dbg(&udev->dev, "Not enough power to charge available\n"); + return -ENODEV; + } + dbg(&udev->dev, "Power is set to %dmA\n", udev->actconfig->desc.bMaxPower * 2); diff --git a/drivers/usb/misc/emi26.c b/drivers/usb/misc/emi26.c index e762beb5f3c6..879a980ca8c4 100644 --- a/drivers/usb/misc/emi26.c +++ b/drivers/usb/misc/emi26.c @@ -160,7 +160,7 @@ static int emi26_load_firmware (struct usb_device *dev) err("%s - error loading firmware: error = %d", __func__, err); goto wraperr; } - } while (i > 0); + } while (rec); /* Assert reset (stop the CPU in the EMI) */ err = emi26_set_reset(dev,1); diff --git a/drivers/usb/misc/usbtest.c b/drivers/usb/misc/usbtest.c index 444c69c447be..5f1a19d1497d 100644 --- a/drivers/usb/misc/usbtest.c +++ b/drivers/usb/misc/usbtest.c @@ -192,8 +192,6 @@ static struct urb *simple_alloc_urb ( { struct urb *urb; - if (bytes < 0) - return NULL; urb = usb_alloc_urb (0, GFP_KERNEL); if (!urb) return urb; diff --git a/drivers/usb/mon/Kconfig b/drivers/usb/mon/Kconfig index deb9ddffa402..f28f350cd96a 100644 --- a/drivers/usb/mon/Kconfig +++ b/drivers/usb/mon/Kconfig @@ -3,14 +3,13 @@ # config USB_MON - bool "USB Monitor" - depends on USB!=n - default y + tristate "USB Monitor" + depends on USB + default y if USB=y + default m if USB=m help - If you say Y here, a component which captures the USB traffic + If you select this option, a component which captures the USB traffic between peripheral-specific drivers and HC drivers will be built. For more information, see <file:Documentation/usb/usbmon.txt>. - This is somewhat experimental at this time, but it should be safe. - - If unsure, say Y. + If unsure, say Y (if allowed), otherwise M. diff --git a/drivers/usb/mon/Makefile b/drivers/usb/mon/Makefile index 0f76ed5e1617..c6516b566731 100644 --- a/drivers/usb/mon/Makefile +++ b/drivers/usb/mon/Makefile @@ -4,5 +4,4 @@ usbmon-objs := mon_main.o mon_stat.o mon_text.o mon_bin.o mon_dma.o -# This does not use CONFIG_USB_MON because we want this to use a tristate. -obj-$(CONFIG_USB) += usbmon.o +obj-$(CONFIG_USB_MON) += usbmon.o diff --git a/drivers/usb/musb/Kconfig b/drivers/usb/musb/Kconfig index 4b9542bbb35c..5af7379cd9a3 100644 --- a/drivers/usb/musb/Kconfig +++ b/drivers/usb/musb/Kconfig @@ -11,7 +11,7 @@ config USB_MUSB_HDRC depends on (USB || USB_GADGET) && HAVE_CLK depends on !SUPERH select TWL4030_USB if MACH_OMAP_3430SDP - tristate 'Inventra Highspeed Dual Role Controller (TI, ...)' + tristate 'Inventra Highspeed Dual Role Controller (TI, ADI, ...)' help Say Y here if your system has a dual role high speed USB controller based on the Mentor Graphics silicon IP. Then @@ -22,6 +22,9 @@ config USB_MUSB_HDRC Texas Instruments parts using this IP include DaVinci 644x, OMAP 243x, OMAP 343x, and TUSB 6010. + Analog Devices parts using this IP include Blackfin BF54x, + BF525 and BF527. + If you do not know what this is, please say N. To compile this driver as a module, choose M here; the @@ -33,6 +36,8 @@ config USB_MUSB_SOC default y if ARCH_DAVINCI default y if ARCH_OMAP2430 default y if ARCH_OMAP34XX + default y if (BF54x && !BF544) + default y if (BF52x && !BF522 && !BF523) comment "DaVinci 644x USB support" depends on USB_MUSB_HDRC && ARCH_DAVINCI @@ -43,6 +48,9 @@ comment "OMAP 243x high speed USB support" comment "OMAP 343x high speed USB support" depends on USB_MUSB_HDRC && ARCH_OMAP34XX +comment "Blackfin high speed USB Support" + depends on USB_MUSB_HDRC && (BF54x && !BF544) || (BF52x && !BF522 && !BF523) + config USB_TUSB6010 boolean "TUSB 6010 support" depends on USB_MUSB_HDRC && !USB_MUSB_SOC @@ -142,7 +150,7 @@ config MUSB_PIO_ONLY config USB_INVENTRA_DMA bool depends on USB_MUSB_HDRC && !MUSB_PIO_ONLY - default ARCH_OMAP2430 || ARCH_OMAP34XX + default ARCH_OMAP2430 || ARCH_OMAP34XX || BLACKFIN help Enable DMA transfers using Mentor's engine. diff --git a/drivers/usb/musb/Makefile b/drivers/usb/musb/Makefile index b6af0d687a73..85710ccc1887 100644 --- a/drivers/usb/musb/Makefile +++ b/drivers/usb/musb/Makefile @@ -22,6 +22,14 @@ ifeq ($(CONFIG_ARCH_OMAP3430),y) musb_hdrc-objs += omap2430.o endif +ifeq ($(CONFIG_BF54x),y) + musb_hdrc-objs += blackfin.o +endif + +ifeq ($(CONFIG_BF52x),y) + musb_hdrc-objs += blackfin.o +endif + ifeq ($(CONFIG_USB_GADGET_MUSB_HDRC),y) musb_hdrc-objs += musb_gadget_ep0.o musb_gadget.o endif diff --git a/drivers/usb/musb/blackfin.c b/drivers/usb/musb/blackfin.c new file mode 100644 index 000000000000..786134852092 --- /dev/null +++ b/drivers/usb/musb/blackfin.c @@ -0,0 +1,320 @@ +/* + * MUSB OTG controller driver for Blackfin Processors + * + * Copyright 2006-2008 Analog Devices Inc. + * + * Enter bugs at http://blackfin.uclinux.org/ + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/slab.h> +#include <linux/init.h> +#include <linux/list.h> +#include <linux/clk.h> +#include <linux/gpio.h> +#include <linux/io.h> + +#include <asm/cacheflush.h> + +#include "musb_core.h" +#include "blackfin.h" + +/* + * Load an endpoint's FIFO + */ +void musb_write_fifo(struct musb_hw_ep *hw_ep, u16 len, const u8 *src) +{ + void __iomem *fifo = hw_ep->fifo; + void __iomem *epio = hw_ep->regs; + + prefetch((u8 *)src); + + musb_writew(epio, MUSB_TXCOUNT, len); + + DBG(4, "TX ep%d fifo %p count %d buf %p, epio %p\n", + hw_ep->epnum, fifo, len, src, epio); + + dump_fifo_data(src, len); + + if (unlikely((unsigned long)src & 0x01)) + outsw_8((unsigned long)fifo, src, + len & 0x01 ? (len >> 1) + 1 : len >> 1); + else + outsw((unsigned long)fifo, src, + len & 0x01 ? (len >> 1) + 1 : len >> 1); +} + +/* + * Unload an endpoint's FIFO + */ +void musb_read_fifo(struct musb_hw_ep *hw_ep, u16 len, u8 *dst) +{ + void __iomem *fifo = hw_ep->fifo; + u8 epnum = hw_ep->epnum; + u16 dma_reg = 0; + + DBG(4, "%cX ep%d fifo %p count %d buf %p\n", + 'R', hw_ep->epnum, fifo, len, dst); + +#ifdef CONFIG_BF52x + invalidate_dcache_range((unsigned int)dst, + (unsigned int)(dst + len)); + + /* Setup DMA address register */ + dma_reg = (u16) ((u32) dst & 0xFFFF); + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_LOW), dma_reg); + SSYNC(); + + dma_reg = (u16) (((u32) dst >> 16) & 0xFFFF); + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_ADDR_HIGH), dma_reg); + SSYNC(); + + /* Setup DMA count register */ + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_LOW), len); + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_COUNT_HIGH), 0); + SSYNC(); + + /* Enable the DMA */ + dma_reg = (epnum << 4) | DMA_ENA | INT_ENA; + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), dma_reg); + SSYNC(); + + /* Wait for compelete */ + while (!(bfin_read_USB_DMA_INTERRUPT() & (1 << epnum))) + cpu_relax(); + + /* acknowledge dma interrupt */ + bfin_write_USB_DMA_INTERRUPT(1 << epnum); + SSYNC(); + + /* Reset DMA */ + bfin_write16(USB_DMA_REG(epnum, USB_DMAx_CTRL), 0); + SSYNC(); +#else + if (unlikely((unsigned long)dst & 0x01)) + insw_8((unsigned long)fifo, dst, + len & 0x01 ? (len >> 1) + 1 : len >> 1); + else + insw((unsigned long)fifo, dst, + len & 0x01 ? (len >> 1) + 1 : len >> 1); +#endif + + dump_fifo_data(dst, len); +} + +static irqreturn_t blackfin_interrupt(int irq, void *__hci) +{ + unsigned long flags; + irqreturn_t retval = IRQ_NONE; + struct musb *musb = __hci; + + spin_lock_irqsave(&musb->lock, flags); + + musb->int_usb = musb_readb(musb->mregs, MUSB_INTRUSB); + musb->int_tx = musb_readw(musb->mregs, MUSB_INTRTX); + musb->int_rx = musb_readw(musb->mregs, MUSB_INTRRX); + + if (musb->int_usb || musb->int_tx || musb->int_rx) { + musb_writeb(musb->mregs, MUSB_INTRUSB, musb->int_usb); + musb_writew(musb->mregs, MUSB_INTRTX, musb->int_tx); + musb_writew(musb->mregs, MUSB_INTRRX, musb->int_rx); + retval = musb_interrupt(musb); + } + + spin_unlock_irqrestore(&musb->lock, flags); + + /* REVISIT we sometimes get spurious IRQs on g_ep0 + * not clear why... fall in BF54x too. + */ + if (retval != IRQ_HANDLED) + DBG(5, "spurious?\n"); + + return IRQ_HANDLED; +} + +static void musb_conn_timer_handler(unsigned long _musb) +{ + struct musb *musb = (void *)_musb; + unsigned long flags; + u16 val; + + spin_lock_irqsave(&musb->lock, flags); + switch (musb->xceiv.state) { + case OTG_STATE_A_IDLE: + case OTG_STATE_A_WAIT_BCON: + /* Start a new session */ + val = musb_readw(musb->mregs, MUSB_DEVCTL); + val |= MUSB_DEVCTL_SESSION; + musb_writew(musb->mregs, MUSB_DEVCTL, val); + + val = musb_readw(musb->mregs, MUSB_DEVCTL); + if (!(val & MUSB_DEVCTL_BDEVICE)) { + gpio_set_value(musb->config->gpio_vrsel, 1); + musb->xceiv.state = OTG_STATE_A_WAIT_BCON; + } else { + gpio_set_value(musb->config->gpio_vrsel, 0); + + /* Ignore VBUSERROR and SUSPEND IRQ */ + val = musb_readb(musb->mregs, MUSB_INTRUSBE); + val &= ~MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSBE, val); + + val = MUSB_INTR_SUSPEND | MUSB_INTR_VBUSERROR; + musb_writeb(musb->mregs, MUSB_INTRUSB, val); + + val = MUSB_POWER_HSENAB; + musb_writeb(musb->mregs, MUSB_POWER, val); + } + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); + break; + + default: + DBG(1, "%s state not handled\n", otg_state_string(musb)); + break; + } + spin_unlock_irqrestore(&musb->lock, flags); + + DBG(4, "state is %s\n", otg_state_string(musb)); +} + +void musb_platform_enable(struct musb *musb) +{ + if (is_host_enabled(musb)) { + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); + musb->a_wait_bcon = TIMER_DELAY; + } +} + +void musb_platform_disable(struct musb *musb) +{ +} + +static void bfin_vbus_power(struct musb *musb, int is_on, int sleeping) +{ +} + +static void bfin_set_vbus(struct musb *musb, int is_on) +{ + if (is_on) + gpio_set_value(musb->config->gpio_vrsel, 1); + else + gpio_set_value(musb->config->gpio_vrsel, 0); + + DBG(1, "VBUS %s, devctl %02x " + /* otg %3x conf %08x prcm %08x */ "\n", + otg_state_string(musb), + musb_readb(musb->mregs, MUSB_DEVCTL)); +} + +static int bfin_set_power(struct otg_transceiver *x, unsigned mA) +{ + return 0; +} + +void musb_platform_try_idle(struct musb *musb, unsigned long timeout) +{ + if (is_host_enabled(musb)) + mod_timer(&musb_conn_timer, jiffies + TIMER_DELAY); +} + +int musb_platform_get_vbus_status(struct musb *musb) +{ + return 0; +} + +void musb_platform_set_mode(struct musb *musb, u8 musb_mode) +{ +} + +int __init musb_platform_init(struct musb *musb) +{ + + /* + * Rev 1.0 BF549 EZ-KITs require PE7 to be high for both DEVICE + * and OTG HOST modes, while rev 1.1 and greater require PE7 to + * be low for DEVICE mode and high for HOST mode. We set it high + * here because we are in host mode + */ + + if (gpio_request(musb->config->gpio_vrsel, "USB_VRSEL")) { + printk(KERN_ERR "Failed ro request USB_VRSEL GPIO_%d \n", + musb->config->gpio_vrsel); + return -ENODEV; + } + gpio_direction_output(musb->config->gpio_vrsel, 0); + + if (ANOMALY_05000346) { + bfin_write_USB_APHY_CALIB(ANOMALY_05000346_value); + SSYNC(); + } + + if (ANOMALY_05000347) { + bfin_write_USB_APHY_CNTRL(0x0); + SSYNC(); + } + + /* TODO + * Set SIC-IVG register + */ + + /* Configure PLL oscillator register */ + bfin_write_USB_PLLOSC_CTRL(0x30a8); + SSYNC(); + + bfin_write_USB_SRP_CLKDIV((get_sclk()/1000) / 32 - 1); + SSYNC(); + + bfin_write_USB_EP_NI0_RXMAXP(64); + SSYNC(); + + bfin_write_USB_EP_NI0_TXMAXP(64); + SSYNC(); + + /* Route INTRUSB/INTR_RX/INTR_TX to USB_INT0*/ + bfin_write_USB_GLOBINTR(0x7); + SSYNC(); + + bfin_write_USB_GLOBAL_CTL(GLOBAL_ENA | EP1_TX_ENA | EP2_TX_ENA | + EP3_TX_ENA | EP4_TX_ENA | EP5_TX_ENA | + EP6_TX_ENA | EP7_TX_ENA | EP1_RX_ENA | + EP2_RX_ENA | EP3_RX_ENA | EP4_RX_ENA | + EP5_RX_ENA | EP6_RX_ENA | EP7_RX_ENA); + SSYNC(); + + if (is_host_enabled(musb)) { + musb->board_set_vbus = bfin_set_vbus; + setup_timer(&musb_conn_timer, + musb_conn_timer_handler, (unsigned long) musb); + } + if (is_peripheral_enabled(musb)) + musb->xceiv.set_power = bfin_set_power; + + musb->isr = blackfin_interrupt; + + return 0; +} + +int musb_platform_suspend(struct musb *musb) +{ + return 0; +} + +int musb_platform_resume(struct musb *musb) +{ + return 0; +} + + +int musb_platform_exit(struct musb *musb) +{ + + bfin_vbus_power(musb, 0 /*off*/, 1); + gpio_free(musb->config->gpio_vrsel); + musb_platform_suspend(musb); + + return 0; +} diff --git a/drivers/usb/musb/blackfin.h b/drivers/usb/musb/blackfin.h new file mode 100644 index 000000000000..a240c1e53d16 --- /dev/null +++ b/drivers/usb/musb/blackfin.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2007 by Analog Devices, Inc. + * + * The Inventra Controller Driver for Linux 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 __MUSB_BLACKFIN_H__ +#define __MUSB_BLACKFIN_H__ + +/* + * Blackfin specific definitions + */ + +#undef DUMP_FIFO_DATA +#ifdef DUMP_FIFO_DATA +static void dump_fifo_data(u8 *buf, u16 len) +{ + u8 *tmp = buf; + int i; + + for (i = 0; i < len; i++) { + if (!(i % 16) && i) + pr_debug("\n"); + pr_debug("%02x ", *tmp++); + } + pr_debug("\n"); +} +#else +#define dump_fifo_data(buf, len) do {} while (0) +#endif + +#ifdef CONFIG_BF52x + +#define USB_DMA_BASE USB_DMA_INTERRUPT +#define USB_DMAx_CTRL 0x04 +#define USB_DMAx_ADDR_LOW 0x08 +#define USB_DMAx_ADDR_HIGH 0x0C +#define USB_DMAx_COUNT_LOW 0x10 +#define USB_DMAx_COUNT_HIGH 0x14 + +#define USB_DMA_REG(ep, reg) (USB_DMA_BASE + 0x20 * ep + reg) +#endif + +/* Almost 1 second */ +#define TIMER_DELAY (1 * HZ) + +static struct timer_list musb_conn_timer; + +#endif /* __MUSB_BLACKFIN_H__ */ diff --git a/drivers/usb/musb/davinci.c b/drivers/usb/musb/davinci.c index dfb3bcbe00fc..0d566dc5ce06 100644 --- a/drivers/usb/musb/davinci.c +++ b/drivers/usb/musb/davinci.c @@ -32,9 +32,9 @@ #include <linux/io.h> #include <linux/gpio.h> -#include <asm/arch/hardware.h> -#include <asm/arch/memory.h> -#include <asm/arch/gpio.h> +#include <mach/arch/hardware.h> +#include <mach/arch/memory.h> +#include <mach/arch/gpio.h> #include <asm/mach-types.h> #include "musb_core.h" @@ -364,6 +364,18 @@ static irqreturn_t davinci_interrupt(int irq, void *__hci) return IRQ_HANDLED; } +int musb_platform_set_mode(struct musb *musb, u8 mode) +{ + /* EVM can't do this (right?) */ + return -EIO; +} + +int musb_platform_set_mode(struct musb *musb, u8 mode) +{ + /* EVM can't do this (right?) */ + return -EIO; +} + int __init musb_platform_init(struct musb *musb) { void __iomem *tibase = musb->ctrl_base; diff --git a/drivers/usb/musb/musb_core.c b/drivers/usb/musb/musb_core.c index 5280dba9b1fb..6c7faacfb535 100644 --- a/drivers/usb/musb/musb_core.c +++ b/drivers/usb/musb/musb_core.c @@ -148,7 +148,8 @@ static inline struct musb *dev_to_musb(struct device *dev) /*-------------------------------------------------------------------------*/ -#ifndef CONFIG_USB_TUSB6010 +#if !defined(CONFIG_USB_TUSB6010) && !defined(CONFIG_BLACKFIN) + /* * Load an endpoint's FIFO */ @@ -1124,25 +1125,25 @@ fifo_setup(struct musb *musb, struct musb_hw_ep *hw_ep, #endif switch (cfg->style) { case FIFO_TX: - musb_writeb(mbase, MUSB_TXFIFOSZ, c_size); - musb_writew(mbase, MUSB_TXFIFOADD, c_off); + musb_write_txfifosz(mbase, c_size); + musb_write_txfifoadd(mbase, c_off); hw_ep->tx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); hw_ep->max_packet_sz_tx = maxpacket; break; case FIFO_RX: - musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); - musb_writew(mbase, MUSB_RXFIFOADD, c_off); + musb_write_rxfifosz(mbase, c_size); + musb_write_rxfifoadd(mbase, c_off); hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); hw_ep->max_packet_sz_rx = maxpacket; break; case FIFO_RXTX: - musb_writeb(mbase, MUSB_TXFIFOSZ, c_size); - musb_writew(mbase, MUSB_TXFIFOADD, c_off); + musb_write_txfifosz(mbase, c_size); + musb_write_txfifoadd(mbase, c_off); hw_ep->rx_double_buffered = !!(c_size & MUSB_FIFOSZ_DPB); hw_ep->max_packet_sz_rx = maxpacket; - musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); - musb_writew(mbase, MUSB_RXFIFOADD, c_off); + musb_write_rxfifosz(mbase, c_size); + musb_write_rxfifoadd(mbase, c_off); hw_ep->tx_double_buffered = hw_ep->rx_double_buffered; hw_ep->max_packet_sz_tx = maxpacket; @@ -1212,7 +1213,7 @@ static int __init ep_config_from_table(struct musb *musb) if (epn >= musb->config->num_eps) { pr_debug("%s: invalid ep %d\n", musb_driver_name, epn); - continue; + return -EINVAL; } offset = fifo_setup(musb, hw_ep + epn, cfg++, offset); if (offset < 0) { @@ -1246,9 +1247,10 @@ static int __init ep_config_from_table(struct musb *musb) */ static int __init ep_config_from_hw(struct musb *musb) { - u8 epnum = 0, reg; + u8 epnum = 0; struct musb_hw_ep *hw_ep; void *mbase = musb->mregs; + int ret = 0; DBG(2, "<== static silicon ep config\n"); @@ -1258,26 +1260,9 @@ static int __init ep_config_from_hw(struct musb *musb) musb_ep_select(mbase, epnum); hw_ep = musb->endpoints + epnum; - /* read from core using indexed model */ - reg = musb_readb(hw_ep->regs, 0x10 + MUSB_FIFOSIZE); - if (!reg) { - /* 0's returned when no more endpoints */ + ret = musb_read_fifosize(musb, hw_ep, epnum); + if (ret < 0) break; - } - musb->nr_endpoints++; - musb->epmask |= (1 << epnum); - - hw_ep->max_packet_sz_tx = 1 << (reg & 0x0f); - - /* shared TX/RX FIFO? */ - if ((reg & 0xf0) == 0xf0) { - hw_ep->max_packet_sz_rx = hw_ep->max_packet_sz_tx; - hw_ep->is_shared_fifo = true; - continue; - } else { - hw_ep->max_packet_sz_rx = 1 << ((reg & 0xf0) >> 4); - hw_ep->is_shared_fifo = false; - } /* FIXME set up hw_ep->{rx,tx}_double_buffered */ @@ -1326,7 +1311,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) /* log core options (read using indexed model) */ musb_ep_select(mbase, 0); - reg = musb_readb(mbase, 0x10 + MUSB_CONFIGDATA); + reg = musb_read_configdata(mbase); strcpy(aInfo, (reg & MUSB_CONFIGDATA_UTMIDW) ? "UTMI-16" : "UTMI-8"); if (reg & MUSB_CONFIGDATA_DYNFIFO) @@ -1391,7 +1376,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) } /* log release info */ - hwvers = musb_readw(mbase, MUSB_HWVERS); + hwvers = musb_read_hwvers(mbase); rev_major = (hwvers >> 10) & 0x1f; rev_minor = hwvers & 0x3ff; snprintf(aRevision, 32, "%d.%d%s", rev_major, @@ -1400,8 +1385,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) musb_driver_name, type, aRevision, aDate); /* configure ep0 */ - musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE; - musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE; + musb_configure_ep0(musb); /* discover endpoint configuration */ musb->nr_endpoints = 1; @@ -1445,7 +1429,7 @@ static int __init musb_core_init(u16 musb_type, struct musb *musb) hw_ep->regs = MUSB_EP_OFFSET(i, 0) + mbase; #ifdef CONFIG_USB_MUSB_HDRC_HCD - hw_ep->target_regs = MUSB_BUSCTL_OFFSET(i, 0) + mbase; + hw_ep->target_regs = musb_read_target_reg_base(i, mbase); hw_ep->rx_reinit = 1; hw_ep->tx_reinit = 1; #endif @@ -1671,17 +1655,20 @@ musb_mode_store(struct device *dev, struct device_attribute *attr, { struct musb *musb = dev_to_musb(dev); unsigned long flags; + int status; spin_lock_irqsave(&musb->lock, flags); - if (!strncmp(buf, "host", 4)) - musb_platform_set_mode(musb, MUSB_HOST); - if (!strncmp(buf, "peripheral", 10)) - musb_platform_set_mode(musb, MUSB_PERIPHERAL); - if (!strncmp(buf, "otg", 3)) - musb_platform_set_mode(musb, MUSB_OTG); + if (sysfs_streq(buf, "host")) + status = musb_platform_set_mode(musb, MUSB_HOST); + else if (sysfs_streq(buf, "peripheral")) + status = musb_platform_set_mode(musb, MUSB_PERIPHERAL); + else if (sysfs_streq(buf, "otg")) + status = musb_platform_set_mode(musb, MUSB_OTG); + else + status = -EINVAL; spin_unlock_irqrestore(&musb->lock, flags); - return n; + return (status == 0) ? n : status; } static DEVICE_ATTR(mode, 0644, musb_mode_show, musb_mode_store); @@ -1781,7 +1768,7 @@ allocate_instance(struct device *dev, #ifdef CONFIG_USB_MUSB_HDRC_HCD struct usb_hcd *hcd; - hcd = usb_create_hcd(&musb_hc_driver, dev, dev->bus_id); + hcd = usb_create_hcd(&musb_hc_driver, dev, dev_name(dev)); if (!hcd) return NULL; /* usbcore sets dev->driver_data to hcd, and sometimes uses that... */ @@ -1810,7 +1797,6 @@ allocate_instance(struct device *dev, for (epnum = 0, ep = musb->endpoints; epnum < musb->config->num_eps; epnum++, ep++) { - ep->musb = musb; ep->epnum = epnum; } @@ -1838,7 +1824,7 @@ static void musb_free(struct musb *musb) musb_gadget_cleanup(musb); #endif - if (musb->nIrq >= 0) { + if (musb->nIrq >= 0 && musb->irq_wake) { disable_irq_wake(musb->nIrq); free_irq(musb->nIrq, musb); } @@ -1984,15 +1970,19 @@ bad_config: INIT_WORK(&musb->irq_work, musb_irq_work); /* attach to the IRQ */ - if (request_irq(nIrq, musb->isr, 0, dev->bus_id, musb)) { + if (request_irq(nIrq, musb->isr, 0, dev_name(dev), musb)) { dev_err(dev, "request_irq %d failed!\n", nIrq); status = -ENODEV; goto fail2; } musb->nIrq = nIrq; /* FIXME this handles wakeup irqs wrong */ - if (enable_irq_wake(nIrq) == 0) + if (enable_irq_wake(nIrq) == 0) { + musb->irq_wake = 1; device_init_wakeup(dev, 1); + } else { + musb->irq_wake = 0; + } pr_info("%s: USB %s mode controller at %p using %s, IRQ %d\n", musb_driver_name, diff --git a/drivers/usb/musb/musb_core.h b/drivers/usb/musb/musb_core.h index 82227251931b..630946a2d9fc 100644 --- a/drivers/usb/musb/musb_core.h +++ b/drivers/usb/musb/musb_core.h @@ -191,7 +191,7 @@ enum musb_g_ep0_state { */ #if defined(CONFIG_ARCH_DAVINCI) || defined(CONFIG_ARCH_OMAP2430) \ - || defined(CONFIG_ARCH_OMAP3430) + || defined(CONFIG_ARCH_OMAP3430) || defined(CONFIG_BLACKFIN) /* REVISIT indexed access seemed to * misbehave (on DaVinci) for at least peripheral IN ... */ @@ -359,6 +359,7 @@ struct musb { struct otg_transceiver xceiv; int nIrq; + unsigned irq_wake:1; struct musb_hw_ep endpoints[MUSB_C_NUM_EPS]; #define control_ep endpoints @@ -447,6 +448,70 @@ static inline struct musb *gadget_to_musb(struct usb_gadget *g) } #endif +#ifdef CONFIG_BLACKFIN +static inline int musb_read_fifosize(struct musb *musb, + struct musb_hw_ep *hw_ep, u8 epnum) +{ + musb->nr_endpoints++; + musb->epmask |= (1 << epnum); + + if (epnum < 5) { + hw_ep->max_packet_sz_tx = 128; + hw_ep->max_packet_sz_rx = 128; + } else { + hw_ep->max_packet_sz_tx = 1024; + hw_ep->max_packet_sz_rx = 1024; + } + hw_ep->is_shared_fifo = false; + + return 0; +} + +static inline void musb_configure_ep0(struct musb *musb) +{ + musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].is_shared_fifo = true; +} + +#else + +static inline int musb_read_fifosize(struct musb *musb, + struct musb_hw_ep *hw_ep, u8 epnum) +{ + u8 reg = 0; + + /* read from core using indexed model */ + reg = musb_readb(hw_ep->regs, 0x10 + MUSB_FIFOSIZE); + /* 0's returned when no more endpoints */ + if (!reg) + return -ENODEV; + + musb->nr_endpoints++; + musb->epmask |= (1 << epnum); + + hw_ep->max_packet_sz_tx = 1 << (reg & 0x0f); + + /* shared TX/RX FIFO? */ + if ((reg & 0xf0) == 0xf0) { + hw_ep->max_packet_sz_rx = hw_ep->max_packet_sz_tx; + hw_ep->is_shared_fifo = true; + return 0; + } else { + hw_ep->max_packet_sz_rx = 1 << ((reg & 0xf0) >> 4); + hw_ep->is_shared_fifo = false; + } + + return 0; +} + +static inline void musb_configure_ep0(struct musb *musb) +{ + musb->endpoints[0].max_packet_sz_tx = MUSB_EP0_FIFOSIZE; + musb->endpoints[0].max_packet_sz_rx = MUSB_EP0_FIFOSIZE; +} +#endif /* CONFIG_BLACKFIN */ + /***************************** Glue it together *****************************/ @@ -467,16 +532,16 @@ extern void musb_platform_disable(struct musb *musb); extern void musb_hnp_stop(struct musb *musb); -extern void musb_platform_set_mode(struct musb *musb, u8 musb_mode); +extern int musb_platform_set_mode(struct musb *musb, u8 musb_mode); -#if defined(CONFIG_USB_TUSB6010) || \ +#if defined(CONFIG_USB_TUSB6010) || defined(CONFIG_BLACKFIN) || \ defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP34XX) extern void musb_platform_try_idle(struct musb *musb, unsigned long timeout); #else #define musb_platform_try_idle(x, y) do {} while (0) #endif -#ifdef CONFIG_USB_TUSB6010 +#if defined(CONFIG_USB_TUSB6010) || defined(CONFIG_BLACKFIN) extern int musb_platform_get_vbus_status(struct musb *musb); #else #define musb_platform_get_vbus_status(x) 0 diff --git a/drivers/usb/musb/musb_gadget.c b/drivers/usb/musb/musb_gadget.c index d6a802c224fa..6197daeab8f9 100644 --- a/drivers/usb/musb/musb_gadget.c +++ b/drivers/usb/musb/musb_gadget.c @@ -1633,7 +1633,7 @@ int __init musb_gadget_setup(struct musb *musb) musb->g.speed = USB_SPEED_UNKNOWN; /* this "gadget" abstracts/virtualizes the controller */ - strcpy(musb->g.dev.bus_id, "gadget"); + dev_set_name(&musb->g.dev, "gadget"); musb->g.dev.parent = musb->controller; musb->g.dev.dma_mask = musb->controller->dma_mask; musb->g.dev.release = musb_gadget_release; diff --git a/drivers/usb/musb/musb_host.c b/drivers/usb/musb/musb_host.c index cc64462d4c4e..99fa61234876 100644 --- a/drivers/usb/musb/musb_host.c +++ b/drivers/usb/musb/musb_host.c @@ -112,18 +112,21 @@ static void musb_h_tx_flush_fifo(struct musb_hw_ep *ep) { void __iomem *epio = ep->regs; u16 csr; + u16 lastcsr = 0; int retries = 1000; csr = musb_readw(epio, MUSB_TXCSR); while (csr & MUSB_TXCSR_FIFONOTEMPTY) { - DBG(5, "Host TX FIFONOTEMPTY csr: %02x\n", csr); + if (csr != lastcsr) + DBG(3, "Host TX FIFONOTEMPTY csr: %02x\n", csr); + lastcsr = csr; csr |= MUSB_TXCSR_FLUSHFIFO; musb_writew(epio, MUSB_TXCSR, csr); csr = musb_readw(epio, MUSB_TXCSR); - if (retries-- < 1) { - ERR("Could not flush host TX fifo: csr: %04x\n", csr); + if (WARN(retries-- < 1, + "Could not flush host TX%d fifo: csr: %04x\n", + ep->epnum, csr)) return; - } mdelay(1); } } @@ -268,7 +271,7 @@ __musb_giveback(struct musb *musb, struct urb *urb, int status) __releases(musb->lock) __acquires(musb->lock) { - DBG(({ int level; switch (urb->status) { + DBG(({ int level; switch (status) { case 0: level = 4; break; @@ -283,8 +286,8 @@ __acquires(musb->lock) level = 2; break; }; level; }), - "complete %p (%d), dev%d ep%d%s, %d/%d\n", - urb, urb->status, + "complete %p %pF (%d), dev%d ep%d%s, %d/%d\n", + urb, urb->complete, status, usb_pipedevice(urb->pipe), usb_pipeendpoint(urb->pipe), usb_pipein(urb->pipe) ? "in" : "out", @@ -593,12 +596,10 @@ musb_rx_reinit(struct musb *musb, struct musb_qh *qh, struct musb_hw_ep *ep) /* target addr and (for multipoint) hub addr/port */ if (musb->is_multipoint) { - musb_writeb(ep->target_regs, MUSB_RXFUNCADDR, - qh->addr_reg); - musb_writeb(ep->target_regs, MUSB_RXHUBADDR, - qh->h_addr_reg); - musb_writeb(ep->target_regs, MUSB_RXHUBPORT, - qh->h_port_reg); + musb_write_rxfunaddr(ep->target_regs, qh->addr_reg); + musb_write_rxhubaddr(ep->target_regs, qh->h_addr_reg); + musb_write_rxhubport(ep->target_regs, qh->h_port_reg); + } else musb_writeb(musb->mregs, MUSB_FADDR, qh->addr_reg); @@ -712,15 +713,9 @@ static void musb_ep_program(struct musb *musb, u8 epnum, /* target addr and (for multipoint) hub addr/port */ if (musb->is_multipoint) { - musb_writeb(mbase, - MUSB_BUSCTL_OFFSET(epnum, MUSB_TXFUNCADDR), - qh->addr_reg); - musb_writeb(mbase, - MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBADDR), - qh->h_addr_reg); - musb_writeb(mbase, - MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBPORT), - qh->h_port_reg); + musb_write_txfunaddr(mbase, epnum, qh->addr_reg); + musb_write_txhubaddr(mbase, epnum, qh->h_addr_reg); + musb_write_txhubport(mbase, epnum, qh->h_port_reg); /* FIXME if !epnum, do the same for RX ... */ } else musb_writeb(mbase, MUSB_FADDR, qh->addr_reg); @@ -988,8 +983,10 @@ static bool musb_h_ep0_continue(struct musb *musb, u16 len, struct urb *urb) if (fifo_count) { fifo_dest = (u8 *) (urb->transfer_buffer + urb->actual_length); - DBG(3, "Sending %d bytes to %p\n", - fifo_count, fifo_dest); + DBG(3, "Sending %d byte%s to ep0 fifo %p\n", + fifo_count, + (fifo_count == 1) ? "" : "s", + fifo_dest); musb_write_fifo(hw_ep, fifo_count, fifo_dest); urb->actual_length += fifo_count; diff --git a/drivers/usb/musb/musb_io.h b/drivers/usb/musb/musb_io.h index 223f0a514094..b06e9ef00cfc 100644 --- a/drivers/usb/musb/musb_io.h +++ b/drivers/usb/musb/musb_io.h @@ -39,7 +39,7 @@ #if !defined(CONFIG_ARM) && !defined(CONFIG_SUPERH) \ && !defined(CONFIG_AVR32) && !defined(CONFIG_PPC32) \ - && !defined(CONFIG_PPC64) + && !defined(CONFIG_PPC64) && !defined(CONFIG_BLACKFIN) static inline void readsl(const void __iomem *addr, void *buf, int len) { insl((unsigned long)addr, buf, len); } static inline void readsw(const void __iomem *addr, void *buf, int len) @@ -56,6 +56,8 @@ static inline void writesb(const void __iomem *addr, const void *buf, int len) #endif +#ifndef CONFIG_BLACKFIN + /* NOTE: these offsets are all in bytes */ static inline u16 musb_readw(const void __iomem *addr, unsigned offset) @@ -114,4 +116,26 @@ static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data) #endif /* CONFIG_USB_TUSB6010 */ +#else + +static inline u8 musb_readb(const void __iomem *addr, unsigned offset) + { return (u8) (bfin_read16(addr + offset)); } + +static inline u16 musb_readw(const void __iomem *addr, unsigned offset) + { return bfin_read16(addr + offset); } + +static inline u32 musb_readl(const void __iomem *addr, unsigned offset) + { return (u32) (bfin_read16(addr + offset)); } + +static inline void musb_writeb(void __iomem *addr, unsigned offset, u8 data) + { bfin_write16(addr + offset, (u16) data); } + +static inline void musb_writew(void __iomem *addr, unsigned offset, u16 data) + { bfin_write16(addr + offset, data); } + +static inline void musb_writel(void __iomem *addr, unsigned offset, u32 data) + { bfin_write16(addr + offset, (u16) data); } + +#endif /* CONFIG_BLACKFIN */ + #endif diff --git a/drivers/usb/musb/musb_regs.h b/drivers/usb/musb/musb_regs.h index 9c228661aa5a..de3b2f18db44 100644 --- a/drivers/usb/musb/musb_regs.h +++ b/drivers/usb/musb/musb_regs.h @@ -38,97 +38,6 @@ #define MUSB_EP0_FIFOSIZE 64 /* This is non-configurable */ /* - * Common USB registers - */ - -#define MUSB_FADDR 0x00 /* 8-bit */ -#define MUSB_POWER 0x01 /* 8-bit */ - -#define MUSB_INTRTX 0x02 /* 16-bit */ -#define MUSB_INTRRX 0x04 -#define MUSB_INTRTXE 0x06 -#define MUSB_INTRRXE 0x08 -#define MUSB_INTRUSB 0x0A /* 8 bit */ -#define MUSB_INTRUSBE 0x0B /* 8 bit */ -#define MUSB_FRAME 0x0C -#define MUSB_INDEX 0x0E /* 8 bit */ -#define MUSB_TESTMODE 0x0F /* 8 bit */ - -/* Get offset for a given FIFO from musb->mregs */ -#ifdef CONFIG_USB_TUSB6010 -#define MUSB_FIFO_OFFSET(epnum) (0x200 + ((epnum) * 0x20)) -#else -#define MUSB_FIFO_OFFSET(epnum) (0x20 + ((epnum) * 4)) -#endif - -/* - * Additional Control Registers - */ - -#define MUSB_DEVCTL 0x60 /* 8 bit */ - -/* These are always controlled through the INDEX register */ -#define MUSB_TXFIFOSZ 0x62 /* 8-bit (see masks) */ -#define MUSB_RXFIFOSZ 0x63 /* 8-bit (see masks) */ -#define MUSB_TXFIFOADD 0x64 /* 16-bit offset shifted right 3 */ -#define MUSB_RXFIFOADD 0x66 /* 16-bit offset shifted right 3 */ - -/* REVISIT: vctrl/vstatus: optional vendor utmi+phy register at 0x68 */ -#define MUSB_HWVERS 0x6C /* 8 bit */ - -#define MUSB_EPINFO 0x78 /* 8 bit */ -#define MUSB_RAMINFO 0x79 /* 8 bit */ -#define MUSB_LINKINFO 0x7a /* 8 bit */ -#define MUSB_VPLEN 0x7b /* 8 bit */ -#define MUSB_HS_EOF1 0x7c /* 8 bit */ -#define MUSB_FS_EOF1 0x7d /* 8 bit */ -#define MUSB_LS_EOF1 0x7e /* 8 bit */ - -/* Offsets to endpoint registers */ -#define MUSB_TXMAXP 0x00 -#define MUSB_TXCSR 0x02 -#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */ -#define MUSB_RXMAXP 0x04 -#define MUSB_RXCSR 0x06 -#define MUSB_RXCOUNT 0x08 -#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */ -#define MUSB_TXTYPE 0x0A -#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */ -#define MUSB_TXINTERVAL 0x0B -#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */ -#define MUSB_RXTYPE 0x0C -#define MUSB_RXINTERVAL 0x0D -#define MUSB_FIFOSIZE 0x0F -#define MUSB_CONFIGDATA MUSB_FIFOSIZE /* Re-used for EP0 */ - -/* Offsets to endpoint registers in indexed model (using INDEX register) */ -#define MUSB_INDEXED_OFFSET(_epnum, _offset) \ - (0x10 + (_offset)) - -/* Offsets to endpoint registers in flat models */ -#define MUSB_FLAT_OFFSET(_epnum, _offset) \ - (0x100 + (0x10*(_epnum)) + (_offset)) - -#ifdef CONFIG_USB_TUSB6010 -/* TUSB6010 EP0 configuration register is special */ -#define MUSB_TUSB_OFFSET(_epnum, _offset) \ - (0x10 + _offset) -#include "tusb6010.h" /* Needed "only" for TUSB_EP0_CONF */ -#endif - -/* "bus control"/target registers, for host side multipoint (external hubs) */ -#define MUSB_TXFUNCADDR 0x00 -#define MUSB_TXHUBADDR 0x02 -#define MUSB_TXHUBPORT 0x03 - -#define MUSB_RXFUNCADDR 0x04 -#define MUSB_RXHUBADDR 0x06 -#define MUSB_RXHUBPORT 0x07 - -#define MUSB_BUSCTL_OFFSET(_epnum, _offset) \ - (0x80 + (8*(_epnum)) + (_offset)) - -/* * MUSB Register bits */ @@ -228,7 +137,6 @@ /* TXCSR in Peripheral and Host mode */ #define MUSB_TXCSR_AUTOSET 0x8000 -#define MUSB_TXCSR_MODE 0x2000 #define MUSB_TXCSR_DMAENAB 0x1000 #define MUSB_TXCSR_FRCDATATOG 0x0800 #define MUSB_TXCSR_DMAMODE 0x0400 @@ -297,4 +205,309 @@ /* HUBADDR */ #define MUSB_HUBADDR_MULTI_TT 0x80 + +#ifndef CONFIG_BLACKFIN + +/* + * Common USB registers + */ + +#define MUSB_FADDR 0x00 /* 8-bit */ +#define MUSB_POWER 0x01 /* 8-bit */ + +#define MUSB_INTRTX 0x02 /* 16-bit */ +#define MUSB_INTRRX 0x04 +#define MUSB_INTRTXE 0x06 +#define MUSB_INTRRXE 0x08 +#define MUSB_INTRUSB 0x0A /* 8 bit */ +#define MUSB_INTRUSBE 0x0B /* 8 bit */ +#define MUSB_FRAME 0x0C +#define MUSB_INDEX 0x0E /* 8 bit */ +#define MUSB_TESTMODE 0x0F /* 8 bit */ + +/* Get offset for a given FIFO from musb->mregs */ +#ifdef CONFIG_USB_TUSB6010 +#define MUSB_FIFO_OFFSET(epnum) (0x200 + ((epnum) * 0x20)) +#else +#define MUSB_FIFO_OFFSET(epnum) (0x20 + ((epnum) * 4)) +#endif + +/* + * Additional Control Registers + */ + +#define MUSB_DEVCTL 0x60 /* 8 bit */ + +/* These are always controlled through the INDEX register */ +#define MUSB_TXFIFOSZ 0x62 /* 8-bit (see masks) */ +#define MUSB_RXFIFOSZ 0x63 /* 8-bit (see masks) */ +#define MUSB_TXFIFOADD 0x64 /* 16-bit offset shifted right 3 */ +#define MUSB_RXFIFOADD 0x66 /* 16-bit offset shifted right 3 */ + +/* REVISIT: vctrl/vstatus: optional vendor utmi+phy register at 0x68 */ +#define MUSB_HWVERS 0x6C /* 8 bit */ + +#define MUSB_EPINFO 0x78 /* 8 bit */ +#define MUSB_RAMINFO 0x79 /* 8 bit */ +#define MUSB_LINKINFO 0x7a /* 8 bit */ +#define MUSB_VPLEN 0x7b /* 8 bit */ +#define MUSB_HS_EOF1 0x7c /* 8 bit */ +#define MUSB_FS_EOF1 0x7d /* 8 bit */ +#define MUSB_LS_EOF1 0x7e /* 8 bit */ + +/* Offsets to endpoint registers */ +#define MUSB_TXMAXP 0x00 +#define MUSB_TXCSR 0x02 +#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */ +#define MUSB_RXMAXP 0x04 +#define MUSB_RXCSR 0x06 +#define MUSB_RXCOUNT 0x08 +#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */ +#define MUSB_TXTYPE 0x0A +#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */ +#define MUSB_TXINTERVAL 0x0B +#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */ +#define MUSB_RXTYPE 0x0C +#define MUSB_RXINTERVAL 0x0D +#define MUSB_FIFOSIZE 0x0F +#define MUSB_CONFIGDATA MUSB_FIFOSIZE /* Re-used for EP0 */ + +/* Offsets to endpoint registers in indexed model (using INDEX register) */ +#define MUSB_INDEXED_OFFSET(_epnum, _offset) \ + (0x10 + (_offset)) + +/* Offsets to endpoint registers in flat models */ +#define MUSB_FLAT_OFFSET(_epnum, _offset) \ + (0x100 + (0x10*(_epnum)) + (_offset)) + +#ifdef CONFIG_USB_TUSB6010 +/* TUSB6010 EP0 configuration register is special */ +#define MUSB_TUSB_OFFSET(_epnum, _offset) \ + (0x10 + _offset) +#include "tusb6010.h" /* Needed "only" for TUSB_EP0_CONF */ +#endif + +#define MUSB_TXCSR_MODE 0x2000 + +/* "bus control"/target registers, for host side multipoint (external hubs) */ +#define MUSB_TXFUNCADDR 0x00 +#define MUSB_TXHUBADDR 0x02 +#define MUSB_TXHUBPORT 0x03 + +#define MUSB_RXFUNCADDR 0x04 +#define MUSB_RXHUBADDR 0x06 +#define MUSB_RXHUBPORT 0x07 + +#define MUSB_BUSCTL_OFFSET(_epnum, _offset) \ + (0x80 + (8*(_epnum)) + (_offset)) + +static inline void musb_write_txfifosz(void __iomem *mbase, u8 c_size) +{ + musb_writeb(mbase, MUSB_TXFIFOSZ, c_size); +} + +static inline void musb_write_txfifoadd(void __iomem *mbase, u16 c_off) +{ + musb_writew(mbase, MUSB_TXFIFOADD, c_off); +} + +static inline void musb_write_rxfifosz(void __iomem *mbase, u8 c_size) +{ + musb_writeb(mbase, MUSB_RXFIFOSZ, c_size); +} + +static inline void musb_write_rxfifoadd(void __iomem *mbase, u16 c_off) +{ + musb_writew(mbase, MUSB_RXFIFOADD, c_off); +} + +static inline u8 musb_read_configdata(void __iomem *mbase) +{ + return musb_readb(mbase, 0x10 + MUSB_CONFIGDATA); +} + +static inline u16 musb_read_hwvers(void __iomem *mbase) +{ + return musb_readw(mbase, MUSB_HWVERS); +} + +static inline void __iomem *musb_read_target_reg_base(u8 i, void __iomem *mbase) +{ + return (MUSB_BUSCTL_OFFSET(i, 0) + mbase); +} + +static inline void musb_write_rxfunaddr(void __iomem *ep_target_regs, + u8 qh_addr_reg) +{ + musb_writeb(ep_target_regs, MUSB_RXFUNCADDR, qh_addr_reg); +} + +static inline void musb_write_rxhubaddr(void __iomem *ep_target_regs, + u8 qh_h_addr_reg) +{ + musb_writeb(ep_target_regs, MUSB_RXHUBADDR, qh_h_addr_reg); +} + +static inline void musb_write_rxhubport(void __iomem *ep_target_regs, + u8 qh_h_port_reg) +{ + musb_writeb(ep_target_regs, MUSB_RXHUBPORT, qh_h_port_reg); +} + +static inline void musb_write_txfunaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ + musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXFUNCADDR), + qh_addr_reg); +} + +static inline void musb_write_txhubaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ + musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBADDR), + qh_addr_reg); +} + +static inline void musb_write_txhubport(void __iomem *mbase, u8 epnum, + u8 qh_h_port_reg) +{ + musb_writeb(mbase, MUSB_BUSCTL_OFFSET(epnum, MUSB_TXHUBPORT), + qh_h_port_reg); +} + +#else /* CONFIG_BLACKFIN */ + +#define USB_BASE USB_FADDR +#define USB_OFFSET(reg) (reg - USB_BASE) + +/* + * Common USB registers + */ +#define MUSB_FADDR USB_OFFSET(USB_FADDR) /* 8-bit */ +#define MUSB_POWER USB_OFFSET(USB_POWER) /* 8-bit */ +#define MUSB_INTRTX USB_OFFSET(USB_INTRTX) /* 16-bit */ +#define MUSB_INTRRX USB_OFFSET(USB_INTRRX) +#define MUSB_INTRTXE USB_OFFSET(USB_INTRTXE) +#define MUSB_INTRRXE USB_OFFSET(USB_INTRRXE) +#define MUSB_INTRUSB USB_OFFSET(USB_INTRUSB) /* 8 bit */ +#define MUSB_INTRUSBE USB_OFFSET(USB_INTRUSBE)/* 8 bit */ +#define MUSB_FRAME USB_OFFSET(USB_FRAME) +#define MUSB_INDEX USB_OFFSET(USB_INDEX) /* 8 bit */ +#define MUSB_TESTMODE USB_OFFSET(USB_TESTMODE)/* 8 bit */ + +/* Get offset for a given FIFO from musb->mregs */ +#define MUSB_FIFO_OFFSET(epnum) \ + (USB_OFFSET(USB_EP0_FIFO) + ((epnum) * 8)) + +/* + * Additional Control Registers + */ + +#define MUSB_DEVCTL USB_OFFSET(USB_OTG_DEV_CTL) /* 8 bit */ + +#define MUSB_LINKINFO USB_OFFSET(USB_LINKINFO)/* 8 bit */ +#define MUSB_VPLEN USB_OFFSET(USB_VPLEN) /* 8 bit */ +#define MUSB_HS_EOF1 USB_OFFSET(USB_HS_EOF1) /* 8 bit */ +#define MUSB_FS_EOF1 USB_OFFSET(USB_FS_EOF1) /* 8 bit */ +#define MUSB_LS_EOF1 USB_OFFSET(USB_LS_EOF1) /* 8 bit */ + +/* Offsets to endpoint registers */ +#define MUSB_TXMAXP 0x00 +#define MUSB_TXCSR 0x04 +#define MUSB_CSR0 MUSB_TXCSR /* Re-used for EP0 */ +#define MUSB_RXMAXP 0x08 +#define MUSB_RXCSR 0x0C +#define MUSB_RXCOUNT 0x10 +#define MUSB_COUNT0 MUSB_RXCOUNT /* Re-used for EP0 */ +#define MUSB_TXTYPE 0x14 +#define MUSB_TYPE0 MUSB_TXTYPE /* Re-used for EP0 */ +#define MUSB_TXINTERVAL 0x18 +#define MUSB_NAKLIMIT0 MUSB_TXINTERVAL /* Re-used for EP0 */ +#define MUSB_RXTYPE 0x1C +#define MUSB_RXINTERVAL 0x20 +#define MUSB_TXCOUNT 0x28 + +/* Offsets to endpoint registers in indexed model (using INDEX register) */ +#define MUSB_INDEXED_OFFSET(_epnum, _offset) \ + (0x40 + (_offset)) + +/* Offsets to endpoint registers in flat models */ +#define MUSB_FLAT_OFFSET(_epnum, _offset) \ + (USB_OFFSET(USB_EP_NI0_TXMAXP) + (0x40 * (_epnum)) + (_offset)) + +/* Not implemented - HW has seperate Tx/Rx FIFO */ +#define MUSB_TXCSR_MODE 0x0000 + +/* + * Dummy stub for clk framework, it will be removed + * until Blackfin supports clk framework + */ +#define clk_get(dev, id) NULL +#define clk_put(clock) do {} while (0) +#define clk_enable(clock) do {} while (0) +#define clk_disable(clock) do {} while (0) + +static inline void musb_write_txfifosz(void __iomem *mbase, u8 c_size) +{ +} + +static inline void musb_write_txfifoadd(void __iomem *mbase, u16 c_off) +{ +} + +static inline void musb_write_rxfifosz(void __iomem *mbase, u8 c_size) +{ +} + +static inline void musb_write_rxfifoadd(void __iomem *mbase, u16 c_off) +{ +} + +static inline u8 musb_read_configdata(void __iomem *mbase) +{ + return 0; +} + +static inline u16 musb_read_hwvers(void __iomem *mbase) +{ + return 0; +} + +static inline u16 musb_read_target_reg_base(u8 i, void __iomem *mbase) +{ + return 0; +} + +static inline void musb_write_rxfunaddr(void __iomem *ep_target_regs, + u8 qh_addr_req) +{ +} + +static inline void musb_write_rxhubaddr(void __iomem *ep_target_regs, + u8 qh_h_addr_reg) +{ +} + +static inline void musb_write_rxhubport(void __iomem *ep_target_regs, + u8 qh_h_port_reg) +{ +} + +static inline void musb_write_txfunaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ +} + +static inline void musb_write_txhubaddr(void __iomem *mbase, u8 epnum, + u8 qh_addr_reg) +{ +} + +static inline void musb_write_txhubport(void __iomem *mbase, u8 epnum, + u8 qh_h_port_reg) +{ +} + +#endif /* CONFIG_BLACKFIN */ + #endif /* __MUSB_REGS_H__ */ diff --git a/drivers/usb/musb/musbhsdma.c b/drivers/usb/musb/musbhsdma.c index 8c734ef2c1ed..8662e9e159c3 100644 --- a/drivers/usb/musb/musbhsdma.c +++ b/drivers/usb/musb/musbhsdma.c @@ -34,58 +34,7 @@ #include <linux/interrupt.h> #include <linux/platform_device.h> #include "musb_core.h" - -#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3430) -#include "omap2430.h" -#endif - -#define MUSB_HSDMA_BASE 0x200 -#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0) -#define MUSB_HSDMA_CONTROL 0x4 -#define MUSB_HSDMA_ADDRESS 0x8 -#define MUSB_HSDMA_COUNT 0xc - -#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \ - (MUSB_HSDMA_BASE + (_bchannel << 4) + _offset) - -/* control register (16-bit): */ -#define MUSB_HSDMA_ENABLE_SHIFT 0 -#define MUSB_HSDMA_TRANSMIT_SHIFT 1 -#define MUSB_HSDMA_MODE1_SHIFT 2 -#define MUSB_HSDMA_IRQENABLE_SHIFT 3 -#define MUSB_HSDMA_ENDPOINT_SHIFT 4 -#define MUSB_HSDMA_BUSERROR_SHIFT 8 -#define MUSB_HSDMA_BURSTMODE_SHIFT 9 -#define MUSB_HSDMA_BURSTMODE (3 << MUSB_HSDMA_BURSTMODE_SHIFT) -#define MUSB_HSDMA_BURSTMODE_UNSPEC 0 -#define MUSB_HSDMA_BURSTMODE_INCR4 1 -#define MUSB_HSDMA_BURSTMODE_INCR8 2 -#define MUSB_HSDMA_BURSTMODE_INCR16 3 - -#define MUSB_HSDMA_CHANNELS 8 - -struct musb_dma_controller; - -struct musb_dma_channel { - struct dma_channel channel; - struct musb_dma_controller *controller; - u32 start_addr; - u32 len; - u16 max_packet_sz; - u8 idx; - u8 epnum; - u8 transmit; -}; - -struct musb_dma_controller { - struct dma_controller controller; - struct musb_dma_channel channel[MUSB_HSDMA_CHANNELS]; - void *private_data; - void __iomem *base; - u8 channel_count; - u8 used_channels; - u8 irq; -}; +#include "musbhsdma.h" static int dma_controller_start(struct dma_controller *c) { @@ -203,12 +152,8 @@ static void configure_channel(struct dma_channel *channel, : 0); /* address/count */ - musb_writel(mbase, - MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS), - dma_addr); - musb_writel(mbase, - MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT), - len); + musb_write_hsdma_addr(mbase, bchannel, dma_addr); + musb_write_hsdma_count(mbase, bchannel, len); /* control (this should start things) */ musb_writew(mbase, @@ -279,13 +224,8 @@ static int dma_channel_abort(struct dma_channel *channel) musb_writew(mbase, MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_CONTROL), 0); - musb_writel(mbase, - MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS), - 0); - musb_writel(mbase, - MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT), - 0); - + musb_write_hsdma_addr(mbase, bchannel, 0); + musb_write_hsdma_count(mbase, bchannel, 0); channel->status = MUSB_DMA_STATUS_FREE; } @@ -333,10 +273,8 @@ static irqreturn_t dma_controller_irq(int irq, void *private_data) } else { u8 devctl; - addr = musb_readl(mbase, - MUSB_HSDMA_CHANNEL_OFFSET( - bchannel, - MUSB_HSDMA_ADDRESS)); + addr = musb_read_hsdma_addr(mbase, + bchannel); channel->actual_len = addr - musb_channel->start_addr; @@ -375,6 +313,12 @@ static irqreturn_t dma_controller_irq(int irq, void *private_data) } } } + +#ifdef CONFIG_BLACKFIN + /* Clear DMA interrup flags */ + musb_writeb(mbase, MUSB_HSDMA_INTR, int_hsdma); +#endif + retval = IRQ_HANDLED; done: spin_unlock_irqrestore(&musb->lock, flags); @@ -424,7 +368,7 @@ dma_controller_create(struct musb *musb, void __iomem *base) controller->controller.channel_abort = dma_channel_abort; if (request_irq(irq, dma_controller_irq, IRQF_DISABLED, - musb->controller->bus_id, &controller->controller)) { + dev_name(musb->controller), &controller->controller)) { dev_err(dev, "request_irq %d failed!\n", irq); dma_controller_destroy(&controller->controller); diff --git a/drivers/usb/musb/musbhsdma.h b/drivers/usb/musb/musbhsdma.h new file mode 100644 index 000000000000..1299d92dc83f --- /dev/null +++ b/drivers/usb/musb/musbhsdma.h @@ -0,0 +1,149 @@ +/* + * MUSB OTG driver - support for Mentor's DMA controller + * + * Copyright 2005 Mentor Graphics Corporation + * Copyright (C) 2005-2007 by Texas Instruments + * + * 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. + * + * 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 + * + * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN + * NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON + * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +#if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3430) +#include "omap2430.h" +#endif + +#ifndef CONFIG_BLACKFIN + +#define MUSB_HSDMA_BASE 0x200 +#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0) +#define MUSB_HSDMA_CONTROL 0x4 +#define MUSB_HSDMA_ADDRESS 0x8 +#define MUSB_HSDMA_COUNT 0xc + +#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \ + (MUSB_HSDMA_BASE + (_bchannel << 4) + _offset) + +#define musb_read_hsdma_addr(mbase, bchannel) \ + musb_readl(mbase, \ + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS)) + +#define musb_write_hsdma_addr(mbase, bchannel, addr) \ + musb_writel(mbase, \ + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDRESS), \ + addr) + +#define musb_write_hsdma_count(mbase, bchannel, len) \ + musb_writel(mbase, \ + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT), \ + len) +#else + +#define MUSB_HSDMA_BASE 0x400 +#define MUSB_HSDMA_INTR (MUSB_HSDMA_BASE + 0) +#define MUSB_HSDMA_CONTROL 0x04 +#define MUSB_HSDMA_ADDR_LOW 0x08 +#define MUSB_HSDMA_ADDR_HIGH 0x0C +#define MUSB_HSDMA_COUNT_LOW 0x10 +#define MUSB_HSDMA_COUNT_HIGH 0x14 + +#define MUSB_HSDMA_CHANNEL_OFFSET(_bchannel, _offset) \ + (MUSB_HSDMA_BASE + (_bchannel * 0x20) + _offset) + +static inline u32 musb_read_hsdma_addr(void __iomem *mbase, u8 bchannel) +{ + u32 addr = musb_readw(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_HIGH)); + + addr = addr << 16; + + addr |= musb_readw(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_LOW)); + + return addr; +} + +static inline void musb_write_hsdma_addr(void __iomem *mbase, + u8 bchannel, dma_addr_t dma_addr) +{ + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_LOW), + ((u16)((u32) dma_addr & 0xFFFF))); + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_ADDR_HIGH), + ((u16)(((u32) dma_addr >> 16) & 0xFFFF))); +} + +static inline void musb_write_hsdma_count(void __iomem *mbase, + u8 bchannel, u32 len) +{ + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_LOW), + ((u16)((u32) len & 0xFFFF))); + musb_writew(mbase, + MUSB_HSDMA_CHANNEL_OFFSET(bchannel, MUSB_HSDMA_COUNT_HIGH), + ((u16)(((u32) len >> 16) & 0xFFFF))); +} + +#endif /* CONFIG_BLACKFIN */ + +/* control register (16-bit): */ +#define MUSB_HSDMA_ENABLE_SHIFT 0 +#define MUSB_HSDMA_TRANSMIT_SHIFT 1 +#define MUSB_HSDMA_MODE1_SHIFT 2 +#define MUSB_HSDMA_IRQENABLE_SHIFT 3 +#define MUSB_HSDMA_ENDPOINT_SHIFT 4 +#define MUSB_HSDMA_BUSERROR_SHIFT 8 +#define MUSB_HSDMA_BURSTMODE_SHIFT 9 +#define MUSB_HSDMA_BURSTMODE (3 << MUSB_HSDMA_BURSTMODE_SHIFT) +#define MUSB_HSDMA_BURSTMODE_UNSPEC 0 +#define MUSB_HSDMA_BURSTMODE_INCR4 1 +#define MUSB_HSDMA_BURSTMODE_INCR8 2 +#define MUSB_HSDMA_BURSTMODE_INCR16 3 + +#define MUSB_HSDMA_CHANNELS 8 + +struct musb_dma_controller; + +struct musb_dma_channel { + struct dma_channel channel; + struct musb_dma_controller *controller; + u32 start_addr; + u32 len; + u16 max_packet_sz; + u8 idx; + u8 epnum; + u8 transmit; +}; + +struct musb_dma_controller { + struct dma_controller controller; + struct musb_dma_channel channel[MUSB_HSDMA_CHANNELS]; + void *private_data; + void __iomem *base; + u8 channel_count; + u8 used_channels; + u8 irq; +}; diff --git a/drivers/usb/musb/omap2430.c b/drivers/usb/musb/omap2430.c index ce6c162920f7..901dffdf23b1 100644 --- a/drivers/usb/musb/omap2430.c +++ b/drivers/usb/musb/omap2430.c @@ -58,10 +58,10 @@ static void musb_do_idle(unsigned long _musb) #endif u8 devctl; - devctl = musb_readb(musb->mregs, MUSB_DEVCTL); - spin_lock_irqsave(&musb->lock, flags); + devctl = musb_readb(musb->mregs, MUSB_DEVCTL); + switch (musb->xceiv.state) { case OTG_STATE_A_WAIT_BCON: devctl &= ~MUSB_DEVCTL_SESSION; @@ -196,7 +196,7 @@ static int omap_set_power(struct otg_transceiver *x, unsigned mA) static int musb_platform_resume(struct musb *musb); -void musb_platform_set_mode(struct musb *musb, u8 musb_mode) +int musb_platform_set_mode(struct musb *musb, u8 musb_mode) { u8 devctl = musb_readb(musb->mregs, MUSB_DEVCTL); @@ -204,15 +204,24 @@ void musb_platform_set_mode(struct musb *musb, u8 musb_mode) musb_writeb(musb->mregs, MUSB_DEVCTL, devctl); switch (musb_mode) { +#ifdef CONFIG_USB_MUSB_HDRC_HCD case MUSB_HOST: otg_set_host(&musb->xceiv, musb->xceiv.host); break; +#endif +#ifdef CONFIG_USB_GADGET_MUSB_HDRC case MUSB_PERIPHERAL: otg_set_peripheral(&musb->xceiv, musb->xceiv.gadget); break; +#endif +#ifdef CONFIG_USB_MUSB_OTG case MUSB_OTG: break; +#endif + default: + return -EINVAL; } + return 0; } int __init musb_platform_init(struct musb *musb) diff --git a/drivers/usb/musb/tusb6010.c b/drivers/usb/musb/tusb6010.c index ee8fca92a4ac..9e20fd070d71 100644 --- a/drivers/usb/musb/tusb6010.c +++ b/drivers/usb/musb/tusb6010.c @@ -598,7 +598,7 @@ static void tusb_source_power(struct musb *musb, int is_on) * and peripheral modes in non-OTG configurations by reconfiguring hardware * and then setting musb->board_mode. For now, only support OTG mode. */ -void musb_platform_set_mode(struct musb *musb, u8 musb_mode) +int musb_platform_set_mode(struct musb *musb, u8 musb_mode) { void __iomem *tbase = musb->ctrl_base; u32 otg_stat, phy_otg_ctrl, phy_otg_ena, dev_conf; @@ -641,7 +641,8 @@ void musb_platform_set_mode(struct musb *musb, u8 musb_mode) #endif default: - DBG(2, "Trying to set unknown mode %i\n", musb_mode); + DBG(2, "Trying to set mode %i\n", musb_mode); + return -EINVAL; } musb_writel(tbase, TUSB_PHY_OTG_CTRL, @@ -655,6 +656,8 @@ void musb_platform_set_mode(struct musb *musb, u8 musb_mode) !(otg_stat & TUSB_DEV_OTG_STAT_ID_STATUS)) INFO("Cannot be peripheral with mini-A cable " "otg_stat: %08x\n", otg_stat); + + return 0; } static inline unsigned long diff --git a/drivers/usb/otg/Kconfig b/drivers/usb/otg/Kconfig new file mode 100644 index 000000000000..8e8dbdb9b39b --- /dev/null +++ b/drivers/usb/otg/Kconfig @@ -0,0 +1,54 @@ +# +# USB OTG infrastructure may be needed for peripheral-only, host-only, +# or OTG-capable configurations when OTG transceivers or controllers +# are used. +# + +comment "OTG and related infrastructure" + +if USB || USB_GADGET + +config USB_OTG_UTILS + bool + help + Select this to make sure the build includes objects from + the OTG infrastructure directory. + +# +# USB Transceiver Drivers +# +config USB_GPIO_VBUS + tristate "GPIO based peripheral-only VBUS sensing 'transceiver'" + depends on GENERIC_GPIO + select USB_OTG_UTILS + help + Provides simple GPIO VBUS sensing for controllers with an + internal transceiver via the otg_transceiver interface, and + optionally control of a D+ pullup GPIO as well as a VBUS + current limit regulator. + +config ISP1301_OMAP + tristate "Philips ISP1301 with OMAP OTG" + depends on I2C && ARCH_OMAP_OTG + select USB_OTG_UTILS + help + If you say yes here you get support for the Philips ISP1301 + USB-On-The-Go transceiver working with the OMAP OTG controller. + The ISP1301 is a full speed USB transceiver which is used in + products including H2, H3, and H4 development boards for Texas + Instruments OMAP processors. + + This driver can also be built as a module. If so, the module + will be called isp1301_omap. + +config TWL4030_USB + tristate "TWL4030 USB Transceiver Driver" + depends on TWL4030_CORE + select USB_OTG_UTILS + help + Enable this to support the USB OTG transceiver on TWL4030 + family chips (including the TWL5030 and TPS659x0 devices). + This transceiver supports high and full speed devices plus, + in host mode, low speed. + +endif # USB || OTG diff --git a/drivers/usb/otg/Makefile b/drivers/usb/otg/Makefile new file mode 100644 index 000000000000..d73c7cf5e2f7 --- /dev/null +++ b/drivers/usb/otg/Makefile @@ -0,0 +1,15 @@ +# +# OTG infrastructure and transceiver drivers +# + +# infrastructure +obj-$(CONFIG_USB_OTG_UTILS) += otg.o + +# transceiver drivers +obj-$(CONFIG_USB_GPIO_VBUS) += gpio_vbus.o +obj-$(CONFIG_ISP1301_OMAP) += isp1301_omap.o +obj-$(CONFIG_TWL4030_USB) += twl4030-usb.o + +ccflags-$(CONFIG_USB_DEBUG) += -DDEBUG +ccflags-$(CONFIG_USB_GADGET_DEBUG) += -DDEBUG + diff --git a/drivers/usb/otg/gpio_vbus.c b/drivers/usb/otg/gpio_vbus.c new file mode 100644 index 000000000000..63a6036f04be --- /dev/null +++ b/drivers/usb/otg/gpio_vbus.c @@ -0,0 +1,335 @@ +/* + * gpio-vbus.c - simple GPIO VBUS sensing driver for B peripheral devices + * + * Copyright (c) 2008 Philipp Zabel <philipp.zabel@gmail.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. + */ + +#include <linux/kernel.h> +#include <linux/platform_device.h> +#include <linux/gpio.h> +#include <linux/interrupt.h> +#include <linux/usb.h> + +#include <linux/regulator/consumer.h> + +#include <linux/usb/gadget.h> +#include <linux/usb/gpio_vbus.h> +#include <linux/usb/otg.h> + + +/* + * A simple GPIO VBUS sensing driver for B peripheral only devices + * with internal transceivers. It can control a D+ pullup GPIO and + * a regulator to limit the current drawn from VBUS. + * + * Needs to be loaded before the UDC driver that will use it. + */ +struct gpio_vbus_data { + struct otg_transceiver otg; + struct device *dev; + struct regulator *vbus_draw; + int vbus_draw_enabled; + unsigned mA; +}; + + +/* + * This driver relies on "both edges" triggering. VBUS has 100 msec to + * stabilize, so the peripheral controller driver may need to cope with + * some bouncing due to current surges (e.g. charging local capacitance) + * and contact chatter. + * + * REVISIT in desperate straits, toggling between rising and falling + * edges might be workable. + */ +#define VBUS_IRQ_FLAGS \ + ( IRQF_SAMPLE_RANDOM | IRQF_SHARED \ + | IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING ) + + +/* interface to regulator framework */ +static void set_vbus_draw(struct gpio_vbus_data *gpio_vbus, unsigned mA) +{ + struct regulator *vbus_draw = gpio_vbus->vbus_draw; + int enabled; + + if (!vbus_draw) + return; + + enabled = gpio_vbus->vbus_draw_enabled; + if (mA) { + regulator_set_current_limit(vbus_draw, 0, 1000 * mA); + if (!enabled) { + regulator_enable(vbus_draw); + gpio_vbus->vbus_draw_enabled = 1; + } + } else { + if (enabled) { + regulator_disable(vbus_draw); + gpio_vbus->vbus_draw_enabled = 0; + } + } + gpio_vbus->mA = mA; +} + +/* VBUS change IRQ handler */ +static irqreturn_t gpio_vbus_irq(int irq, void *data) +{ + struct platform_device *pdev = data; + struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; + struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); + int gpio, vbus; + + vbus = gpio_get_value(pdata->gpio_vbus); + if (pdata->gpio_vbus_inverted) + vbus = !vbus; + + dev_dbg(&pdev->dev, "VBUS %s (gadget: %s)\n", + vbus ? "supplied" : "inactive", + gpio_vbus->otg.gadget ? gpio_vbus->otg.gadget->name : "none"); + + if (!gpio_vbus->otg.gadget) + return IRQ_HANDLED; + + /* Peripheral controllers which manage the pullup themselves won't have + * gpio_pullup configured here. If it's configured here, we'll do what + * isp1301_omap::b_peripheral() does and enable the pullup here... although + * that may complicate usb_gadget_{,dis}connect() support. + */ + gpio = pdata->gpio_pullup; + if (vbus) { + gpio_vbus->otg.state = OTG_STATE_B_PERIPHERAL; + usb_gadget_vbus_connect(gpio_vbus->otg.gadget); + + /* drawing a "unit load" is *always* OK, except for OTG */ + set_vbus_draw(gpio_vbus, 100); + + /* optionally enable D+ pullup */ + if (gpio_is_valid(gpio)) + gpio_set_value(gpio, !pdata->gpio_pullup_inverted); + } else { + /* optionally disable D+ pullup */ + if (gpio_is_valid(gpio)) + gpio_set_value(gpio, pdata->gpio_pullup_inverted); + + set_vbus_draw(gpio_vbus, 0); + + usb_gadget_vbus_disconnect(gpio_vbus->otg.gadget); + gpio_vbus->otg.state = OTG_STATE_B_IDLE; + } + + return IRQ_HANDLED; +} + +/* OTG transceiver interface */ + +/* bind/unbind the peripheral controller */ +static int gpio_vbus_set_peripheral(struct otg_transceiver *otg, + struct usb_gadget *gadget) +{ + struct gpio_vbus_data *gpio_vbus; + struct gpio_vbus_mach_info *pdata; + struct platform_device *pdev; + int gpio, irq; + + gpio_vbus = container_of(otg, struct gpio_vbus_data, otg); + pdev = to_platform_device(gpio_vbus->dev); + pdata = gpio_vbus->dev->platform_data; + irq = gpio_to_irq(pdata->gpio_vbus); + gpio = pdata->gpio_pullup; + + if (!gadget) { + dev_dbg(&pdev->dev, "unregistering gadget '%s'\n", + otg->gadget->name); + + /* optionally disable D+ pullup */ + if (gpio_is_valid(gpio)) + gpio_set_value(gpio, pdata->gpio_pullup_inverted); + + set_vbus_draw(gpio_vbus, 0); + + usb_gadget_vbus_disconnect(otg->gadget); + otg->state = OTG_STATE_UNDEFINED; + + otg->gadget = NULL; + return 0; + } + + otg->gadget = gadget; + dev_dbg(&pdev->dev, "registered gadget '%s'\n", gadget->name); + + /* initialize connection state */ + gpio_vbus_irq(irq, pdev); + return 0; +} + +/* effective for B devices, ignored for A-peripheral */ +static int gpio_vbus_set_power(struct otg_transceiver *otg, unsigned mA) +{ + struct gpio_vbus_data *gpio_vbus; + + gpio_vbus = container_of(otg, struct gpio_vbus_data, otg); + + if (otg->state == OTG_STATE_B_PERIPHERAL) + set_vbus_draw(gpio_vbus, mA); + return 0; +} + +/* for non-OTG B devices: set/clear transceiver suspend mode */ +static int gpio_vbus_set_suspend(struct otg_transceiver *otg, int suspend) +{ + struct gpio_vbus_data *gpio_vbus; + + gpio_vbus = container_of(otg, struct gpio_vbus_data, otg); + + /* draw max 0 mA from vbus in suspend mode; or the previously + * recorded amount of current if not suspended + * + * NOTE: high powered configs (mA > 100) may draw up to 2.5 mA + * if they're wake-enabled ... we don't handle that yet. + */ + return gpio_vbus_set_power(otg, suspend ? 0 : gpio_vbus->mA); +} + +/* platform driver interface */ + +static int __init gpio_vbus_probe(struct platform_device *pdev) +{ + struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; + struct gpio_vbus_data *gpio_vbus; + struct resource *res; + int err, gpio, irq; + + if (!pdata || !gpio_is_valid(pdata->gpio_vbus)) + return -EINVAL; + gpio = pdata->gpio_vbus; + + gpio_vbus = kzalloc(sizeof(struct gpio_vbus_data), GFP_KERNEL); + if (!gpio_vbus) + return -ENOMEM; + + platform_set_drvdata(pdev, gpio_vbus); + gpio_vbus->dev = &pdev->dev; + gpio_vbus->otg.label = "gpio-vbus"; + gpio_vbus->otg.state = OTG_STATE_UNDEFINED; + gpio_vbus->otg.set_peripheral = gpio_vbus_set_peripheral; + gpio_vbus->otg.set_power = gpio_vbus_set_power; + gpio_vbus->otg.set_suspend = gpio_vbus_set_suspend; + + err = gpio_request(gpio, "vbus_detect"); + if (err) { + dev_err(&pdev->dev, "can't request vbus gpio %d, err: %d\n", + gpio, err); + goto err_gpio; + } + gpio_direction_input(gpio); + + res = platform_get_resource(pdev, IORESOURCE_IRQ, 0); + if (res) { + irq = res->start; + res->flags &= IRQF_TRIGGER_MASK; + res->flags |= IRQF_SAMPLE_RANDOM | IRQF_SHARED; + } else + irq = gpio_to_irq(gpio); + + /* if data line pullup is in use, initialize it to "not pulling up" */ + gpio = pdata->gpio_pullup; + if (gpio_is_valid(gpio)) { + err = gpio_request(gpio, "udc_pullup"); + if (err) { + dev_err(&pdev->dev, + "can't request pullup gpio %d, err: %d\n", + gpio, err); + gpio_free(pdata->gpio_vbus); + goto err_gpio; + } + gpio_direction_output(gpio, pdata->gpio_pullup_inverted); + } + + err = request_irq(irq, gpio_vbus_irq, VBUS_IRQ_FLAGS, + "vbus_detect", pdev); + if (err) { + dev_err(&pdev->dev, "can't request irq %i, err: %d\n", + irq, err); + goto err_irq; + } + + /* only active when a gadget is registered */ + err = otg_set_transceiver(&gpio_vbus->otg); + if (err) { + dev_err(&pdev->dev, "can't register transceiver, err: %d\n", + err); + goto err_otg; + } + + gpio_vbus->vbus_draw = regulator_get(&pdev->dev, "vbus_draw"); + if (IS_ERR(gpio_vbus->vbus_draw)) { + dev_dbg(&pdev->dev, "can't get vbus_draw regulator, err: %ld\n", + PTR_ERR(gpio_vbus->vbus_draw)); + gpio_vbus->vbus_draw = NULL; + } + + return 0; +err_otg: + free_irq(irq, &pdev->dev); +err_irq: + if (gpio_is_valid(pdata->gpio_pullup)) + gpio_free(pdata->gpio_pullup); + gpio_free(pdata->gpio_vbus); +err_gpio: + platform_set_drvdata(pdev, NULL); + kfree(gpio_vbus); + return err; +} + +static int __exit gpio_vbus_remove(struct platform_device *pdev) +{ + struct gpio_vbus_data *gpio_vbus = platform_get_drvdata(pdev); + struct gpio_vbus_mach_info *pdata = pdev->dev.platform_data; + int gpio = pdata->gpio_vbus; + + regulator_put(gpio_vbus->vbus_draw); + + otg_set_transceiver(NULL); + + free_irq(gpio_to_irq(gpio), &pdev->dev); + if (gpio_is_valid(pdata->gpio_pullup)) + gpio_free(pdata->gpio_pullup); + gpio_free(gpio); + platform_set_drvdata(pdev, NULL); + kfree(gpio_vbus); + + return 0; +} + +/* NOTE: the gpio-vbus device may *NOT* be hotplugged */ + +MODULE_ALIAS("platform:gpio-vbus"); + +static struct platform_driver gpio_vbus_driver = { + .driver = { + .name = "gpio-vbus", + .owner = THIS_MODULE, + }, + .remove = __exit_p(gpio_vbus_remove), +}; + +static int __init gpio_vbus_init(void) +{ + return platform_driver_probe(&gpio_vbus_driver, gpio_vbus_probe); +} +module_init(gpio_vbus_init); + +static void __exit gpio_vbus_exit(void) +{ + platform_driver_unregister(&gpio_vbus_driver); +} +module_exit(gpio_vbus_exit); + +MODULE_DESCRIPTION("simple GPIO controlled OTG transceiver driver"); +MODULE_AUTHOR("Philipp Zabel"); +MODULE_LICENSE("GPL"); diff --git a/drivers/i2c/chips/isp1301_omap.c b/drivers/usb/otg/isp1301_omap.c index e0d56ef2bcb0..e0d56ef2bcb0 100644 --- a/drivers/i2c/chips/isp1301_omap.c +++ b/drivers/usb/otg/isp1301_omap.c diff --git a/drivers/usb/otg/otg.c b/drivers/usb/otg/otg.c new file mode 100644 index 000000000000..ff318fae7d4d --- /dev/null +++ b/drivers/usb/otg/otg.c @@ -0,0 +1,65 @@ +/* + * otg.c -- USB OTG utility code + * + * Copyright (C) 2004 Texas Instruments + * + * 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/kernel.h> +#include <linux/device.h> + +#include <linux/usb/otg.h> + +static struct otg_transceiver *xceiv; + +/** + * otg_get_transceiver - find the (single) OTG transceiver + * + * Returns the transceiver driver, after getting a refcount to it; or + * null if there is no such transceiver. The caller is responsible for + * calling otg_put_transceiver() to release that count. + * + * For use by USB host and peripheral drivers. + */ +struct otg_transceiver *otg_get_transceiver(void) +{ + if (xceiv) + get_device(xceiv->dev); + return xceiv; +} +EXPORT_SYMBOL(otg_get_transceiver); + +/** + * otg_put_transceiver - release the (single) OTG transceiver + * @x: the transceiver returned by otg_get_transceiver() + * + * Releases a refcount the caller received from otg_get_transceiver(). + * + * For use by USB host and peripheral drivers. + */ +void otg_put_transceiver(struct otg_transceiver *x) +{ + put_device(x->dev); +} +EXPORT_SYMBOL(otg_put_transceiver); + +/** + * otg_set_transceiver - declare the (single) OTG transceiver + * @x: the USB OTG transceiver to be used; or NULL + * + * This call is exclusively for use by transceiver drivers, which + * coordinate the activities of drivers for host and peripheral + * controllers, and in some cases for VBUS current regulation. + */ +int otg_set_transceiver(struct otg_transceiver *x) +{ + if (xceiv && x) + return -EBUSY; + xceiv = x; + return 0; +} +EXPORT_SYMBOL(otg_set_transceiver); diff --git a/drivers/usb/otg/twl4030-usb.c b/drivers/usb/otg/twl4030-usb.c new file mode 100644 index 000000000000..416e4410be02 --- /dev/null +++ b/drivers/usb/otg/twl4030-usb.c @@ -0,0 +1,721 @@ +/* + * twl4030_usb - TWL4030 USB transceiver, talking to OMAP OTG controller + * + * Copyright (C) 2004-2007 Texas Instruments + * Copyright (C) 2008 Nokia Corporation + * Contact: Felipe Balbi <felipe.balbi@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 + * 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. + * + * Current status: + * - HS USB ULPI mode works. + * - 3-pin mode support may be added in future. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/platform_device.h> +#include <linux/spinlock.h> +#include <linux/workqueue.h> +#include <linux/io.h> +#include <linux/delay.h> +#include <linux/usb/otg.h> +#include <linux/i2c/twl4030.h> + + +/* Register defines */ + +#define VENDOR_ID_LO 0x00 +#define VENDOR_ID_HI 0x01 +#define PRODUCT_ID_LO 0x02 +#define PRODUCT_ID_HI 0x03 + +#define FUNC_CTRL 0x04 +#define FUNC_CTRL_SET 0x05 +#define FUNC_CTRL_CLR 0x06 +#define FUNC_CTRL_SUSPENDM (1 << 6) +#define FUNC_CTRL_RESET (1 << 5) +#define FUNC_CTRL_OPMODE_MASK (3 << 3) /* bits 3 and 4 */ +#define FUNC_CTRL_OPMODE_NORMAL (0 << 3) +#define FUNC_CTRL_OPMODE_NONDRIVING (1 << 3) +#define FUNC_CTRL_OPMODE_DISABLE_BIT_NRZI (2 << 3) +#define FUNC_CTRL_TERMSELECT (1 << 2) +#define FUNC_CTRL_XCVRSELECT_MASK (3 << 0) /* bits 0 and 1 */ +#define FUNC_CTRL_XCVRSELECT_HS (0 << 0) +#define FUNC_CTRL_XCVRSELECT_FS (1 << 0) +#define FUNC_CTRL_XCVRSELECT_LS (2 << 0) +#define FUNC_CTRL_XCVRSELECT_FS4LS (3 << 0) + +#define IFC_CTRL 0x07 +#define IFC_CTRL_SET 0x08 +#define IFC_CTRL_CLR 0x09 +#define IFC_CTRL_INTERFACE_PROTECT_DISABLE (1 << 7) +#define IFC_CTRL_AUTORESUME (1 << 4) +#define IFC_CTRL_CLOCKSUSPENDM (1 << 3) +#define IFC_CTRL_CARKITMODE (1 << 2) +#define IFC_CTRL_FSLSSERIALMODE_3PIN (1 << 1) + +#define TWL4030_OTG_CTRL 0x0A +#define TWL4030_OTG_CTRL_SET 0x0B +#define TWL4030_OTG_CTRL_CLR 0x0C +#define TWL4030_OTG_CTRL_DRVVBUS (1 << 5) +#define TWL4030_OTG_CTRL_CHRGVBUS (1 << 4) +#define TWL4030_OTG_CTRL_DISCHRGVBUS (1 << 3) +#define TWL4030_OTG_CTRL_DMPULLDOWN (1 << 2) +#define TWL4030_OTG_CTRL_DPPULLDOWN (1 << 1) +#define TWL4030_OTG_CTRL_IDPULLUP (1 << 0) + +#define USB_INT_EN_RISE 0x0D +#define USB_INT_EN_RISE_SET 0x0E +#define USB_INT_EN_RISE_CLR 0x0F +#define USB_INT_EN_FALL 0x10 +#define USB_INT_EN_FALL_SET 0x11 +#define USB_INT_EN_FALL_CLR 0x12 +#define USB_INT_STS 0x13 +#define USB_INT_LATCH 0x14 +#define USB_INT_IDGND (1 << 4) +#define USB_INT_SESSEND (1 << 3) +#define USB_INT_SESSVALID (1 << 2) +#define USB_INT_VBUSVALID (1 << 1) +#define USB_INT_HOSTDISCONNECT (1 << 0) + +#define CARKIT_CTRL 0x19 +#define CARKIT_CTRL_SET 0x1A +#define CARKIT_CTRL_CLR 0x1B +#define CARKIT_CTRL_MICEN (1 << 6) +#define CARKIT_CTRL_SPKRIGHTEN (1 << 5) +#define CARKIT_CTRL_SPKLEFTEN (1 << 4) +#define CARKIT_CTRL_RXDEN (1 << 3) +#define CARKIT_CTRL_TXDEN (1 << 2) +#define CARKIT_CTRL_IDGNDDRV (1 << 1) +#define CARKIT_CTRL_CARKITPWR (1 << 0) +#define CARKIT_PLS_CTRL 0x22 +#define CARKIT_PLS_CTRL_SET 0x23 +#define CARKIT_PLS_CTRL_CLR 0x24 +#define CARKIT_PLS_CTRL_SPKRRIGHT_BIASEN (1 << 3) +#define CARKIT_PLS_CTRL_SPKRLEFT_BIASEN (1 << 2) +#define CARKIT_PLS_CTRL_RXPLSEN (1 << 1) +#define CARKIT_PLS_CTRL_TXPLSEN (1 << 0) + +#define MCPC_CTRL 0x30 +#define MCPC_CTRL_SET 0x31 +#define MCPC_CTRL_CLR 0x32 +#define MCPC_CTRL_RTSOL (1 << 7) +#define MCPC_CTRL_EXTSWR (1 << 6) +#define MCPC_CTRL_EXTSWC (1 << 5) +#define MCPC_CTRL_VOICESW (1 << 4) +#define MCPC_CTRL_OUT64K (1 << 3) +#define MCPC_CTRL_RTSCTSSW (1 << 2) +#define MCPC_CTRL_HS_UART (1 << 0) + +#define MCPC_IO_CTRL 0x33 +#define MCPC_IO_CTRL_SET 0x34 +#define MCPC_IO_CTRL_CLR 0x35 +#define MCPC_IO_CTRL_MICBIASEN (1 << 5) +#define MCPC_IO_CTRL_CTS_NPU (1 << 4) +#define MCPC_IO_CTRL_RXD_PU (1 << 3) +#define MCPC_IO_CTRL_TXDTYP (1 << 2) +#define MCPC_IO_CTRL_CTSTYP (1 << 1) +#define MCPC_IO_CTRL_RTSTYP (1 << 0) + +#define MCPC_CTRL2 0x36 +#define MCPC_CTRL2_SET 0x37 +#define MCPC_CTRL2_CLR 0x38 +#define MCPC_CTRL2_MCPC_CK_EN (1 << 0) + +#define OTHER_FUNC_CTRL 0x80 +#define OTHER_FUNC_CTRL_SET 0x81 +#define OTHER_FUNC_CTRL_CLR 0x82 +#define OTHER_FUNC_CTRL_BDIS_ACON_EN (1 << 4) +#define OTHER_FUNC_CTRL_FIVEWIRE_MODE (1 << 2) + +#define OTHER_IFC_CTRL 0x83 +#define OTHER_IFC_CTRL_SET 0x84 +#define OTHER_IFC_CTRL_CLR 0x85 +#define OTHER_IFC_CTRL_OE_INT_EN (1 << 6) +#define OTHER_IFC_CTRL_CEA2011_MODE (1 << 5) +#define OTHER_IFC_CTRL_FSLSSERIALMODE_4PIN (1 << 4) +#define OTHER_IFC_CTRL_HIZ_ULPI_60MHZ_OUT (1 << 3) +#define OTHER_IFC_CTRL_HIZ_ULPI (1 << 2) +#define OTHER_IFC_CTRL_ALT_INT_REROUTE (1 << 0) + +#define OTHER_INT_EN_RISE 0x86 +#define OTHER_INT_EN_RISE_SET 0x87 +#define OTHER_INT_EN_RISE_CLR 0x88 +#define OTHER_INT_EN_FALL 0x89 +#define OTHER_INT_EN_FALL_SET 0x8A +#define OTHER_INT_EN_FALL_CLR 0x8B +#define OTHER_INT_STS 0x8C +#define OTHER_INT_LATCH 0x8D +#define OTHER_INT_VB_SESS_VLD (1 << 7) +#define OTHER_INT_DM_HI (1 << 6) /* not valid for "latch" reg */ +#define OTHER_INT_DP_HI (1 << 5) /* not valid for "latch" reg */ +#define OTHER_INT_BDIS_ACON (1 << 3) /* not valid for "fall" regs */ +#define OTHER_INT_MANU (1 << 1) +#define OTHER_INT_ABNORMAL_STRESS (1 << 0) + +#define ID_STATUS 0x96 +#define ID_RES_FLOAT (1 << 4) +#define ID_RES_440K (1 << 3) +#define ID_RES_200K (1 << 2) +#define ID_RES_102K (1 << 1) +#define ID_RES_GND (1 << 0) + +#define POWER_CTRL 0xAC +#define POWER_CTRL_SET 0xAD +#define POWER_CTRL_CLR 0xAE +#define POWER_CTRL_OTG_ENAB (1 << 5) + +#define OTHER_IFC_CTRL2 0xAF +#define OTHER_IFC_CTRL2_SET 0xB0 +#define OTHER_IFC_CTRL2_CLR 0xB1 +#define OTHER_IFC_CTRL2_ULPI_STP_LOW (1 << 4) +#define OTHER_IFC_CTRL2_ULPI_TXEN_POL (1 << 3) +#define OTHER_IFC_CTRL2_ULPI_4PIN_2430 (1 << 2) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_MASK (3 << 0) /* bits 0 and 1 */ +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT1N (0 << 0) +#define OTHER_IFC_CTRL2_USB_INT_OUTSEL_INT2N (1 << 0) + +#define REG_CTRL_EN 0xB2 +#define REG_CTRL_EN_SET 0xB3 +#define REG_CTRL_EN_CLR 0xB4 +#define REG_CTRL_ERROR 0xB5 +#define ULPI_I2C_CONFLICT_INTEN (1 << 0) + +#define OTHER_FUNC_CTRL2 0xB8 +#define OTHER_FUNC_CTRL2_SET 0xB9 +#define OTHER_FUNC_CTRL2_CLR 0xBA +#define OTHER_FUNC_CTRL2_VBAT_TIMER_EN (1 << 0) + +/* following registers do not have separate _clr and _set registers */ +#define VBUS_DEBOUNCE 0xC0 +#define ID_DEBOUNCE 0xC1 +#define VBAT_TIMER 0xD3 +#define PHY_PWR_CTRL 0xFD +#define PHY_PWR_PHYPWD (1 << 0) +#define PHY_CLK_CTRL 0xFE +#define PHY_CLK_CTRL_CLOCKGATING_EN (1 << 2) +#define PHY_CLK_CTRL_CLK32K_EN (1 << 1) +#define REQ_PHY_DPLL_CLK (1 << 0) +#define PHY_CLK_CTRL_STS 0xFF +#define PHY_DPLL_CLK (1 << 0) + +/* In module TWL4030_MODULE_PM_MASTER */ +#define PROTECT_KEY 0x0E + +/* In module TWL4030_MODULE_PM_RECEIVER */ +#define VUSB_DEDICATED1 0x7D +#define VUSB_DEDICATED2 0x7E +#define VUSB1V5_DEV_GRP 0x71 +#define VUSB1V5_TYPE 0x72 +#define VUSB1V5_REMAP 0x73 +#define VUSB1V8_DEV_GRP 0x74 +#define VUSB1V8_TYPE 0x75 +#define VUSB1V8_REMAP 0x76 +#define VUSB3V1_DEV_GRP 0x77 +#define VUSB3V1_TYPE 0x78 +#define VUSB3V1_REMAP 0x79 + +/* In module TWL4030_MODULE_INTBR */ +#define PMBR1 0x0D +#define GPIO_USB_4PIN_ULPI_2430C (3 << 0) + + + +enum linkstat { + USB_LINK_UNKNOWN = 0, + USB_LINK_NONE, + USB_LINK_VBUS, + USB_LINK_ID, +}; + +struct twl4030_usb { + struct otg_transceiver otg; + struct device *dev; + + /* for vbus reporting with irqs disabled */ + spinlock_t lock; + + /* pin configuration */ + enum twl4030_usb_mode usb_mode; + + int irq; + u8 linkstat; + u8 asleep; + bool irq_enabled; +}; + +/* internal define on top of container_of */ +#define xceiv_to_twl(x) container_of((x), struct twl4030_usb, otg); + +/*-------------------------------------------------------------------------*/ + +static int twl4030_i2c_write_u8_verify(struct twl4030_usb *twl, + u8 module, u8 data, u8 address) +{ + u8 check; + + if ((twl4030_i2c_write_u8(module, data, address) >= 0) && + (twl4030_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 1, module, address, check, data); + + /* Failed once: Try again */ + if ((twl4030_i2c_write_u8(module, data, address) >= 0) && + (twl4030_i2c_read_u8(module, &check, address) >= 0) && + (check == data)) + return 0; + dev_dbg(twl->dev, "Write%d[%d,0x%x] wrote %02x but read %02x\n", + 2, module, address, check, data); + + /* Failed again: Return error */ + return -EBUSY; +} + +#define twl4030_usb_write_verify(twl, address, data) \ + twl4030_i2c_write_u8_verify(twl, TWL4030_MODULE_USB, (data), (address)) + +static inline int twl4030_usb_write(struct twl4030_usb *twl, + u8 address, u8 data) +{ + int ret = 0; + + ret = twl4030_i2c_write_u8(TWL4030_MODULE_USB, data, address); + if (ret < 0) + dev_dbg(twl->dev, + "TWL4030:USB:Write[0x%x] Error %d\n", address, ret); + return ret; +} + +static inline int twl4030_readb(struct twl4030_usb *twl, u8 module, u8 address) +{ + u8 data; + int ret = 0; + + ret = twl4030_i2c_read_u8(module, &data, address); + if (ret >= 0) + ret = data; + else + dev_dbg(twl->dev, + "TWL4030:readb[0x%x,0x%x] Error %d\n", + module, address, ret); + + return ret; +} + +static inline int twl4030_usb_read(struct twl4030_usb *twl, u8 address) +{ + return twl4030_readb(twl, TWL4030_MODULE_USB, address); +} + +/*-------------------------------------------------------------------------*/ + +static inline int +twl4030_usb_set_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, reg + 1, bits); +} + +static inline int +twl4030_usb_clear_bits(struct twl4030_usb *twl, u8 reg, u8 bits) +{ + return twl4030_usb_write(twl, reg + 2, bits); +} + +/*-------------------------------------------------------------------------*/ + +static enum linkstat twl4030_usb_linkstat(struct twl4030_usb *twl) +{ + int status; + int linkstat = USB_LINK_UNKNOWN; + + /* STS_HW_CONDITIONS */ + status = twl4030_readb(twl, TWL4030_MODULE_PM_MASTER, 0x0f); + if (status < 0) + dev_err(twl->dev, "USB link status err %d\n", status); + else if (status & BIT(7)) + linkstat = USB_LINK_VBUS; + else if (status & BIT(2)) + linkstat = USB_LINK_ID; + else + linkstat = USB_LINK_NONE; + + dev_dbg(twl->dev, "HW_CONDITIONS 0x%02x/%d; link %d\n", + status, status, linkstat); + + /* REVISIT this assumes host and peripheral controllers + * are registered, and that both are active... + */ + + spin_lock_irq(&twl->lock); + twl->linkstat = linkstat; + if (linkstat == USB_LINK_ID) { + twl->otg.default_a = true; + twl->otg.state = OTG_STATE_A_IDLE; + } else { + twl->otg.default_a = false; + twl->otg.state = OTG_STATE_B_IDLE; + } + spin_unlock_irq(&twl->lock); + + return linkstat; +} + +static void twl4030_usb_set_mode(struct twl4030_usb *twl, int mode) +{ + twl->usb_mode = mode; + + switch (mode) { + case T2_USB_MODE_ULPI: + twl4030_usb_clear_bits(twl, IFC_CTRL, IFC_CTRL_CARKITMODE); + twl4030_usb_set_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + twl4030_usb_clear_bits(twl, FUNC_CTRL, + FUNC_CTRL_XCVRSELECT_MASK | + FUNC_CTRL_OPMODE_MASK); + break; + case -1: + /* FIXME: power on defaults */ + break; + default: + dev_err(twl->dev, "unsupported T2 transceiver mode %d\n", + mode); + break; + }; +} + +static void twl4030_i2c_access(struct twl4030_usb *twl, int on) +{ + unsigned long timeout; + int val = twl4030_usb_read(twl, PHY_CLK_CTRL); + + if (val >= 0) { + if (on) { + /* enable DPLL to access PHY registers over I2C */ + val |= REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + + timeout = jiffies + HZ; + while (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK) + && time_before(jiffies, timeout)) + udelay(10); + if (!(twl4030_usb_read(twl, PHY_CLK_CTRL_STS) & + PHY_DPLL_CLK)) + dev_err(twl->dev, "Timeout setting T2 HSUSB " + "PHY DPLL clock\n"); + } else { + /* let ULPI control the DPLL clock */ + val &= ~REQ_PHY_DPLL_CLK; + WARN_ON(twl4030_usb_write_verify(twl, PHY_CLK_CTRL, + (u8)val) < 0); + } + } +} + +static void twl4030_phy_power(struct twl4030_usb *twl, int on) +{ + u8 pwr; + + pwr = twl4030_usb_read(twl, PHY_PWR_CTRL); + if (on) { + pwr &= ~PHY_PWR_PHYPWD; + WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); + twl4030_usb_write(twl, PHY_CLK_CTRL, + twl4030_usb_read(twl, PHY_CLK_CTRL) | + (PHY_CLK_CTRL_CLOCKGATING_EN | + PHY_CLK_CTRL_CLK32K_EN)); + } else { + pwr |= PHY_PWR_PHYPWD; + WARN_ON(twl4030_usb_write_verify(twl, PHY_PWR_CTRL, pwr) < 0); + } +} + +static void twl4030_phy_suspend(struct twl4030_usb *twl, int controller_off) +{ + if (twl->asleep) + return; + + twl4030_phy_power(twl, 0); + twl->asleep = 1; +} + +static void twl4030_phy_resume(struct twl4030_usb *twl) +{ + if (!twl->asleep) + return; + + twl4030_phy_power(twl, 1); + twl4030_i2c_access(twl, 1); + twl4030_usb_set_mode(twl, twl->usb_mode); + if (twl->usb_mode == T2_USB_MODE_ULPI) + twl4030_i2c_access(twl, 0); + twl->asleep = 0; +} + +static void twl4030_usb_ldo_init(struct twl4030_usb *twl) +{ + /* Enable writing to power configuration registers */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0xC0, PROTECT_KEY); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0x0C, PROTECT_KEY); + + /* put VUSB3V1 LDO in active state */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB_DEDICATED2); + + /* input to VUSB3V1 LDO is from VBAT, not VBUS */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x14, VUSB_DEDICATED1); + + /* turn on 3.1V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB3V1_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB3V1_TYPE); + + /* turn on 1.5V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V5_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V5_TYPE); + + /* turn on 1.8V regulator */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0x20, VUSB1V8_DEV_GRP); + twl4030_i2c_write_u8(TWL4030_MODULE_PM_RECEIVER, 0, VUSB1V8_TYPE); + + /* disable access to power configuration registers */ + twl4030_i2c_write_u8(TWL4030_MODULE_PM_MASTER, 0, PROTECT_KEY); +} + +static ssize_t twl4030_usb_vbus_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct twl4030_usb *twl = dev_get_drvdata(dev); + unsigned long flags; + int ret = -EINVAL; + + spin_lock_irqsave(&twl->lock, flags); + ret = sprintf(buf, "%s\n", + (twl->linkstat == USB_LINK_VBUS) ? "on" : "off"); + spin_unlock_irqrestore(&twl->lock, flags); + + return ret; +} +static DEVICE_ATTR(vbus, 0444, twl4030_usb_vbus_show, NULL); + +static irqreturn_t twl4030_usb_irq(int irq, void *_twl) +{ + struct twl4030_usb *twl = _twl; + int status; + +#ifdef CONFIG_LOCKDEP + /* WORKAROUND for lockdep forcing IRQF_DISABLED on us, which + * we don't want and can't tolerate. Although it might be + * friendlier not to borrow this thread context... + */ + local_irq_enable(); +#endif + + status = twl4030_usb_linkstat(twl); + if (status != USB_LINK_UNKNOWN) { + + /* FIXME add a set_power() method so that B-devices can + * configure the charger appropriately. It's not always + * correct to consume VBUS power, and how much current to + * consume is a function of the USB configuration chosen + * by the host. + * + * REVISIT usb_gadget_vbus_connect(...) as needed, ditto + * its disconnect() sibling, when changing to/from the + * USB_LINK_VBUS state. musb_hdrc won't care until it + * starts to handle softconnect right. + */ + twl4030charger_usb_en(status == USB_LINK_VBUS); + + if (status == USB_LINK_NONE) + twl4030_phy_suspend(twl, 0); + else + twl4030_phy_resume(twl); + } + sysfs_notify(&twl->dev->kobj, NULL, "vbus"); + + return IRQ_HANDLED; +} + +static int twl4030_set_suspend(struct otg_transceiver *x, int suspend) +{ + struct twl4030_usb *twl = xceiv_to_twl(x); + + if (suspend) + twl4030_phy_suspend(twl, 1); + else + twl4030_phy_resume(twl); + + return 0; +} + +static int twl4030_set_peripheral(struct otg_transceiver *x, + struct usb_gadget *gadget) +{ + struct twl4030_usb *twl; + + if (!x) + return -ENODEV; + + twl = xceiv_to_twl(x); + twl->otg.gadget = gadget; + if (!gadget) + twl->otg.state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int twl4030_set_host(struct otg_transceiver *x, struct usb_bus *host) +{ + struct twl4030_usb *twl; + + if (!x) + return -ENODEV; + + twl = xceiv_to_twl(x); + twl->otg.host = host; + if (!host) + twl->otg.state = OTG_STATE_UNDEFINED; + + return 0; +} + +static int __init twl4030_usb_probe(struct platform_device *pdev) +{ + struct twl4030_usb_data *pdata = pdev->dev.platform_data; + struct twl4030_usb *twl; + int status; + + if (!pdata) { + dev_dbg(&pdev->dev, "platform_data not available\n"); + return -EINVAL; + } + + twl = kzalloc(sizeof *twl, GFP_KERNEL); + if (!twl) + return -ENOMEM; + + twl->dev = &pdev->dev; + twl->irq = platform_get_irq(pdev, 0); + twl->otg.dev = twl->dev; + twl->otg.label = "twl4030"; + twl->otg.set_host = twl4030_set_host; + twl->otg.set_peripheral = twl4030_set_peripheral; + twl->otg.set_suspend = twl4030_set_suspend; + twl->usb_mode = pdata->usb_mode; + twl->asleep = 1; + + /* init spinlock for workqueue */ + spin_lock_init(&twl->lock); + + twl4030_usb_ldo_init(twl); + otg_set_transceiver(&twl->otg); + + platform_set_drvdata(pdev, twl); + if (device_create_file(&pdev->dev, &dev_attr_vbus)) + dev_warn(&pdev->dev, "could not create sysfs file\n"); + + /* Our job is to use irqs and status from the power module + * to keep the transceiver disabled when nothing's connected. + * + * FIXME we actually shouldn't start enabling it until the + * USB controller drivers have said they're ready, by calling + * set_host() and/or set_peripheral() ... OTG_capable boards + * need both handles, otherwise just one suffices. + */ + twl->irq_enabled = true; + status = request_irq(twl->irq, twl4030_usb_irq, + IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING, + "twl4030_usb", twl); + if (status < 0) { + dev_dbg(&pdev->dev, "can't get IRQ %d, err %d\n", + twl->irq, status); + kfree(twl); + return status; + } + + /* The IRQ handler just handles changes from the previous states + * of the ID and VBUS pins ... in probe() we must initialize that + * previous state. The easy way: fake an IRQ. + * + * REVISIT: a real IRQ might have happened already, if PREEMPT is + * enabled. Else the IRQ may not yet be configured or enabled, + * because of scheduling delays. + */ + twl4030_usb_irq(twl->irq, twl); + + dev_info(&pdev->dev, "Initialized TWL4030 USB module\n"); + return 0; +} + +static int __exit twl4030_usb_remove(struct platform_device *pdev) +{ + struct twl4030_usb *twl = platform_get_drvdata(pdev); + int val; + + free_irq(twl->irq, twl); + device_remove_file(twl->dev, &dev_attr_vbus); + + /* set transceiver mode to power on defaults */ + twl4030_usb_set_mode(twl, -1); + + /* autogate 60MHz ULPI clock, + * clear dpll clock request for i2c access, + * disable 32KHz + */ + val = twl4030_usb_read(twl, PHY_CLK_CTRL); + if (val >= 0) { + val |= PHY_CLK_CTRL_CLOCKGATING_EN; + val &= ~(PHY_CLK_CTRL_CLK32K_EN | REQ_PHY_DPLL_CLK); + twl4030_usb_write(twl, PHY_CLK_CTRL, (u8)val); + } + + /* disable complete OTG block */ + twl4030_usb_clear_bits(twl, POWER_CTRL, POWER_CTRL_OTG_ENAB); + + twl4030_phy_power(twl, 0); + + kfree(twl); + + return 0; +} + +static struct platform_driver twl4030_usb_driver = { + .probe = twl4030_usb_probe, + .remove = __exit_p(twl4030_usb_remove), + .driver = { + .name = "twl4030_usb", + .owner = THIS_MODULE, + }, +}; + +static int __init twl4030_usb_init(void) +{ + return platform_driver_register(&twl4030_usb_driver); +} +subsys_initcall(twl4030_usb_init); + +static void __exit twl4030_usb_exit(void) +{ + platform_driver_unregister(&twl4030_usb_driver); +} +module_exit(twl4030_usb_exit); + +MODULE_ALIAS("platform:twl4030_usb"); +MODULE_AUTHOR("Texas Instruments, Inc, Nokia Corporation"); +MODULE_DESCRIPTION("TWL4030 USB transceiver driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/Kconfig b/drivers/usb/serial/Kconfig index 70338f4ec918..b361f05cafac 100644 --- a/drivers/usb/serial/Kconfig +++ b/drivers/usb/serial/Kconfig @@ -496,6 +496,14 @@ config USB_SERIAL_SAFE_PADDED bool "USB Secure Encapsulated Driver - Padded" depends on USB_SERIAL_SAFE +config USB_SERIAL_SIEMENS_MPI + tristate "USB Siemens MPI driver" + help + Say M here if you want to use a Siemens USB/MPI adapter. + + To compile this driver as a module, choose M here: the + module will be called siemens_mpi. + config USB_SERIAL_SIERRAWIRELESS tristate "USB Sierra Wireless Driver" help @@ -565,6 +573,15 @@ config USB_SERIAL_OMNINET To compile this driver as a module, choose M here: the module will be called omninet. +config USB_SERIAL_OPTICON + tristate "USB Opticon Barcode driver (serial mode)" + help + Say Y here if you want to use a Opticon USB Barcode device + in serial emulation mode. + + To compile this driver as a module, choose M here: the + module will be called opticon. + config USB_SERIAL_DEBUG tristate "USB Debugging Device" help diff --git a/drivers/usb/serial/Makefile b/drivers/usb/serial/Makefile index 6047f818adfe..b75be91eb8f1 100644 --- a/drivers/usb/serial/Makefile +++ b/drivers/usb/serial/Makefile @@ -41,10 +41,12 @@ obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o obj-$(CONFIG_USB_SERIAL_MOTOROLA) += moto_modem.o obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o +obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o obj-$(CONFIG_USB_SERIAL_OPTION) += option.o obj-$(CONFIG_USB_SERIAL_OTI6858) += oti6858.o obj-$(CONFIG_USB_SERIAL_PL2303) += pl2303.o obj-$(CONFIG_USB_SERIAL_SAFE) += safe_serial.o +obj-$(CONFIG_USB_SERIAL_SIEMENS_MPI) += siemens_mpi.o obj-$(CONFIG_USB_SERIAL_SIERRAWIRELESS) += sierra.o obj-$(CONFIG_USB_SERIAL_SPCP8X5) += spcp8x5.o obj-$(CONFIG_USB_SERIAL_TI) += ti_usb_3410_5052.o diff --git a/drivers/usb/serial/digi_acceleport.c b/drivers/usb/serial/digi_acceleport.c index 69f84f0ea6fe..38ba4ea8b6bf 100644 --- a/drivers/usb/serial/digi_acceleport.c +++ b/drivers/usb/serial/digi_acceleport.c @@ -635,8 +635,7 @@ static int digi_write_oob_command(struct usb_serial_port *port, spin_lock_irqsave(&oob_priv->dp_port_lock, flags); while (count > 0) { - while (oob_port->write_urb->status == -EINPROGRESS - || oob_priv->dp_write_urb_in_use) { + while (oob_priv->dp_write_urb_in_use) { cond_wait_interruptible_timeout_irqrestore( &oob_port->write_wait, DIGI_RETRY_TIMEOUT, &oob_priv->dp_port_lock, flags); @@ -699,9 +698,8 @@ static int digi_write_inb_command(struct usb_serial_port *port, spin_lock_irqsave(&priv->dp_port_lock, flags); while (count > 0 && ret == 0) { - while ((port->write_urb->status == -EINPROGRESS - || priv->dp_write_urb_in_use) - && time_before(jiffies, timeout)) { + while (priv->dp_write_urb_in_use && + time_before(jiffies, timeout)) { cond_wait_interruptible_timeout_irqrestore( &port->write_wait, DIGI_RETRY_TIMEOUT, &priv->dp_port_lock, flags); @@ -779,8 +777,7 @@ static int digi_set_modem_signals(struct usb_serial_port *port, spin_lock_irqsave(&oob_priv->dp_port_lock, flags); spin_lock(&port_priv->dp_port_lock); - while (oob_port->write_urb->status == -EINPROGRESS || - oob_priv->dp_write_urb_in_use) { + while (oob_priv->dp_write_urb_in_use) { spin_unlock(&port_priv->dp_port_lock); cond_wait_interruptible_timeout_irqrestore( &oob_port->write_wait, DIGI_RETRY_TIMEOUT, @@ -1168,12 +1165,10 @@ static int digi_write(struct tty_struct *tty, struct usb_serial_port *port, /* be sure only one write proceeds at a time */ /* there are races on the port private buffer */ - /* and races to check write_urb->status */ spin_lock_irqsave(&priv->dp_port_lock, flags); /* wait for urb status clear to submit another urb */ - if (port->write_urb->status == -EINPROGRESS || - priv->dp_write_urb_in_use) { + if (priv->dp_write_urb_in_use) { /* buffer data if count is 1 (probably put_char) if possible */ if (count == 1 && priv->dp_out_buf_len < DIGI_OUT_BUF_SIZE) { priv->dp_out_buf[priv->dp_out_buf_len++] = *buf; @@ -1236,7 +1231,7 @@ static void digi_write_bulk_callback(struct urb *urb) int ret = 0; int status = urb->status; - dbg("digi_write_bulk_callback: TOP, urb->status=%d", status); + dbg("digi_write_bulk_callback: TOP, status=%d", status); /* port and serial sanity check */ if (port == NULL || (priv = usb_get_serial_port_data(port)) == NULL) { @@ -1266,8 +1261,7 @@ static void digi_write_bulk_callback(struct urb *urb) /* try to send any buffered data on this port, if it is open */ spin_lock(&priv->dp_port_lock); priv->dp_write_urb_in_use = 0; - if (port->port.count && port->write_urb->status != -EINPROGRESS - && priv->dp_out_buf_len > 0) { + if (port->port.count && priv->dp_out_buf_len > 0) { *((unsigned char *)(port->write_urb->transfer_buffer)) = (unsigned char)DIGI_CMD_SEND_DATA; *((unsigned char *)(port->write_urb->transfer_buffer) + 1) @@ -1305,8 +1299,7 @@ static int digi_write_room(struct tty_struct *tty) spin_lock_irqsave(&priv->dp_port_lock, flags); - if (port->write_urb->status == -EINPROGRESS || - priv->dp_write_urb_in_use) + if (priv->dp_write_urb_in_use) room = 0; else room = port->bulk_out_size - 2 - priv->dp_out_buf_len; @@ -1322,8 +1315,7 @@ static int digi_chars_in_buffer(struct tty_struct *tty) struct usb_serial_port *port = tty->driver_data; struct digi_port *priv = usb_get_serial_port_data(port); - if (port->write_urb->status == -EINPROGRESS - || priv->dp_write_urb_in_use) { + if (priv->dp_write_urb_in_use) { dbg("digi_chars_in_buffer: port=%d, chars=%d", priv->dp_port_num, port->bulk_out_size - 2); /* return(port->bulk_out_size - 2); */ @@ -1702,7 +1694,7 @@ static int digi_read_inb_callback(struct urb *urb) /* short/multiple packet check */ if (urb->actual_length != len + 2) { dev_err(&port->dev, "%s: INCOMPLETE OR MULTIPLE PACKET, " - "urb->status=%d, port=%d, opcode=%d, len=%d, " + "status=%d, port=%d, opcode=%d, len=%d, " "actual_length=%d, status=%d\n", __func__, status, priv->dp_port_num, opcode, len, urb->actual_length, port_status); diff --git a/drivers/usb/serial/garmin_gps.c b/drivers/usb/serial/garmin_gps.c index 8e6a66e38db2..a26a0e2cdb4a 100644 --- a/drivers/usb/serial/garmin_gps.c +++ b/drivers/usb/serial/garmin_gps.c @@ -1056,7 +1056,7 @@ static void garmin_write_bulk_callback(struct urb *urb) if (status) { dbg("%s - nonzero write bulk status received: %d", - __func__, urb->status); + __func__, status); spin_lock_irqsave(&garmin_data_p->lock, flags); garmin_data_p->flags |= CLEAR_HALT_REQUIRED; spin_unlock_irqrestore(&garmin_data_p->lock, flags); diff --git a/drivers/usb/serial/ipw.c b/drivers/usb/serial/ipw.c index 3ac59a8a980f..f530032ed93d 100644 --- a/drivers/usb/serial/ipw.c +++ b/drivers/usb/serial/ipw.c @@ -473,7 +473,7 @@ static struct usb_serial_driver ipw_device = { -static int usb_ipw_init(void) +static int __init usb_ipw_init(void) { int retval; @@ -490,7 +490,7 @@ static int usb_ipw_init(void) return 0; } -static void usb_ipw_exit(void) +static void __exit usb_ipw_exit(void) { usb_deregister(&usb_ipw_driver); usb_serial_deregister(&ipw_device); diff --git a/drivers/usb/serial/iuu_phoenix.c b/drivers/usb/serial/iuu_phoenix.c index e320972cb227..2314c6ae4fc2 100644 --- a/drivers/usb/serial/iuu_phoenix.c +++ b/drivers/usb/serial/iuu_phoenix.c @@ -190,10 +190,12 @@ static void iuu_rxcmd(struct urb *urb) { struct usb_serial_port *port = urb->context; int result; + int status = urb->status; + dbg("%s - enter", __func__); - if (urb->status) { - dbg("%s - urb->status = %d", __func__, urb->status); + if (status) { + dbg("%s - status = %d", __func__, status); /* error stop all */ return; } @@ -245,10 +247,12 @@ static void iuu_update_status_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct iuu_private *priv = usb_get_serial_port_data(port); u8 *st; + int status = urb->status; + dbg("%s - enter", __func__); - if (urb->status) { - dbg("%s - urb->status = %d", __func__, urb->status); + if (status) { + dbg("%s - status = %d", __func__, status); /* error stop all */ return; } @@ -274,9 +278,9 @@ static void iuu_status_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; int result; - dbg("%s - enter", __func__); + int status = urb->status; - dbg("%s - urb->status = %d", __func__, urb->status); + dbg("%s - status = %d", __func__, status); usb_fill_bulk_urb(port->read_urb, port->serial->dev, usb_rcvbulkpipe(port->serial->dev, port->bulk_in_endpointAddress), @@ -618,11 +622,12 @@ static void read_buf_callback(struct urb *urb) struct usb_serial_port *port = urb->context; unsigned char *data = urb->transfer_buffer; struct tty_struct *tty; - dbg("%s - urb->status = %d", __func__, urb->status); + int status = urb->status; - if (urb->status) { - dbg("%s - urb->status = %d", __func__, urb->status); - if (urb->status == -EPROTO) { + dbg("%s - status = %d", __func__, status); + + if (status) { + if (status == -EPROTO) { /* reschedule needed */ } return; @@ -695,7 +700,7 @@ static void iuu_uart_read_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct iuu_private *priv = usb_get_serial_port_data(port); unsigned long flags; - int status; + int status = urb->status; int error = 0; int len = 0; unsigned char *data = urb->transfer_buffer; @@ -703,8 +708,8 @@ static void iuu_uart_read_callback(struct urb *urb) dbg("%s - enter", __func__); - if (urb->status) { - dbg("%s - urb->status = %d", __func__, urb->status); + if (status) { + dbg("%s - status = %d", __func__, status); /* error stop all */ return; } @@ -782,12 +787,11 @@ static void read_rxcmd_callback(struct urb *urb) { struct usb_serial_port *port = urb->context; int result; - dbg("%s - enter", __func__); + int status = urb->status; - dbg("%s - urb->status = %d", __func__, urb->status); + dbg("%s - status = %d", __func__, status); - if (urb->status) { - dbg("%s - urb->status = %d", __func__, urb->status); + if (status) { /* error stop all */ return; } diff --git a/drivers/usb/serial/mos7840.c b/drivers/usb/serial/mos7840.c index 96a8c7713212..2c20e88a91b3 100644 --- a/drivers/usb/serial/mos7840.c +++ b/drivers/usb/serial/mos7840.c @@ -214,6 +214,7 @@ struct moschip_port { spinlock_t pool_lock; struct urb *write_urb_pool[NUM_URBS]; char busy[NUM_URBS]; + bool read_urb_busy; }; @@ -679,26 +680,30 @@ static void mos7840_bulk_in_callback(struct urb *urb) struct tty_struct *tty; int status = urb->status; - if (status) { - dbg("nonzero read bulk status received: %d", status); - return; - } - mos7840_port = urb->context; if (!mos7840_port) { dbg("%s", "NULL mos7840_port pointer \n"); + mos7840_port->read_urb_busy = false; + return; + } + + if (status) { + dbg("nonzero read bulk status received: %d", status); + mos7840_port->read_urb_busy = false; return; } port = (struct usb_serial_port *)mos7840_port->port; if (mos7840_port_paranoia_check(port, __func__)) { dbg("%s", "Port Paranoia failed \n"); + mos7840_port->read_urb_busy = false; return; } serial = mos7840_get_usb_serial(port, __func__); if (!serial) { dbg("%s\n", "Bad serial pointer "); + mos7840_port->read_urb_busy = false; return; } @@ -725,17 +730,19 @@ static void mos7840_bulk_in_callback(struct urb *urb) if (!mos7840_port->read_urb) { dbg("%s", "URB KILLED !!!\n"); + mos7840_port->read_urb_busy = false; return; } mos7840_port->read_urb->dev = serial->dev; + mos7840_port->read_urb_busy = true; retval = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC); if (retval) { - dbg(" usb_submit_urb(read bulk) failed, retval = %d", - retval); + dbg("usb_submit_urb(read bulk) failed, retval = %d", retval); + mos7840_port->read_urb_busy = false; } } @@ -1055,10 +1062,12 @@ static int mos7840_open(struct tty_struct *tty, dbg("mos7840_open: bulkin endpoint is %d\n", port->bulk_in_endpointAddress); + mos7840_port->read_urb_busy = true; response = usb_submit_urb(mos7840_port->read_urb, GFP_KERNEL); if (response) { dev_err(&port->dev, "%s - Error %d submitting control urb\n", __func__, response); + mos7840_port->read_urb_busy = false; } /* initialize our wait queues */ @@ -1227,6 +1236,7 @@ static void mos7840_close(struct tty_struct *tty, if (mos7840_port->read_urb) { dbg("%s", "Shutdown bulk read\n"); usb_kill_urb(mos7840_port->read_urb); + mos7840_port->read_urb_busy = false; } if ((&mos7840_port->control_urb)) { dbg("%s", "Shutdown control read\n"); @@ -2043,14 +2053,14 @@ static void mos7840_change_port_settings(struct tty_struct *tty, Data = 0x0c; mos7840_set_uart_reg(port, INTERRUPT_ENABLE_REGISTER, Data); - if (mos7840_port->read_urb->status != -EINPROGRESS) { + if (mos7840_port->read_urb_busy == false) { mos7840_port->read_urb->dev = serial->dev; - + mos7840_port->read_urb_busy = true; status = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC); - if (status) { - dbg(" usb_submit_urb(read bulk) failed, status = %d", + dbg("usb_submit_urb(read bulk) failed, status = %d", status); + mos7840_port->read_urb_busy = false; } } wake_up(&mos7840_port->delta_msr_wait); @@ -2117,12 +2127,14 @@ static void mos7840_set_termios(struct tty_struct *tty, return; } - if (mos7840_port->read_urb->status != -EINPROGRESS) { + if (mos7840_port->read_urb_busy == false) { mos7840_port->read_urb->dev = serial->dev; + mos7840_port->read_urb_busy = true; status = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC); if (status) { - dbg(" usb_submit_urb(read bulk) failed, status = %d", + dbg("usb_submit_urb(read bulk) failed, status = %d", status); + mos7840_port->read_urb_busy = false; } } return; diff --git a/drivers/usb/serial/opticon.c b/drivers/usb/serial/opticon.c new file mode 100644 index 000000000000..cea326f1f105 --- /dev/null +++ b/drivers/usb/serial/opticon.c @@ -0,0 +1,358 @@ +/* + * Opticon USB barcode to serial driver + * + * Copyright (C) 2008 Greg Kroah-Hartman <gregkh@suse.de> + * Copyright (C) 2008 Novell Inc. + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/tty.h> +#include <linux/tty_driver.h> +#include <linux/tty_flip.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> +#include <linux/uaccess.h> + +static int debug; + +static struct usb_device_id id_table[] = { + { USB_DEVICE(0x065a, 0x0009) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +/* This structure holds all of the individual device information */ +struct opticon_private { + struct usb_device *udev; + struct usb_serial *serial; + struct usb_serial_port *port; + unsigned char *bulk_in_buffer; + struct urb *bulk_read_urb; + int buffer_size; + u8 bulk_address; + spinlock_t lock; /* protects the following flags */ + bool throttled; + bool actually_throttled; + bool rts; +}; + +static void opticon_bulk_callback(struct urb *urb) +{ + struct opticon_private *priv = urb->context; + unsigned char *data = urb->transfer_buffer; + struct usb_serial_port *port = priv->port; + int status = urb->status; + struct tty_struct *tty; + int result; + int available_room = 0; + int data_length; + + dbg("%s - port %d", __func__, port->number); + + switch (status) { + case 0: + /* success */ + break; + case -ECONNRESET: + case -ENOENT: + case -ESHUTDOWN: + /* this urb is terminated, clean up */ + dbg("%s - urb shutting down with status: %d", + __func__, status); + return; + default: + dbg("%s - nonzero urb status received: %d", + __func__, status); + goto exit; + } + + usb_serial_debug_data(debug, &port->dev, __func__, urb->actual_length, + data); + + if (urb->actual_length > 2) { + data_length = urb->actual_length - 2; + + /* + * Data from the device comes with a 2 byte header: + * + * <0x00><0x00>data... + * This is real data to be sent to the tty layer + * <0x00><0x01)level + * This is a RTS level change, the third byte is the RTS + * value (0 for low, 1 for high). + */ + if ((data[0] == 0x00) && (data[1] == 0x00)) { + /* real data, send it to the tty layer */ + tty = tty_port_tty_get(&port->port); + if (tty) { + available_room = tty_buffer_request_room(tty, + data_length); + if (available_room) { + tty_insert_flip_string(tty, data, + available_room); + tty_flip_buffer_push(tty); + } + tty_kref_put(tty); + } + } else { + if ((data[0] == 0x00) && (data[1] == 0x01)) { + if (data[2] == 0x00) + priv->rts = false; + else + priv->rts = true; + /* FIXME change the RTS level */ + } else { + dev_dbg(&priv->udev->dev, + "Unknown data packet received from the device:" + " %2x %2x\n", + data[0], data[1]); + } + } + } else { + dev_dbg(&priv->udev->dev, + "Improper ammount of data received from the device, " + "%d bytes", urb->actual_length); + } + +exit: + spin_lock(&priv->lock); + + /* Continue trying to always read if we should */ + if (!priv->throttled) { + usb_fill_bulk_urb(priv->bulk_read_urb, priv->udev, + usb_rcvbulkpipe(priv->udev, + priv->bulk_address), + priv->bulk_in_buffer, priv->buffer_size, + opticon_bulk_callback, priv); + result = usb_submit_urb(port->read_urb, GFP_ATOMIC); + if (result) + dev_err(&port->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, result); + } else + priv->actually_throttled = true; + spin_unlock(&priv->lock); +} + +static int opticon_open(struct tty_struct *tty, struct usb_serial_port *port, + struct file *filp) +{ + struct opticon_private *priv = usb_get_serial_data(port->serial); + unsigned long flags; + int result = 0; + + dbg("%s - port %d", __func__, port->number); + + spin_lock_irqsave(&priv->lock, flags); + priv->throttled = false; + priv->actually_throttled = false; + priv->port = port; + spin_unlock_irqrestore(&priv->lock, flags); + + /* + * Force low_latency on so that our tty_push actually forces the data + * through, otherwise it is scheduled, and with high data rates (like + * with OHCI) data can get lost. + */ + if (tty) + tty->low_latency = 1; + + /* Start reading from the device */ + usb_fill_bulk_urb(priv->bulk_read_urb, priv->udev, + usb_rcvbulkpipe(priv->udev, + priv->bulk_address), + priv->bulk_in_buffer, priv->buffer_size, + opticon_bulk_callback, priv); + result = usb_submit_urb(priv->bulk_read_urb, GFP_KERNEL); + if (result) + dev_err(&port->dev, + "%s - failed resubmitting read urb, error %d\n", + __func__, result); + return result; +} + +static void opticon_close(struct tty_struct *tty, struct usb_serial_port *port, + struct file *filp) +{ + struct opticon_private *priv = usb_get_serial_data(port->serial); + + dbg("%s - port %d", __func__, port->number); + + /* shutdown our urbs */ + usb_kill_urb(priv->bulk_read_urb); +} + +static void opticon_throttle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct opticon_private *priv = usb_get_serial_data(port->serial); + unsigned long flags; + + dbg("%s - port %d", __func__, port->number); + spin_lock_irqsave(&priv->lock, flags); + priv->throttled = true; + spin_unlock_irqrestore(&priv->lock, flags); +} + + +static void opticon_unthrottle(struct tty_struct *tty) +{ + struct usb_serial_port *port = tty->driver_data; + struct opticon_private *priv = usb_get_serial_data(port->serial); + unsigned long flags; + int result; + + dbg("%s - port %d", __func__, port->number); + + spin_lock_irqsave(&priv->lock, flags); + priv->throttled = false; + priv->actually_throttled = false; + spin_unlock_irqrestore(&priv->lock, flags); + + priv->bulk_read_urb->dev = port->serial->dev; + result = usb_submit_urb(priv->bulk_read_urb, GFP_ATOMIC); + if (result) + dev_err(&port->dev, + "%s - failed submitting read urb, error %d\n", + __func__, result); +} + +static int opticon_startup(struct usb_serial *serial) +{ + struct opticon_private *priv; + struct usb_host_interface *intf; + int i; + int retval = -ENOMEM; + bool bulk_in_found = false; + + /* create our private serial structure */ + priv = kzalloc(sizeof(*priv), GFP_KERNEL); + if (priv == NULL) { + dev_err(&serial->dev->dev, "%s - Out of memory\n", __func__); + return -ENOMEM; + } + spin_lock_init(&priv->lock); + priv->serial = serial; + priv->port = serial->port[0]; + priv->udev = serial->dev; + + /* find our bulk endpoint */ + intf = serial->interface->altsetting; + for (i = 0; i < intf->desc.bNumEndpoints; ++i) { + struct usb_endpoint_descriptor *endpoint; + + endpoint = &intf->endpoint[i].desc; + if (!usb_endpoint_is_bulk_in(endpoint)) + continue; + + priv->bulk_read_urb = usb_alloc_urb(0, GFP_KERNEL); + if (!priv->bulk_read_urb) { + dev_err(&priv->udev->dev, "out of memory\n"); + goto error; + } + + priv->buffer_size = le16_to_cpu(endpoint->wMaxPacketSize) * 2; + priv->bulk_in_buffer = kmalloc(priv->buffer_size, GFP_KERNEL); + if (!priv->bulk_in_buffer) { + dev_err(&priv->udev->dev, "out of memory\n"); + goto error; + } + + priv->bulk_address = endpoint->bEndpointAddress; + + /* set up our bulk urb */ + usb_fill_bulk_urb(priv->bulk_read_urb, priv->udev, + usb_rcvbulkpipe(priv->udev, + endpoint->bEndpointAddress), + priv->bulk_in_buffer, priv->buffer_size, + opticon_bulk_callback, priv); + + bulk_in_found = true; + break; + } + + if (!bulk_in_found) { + dev_err(&priv->udev->dev, + "Error - the proper endpoints were not found!\n"); + goto error; + } + + usb_set_serial_data(serial, priv); + return 0; + +error: + usb_free_urb(priv->bulk_read_urb); + kfree(priv->bulk_in_buffer); + kfree(priv); + return retval; +} + +static void opticon_shutdown(struct usb_serial *serial) +{ + struct opticon_private *priv = usb_get_serial_data(serial); + + dbg("%s", __func__); + + usb_kill_urb(priv->bulk_read_urb); + usb_free_urb(priv->bulk_read_urb); + kfree(priv->bulk_in_buffer); + kfree(priv); + usb_set_serial_data(serial, NULL); +} + + +static struct usb_driver opticon_driver = { + .name = "opticon", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = id_table, + .no_dynamic_id = 1, +}; + +static struct usb_serial_driver opticon_device = { + .driver = { + .owner = THIS_MODULE, + .name = "opticon", + }, + .id_table = id_table, + .usb_driver = &opticon_driver, + .num_ports = 1, + .attach = opticon_startup, + .open = opticon_open, + .close = opticon_close, + .shutdown = opticon_shutdown, + .throttle = opticon_throttle, + .unthrottle = opticon_unthrottle, +}; + +static int __init opticon_init(void) +{ + int retval; + + retval = usb_serial_register(&opticon_device); + if (retval) + return retval; + retval = usb_register(&opticon_driver); + if (retval) + usb_serial_deregister(&opticon_device); + return retval; +} + +static void __exit opticon_exit(void) +{ + usb_deregister(&opticon_driver); + usb_serial_deregister(&opticon_device); +} + +module_init(opticon_init); +module_exit(opticon_exit); +MODULE_LICENSE("GPL"); + +module_param(debug, bool, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(debug, "Debug enabled or not"); diff --git a/drivers/usb/serial/option.c b/drivers/usb/serial/option.c index 809697b3c7fc..5ed183477aaf 100644 --- a/drivers/usb/serial/option.c +++ b/drivers/usb/serial/option.c @@ -522,9 +522,9 @@ static int debug; /* per port private data */ #define N_IN_URB 4 -#define N_OUT_URB 1 +#define N_OUT_URB 4 #define IN_BUFLEN 4096 -#define OUT_BUFLEN 128 +#define OUT_BUFLEN 4096 struct option_port_private { /* Input endpoints and buffer for this port */ @@ -654,10 +654,6 @@ static int option_write(struct tty_struct *tty, struct usb_serial_port *port, usb_unlink_urb(this_urb); continue; } - if (this_urb->status != 0) - dbg("usb_write %p failed (err=%d)", - this_urb, this_urb->status); - dbg("%s: endpoint %d buf %d", __func__, usb_pipeendpoint(this_urb->pipe), i); @@ -669,8 +665,7 @@ static int option_write(struct tty_struct *tty, struct usb_serial_port *port, err = usb_submit_urb(this_urb, GFP_ATOMIC); if (err) { dbg("usb_submit_urb %p (write bulk) failed " - "(%d, has %d)", this_urb, - err, this_urb->status); + "(%d)", this_urb, err); clear_bit(i, &portdata->out_busy); continue; } diff --git a/drivers/usb/serial/siemens_mpi.c b/drivers/usb/serial/siemens_mpi.c new file mode 100644 index 000000000000..951ea0c6ba77 --- /dev/null +++ b/drivers/usb/serial/siemens_mpi.c @@ -0,0 +1,77 @@ +/* + * Siemens USB-MPI Serial USB driver + * + * Copyright (C) 2005 Thomas Hergenhahn <thomas.hergenhahn@suse.de> + * Copyright (C) 2005,2008 Greg Kroah-Hartman <gregkh@suse.de> + * + * 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/kernel.h> +#include <linux/init.h> +#include <linux/tty.h> +#include <linux/module.h> +#include <linux/usb.h> +#include <linux/usb/serial.h> + +/* Version Information */ +#define DRIVER_VERSION "Version 0.1 09/26/2005" +#define DRIVER_AUTHOR "Thomas Hergenhahn@web.de http://libnodave.sf.net" +#define DRIVER_DESC "Driver for Siemens USB/MPI adapter" + + +static struct usb_device_id id_table[] = { + /* Vendor and product id for 6ES7-972-0CB20-0XA0 */ + { USB_DEVICE(0x908, 0x0004) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +static struct usb_driver siemens_usb_mpi_driver = { + .name = "siemens_mpi", + .probe = usb_serial_probe, + .disconnect = usb_serial_disconnect, + .id_table = id_table, +}; + +static struct usb_serial_driver siemens_usb_mpi_device = { + .driver = { + .owner = THIS_MODULE, + .name = "siemens_mpi", + }, + .id_table = id_table, + .num_ports = 1, +}; + +static int __init siemens_usb_mpi_init(void) +{ + int retval; + + retval = usb_serial_register(&siemens_usb_mpi_device); + if (retval) + goto failed_usb_serial_register; + retval = usb_register(&siemens_usb_mpi_driver); + if (retval) + goto failed_usb_register; + printk(KERN_INFO DRIVER_DESC "\n"); + printk(KERN_INFO DRIVER_VERSION " " DRIVER_AUTHOR "\n"); + return retval; +failed_usb_register: + usb_serial_deregister(&siemens_usb_mpi_device); +failed_usb_serial_register: + return retval; +} + +static void __exit siemens_usb_mpi_exit(void) +{ + usb_deregister(&siemens_usb_mpi_driver); + usb_serial_deregister(&siemens_usb_mpi_device); +} + +module_init(siemens_usb_mpi_init); +module_exit(siemens_usb_mpi_exit); +MODULE_AUTHOR(DRIVER_AUTHOR); +MODULE_DESCRIPTION(DRIVER_DESC); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/serial/spcp8x5.c b/drivers/usb/serial/spcp8x5.c index a65bc2bd8e71..5e7528cc81a8 100644 --- a/drivers/usb/serial/spcp8x5.c +++ b/drivers/usb/serial/spcp8x5.c @@ -709,21 +709,20 @@ static void spcp8x5_read_bulk_callback(struct urb *urb) unsigned char *data = urb->transfer_buffer; unsigned long flags; int i; - int result; - u8 status = 0; + int result = urb->status; + u8 status; char tty_flag; - dev_dbg(&port->dev, "start, urb->status = %d, " - "urb->actual_length = %d\n,", urb->status, urb->actual_length); + dev_dbg(&port->dev, "start, result = %d, urb->actual_length = %d\n,", + result, urb->actual_length); /* check the urb status */ - if (urb->status) { + if (result) { if (!port->port.count) return; - if (urb->status == -EPROTO) { + if (result == -EPROTO) { /* spcp8x5 mysteriously fails with -EPROTO */ /* reschedule the read */ - urb->status = 0; urb->dev = port->serial->dev; result = usb_submit_urb(urb , GFP_ATOMIC); if (result) @@ -833,8 +832,9 @@ static void spcp8x5_write_bulk_callback(struct urb *urb) struct usb_serial_port *port = urb->context; struct spcp8x5_private *priv = usb_get_serial_port_data(port); int result; + int status = urb->status; - switch (urb->status) { + switch (status) { case 0: /* success */ break; @@ -843,14 +843,14 @@ static void spcp8x5_write_bulk_callback(struct urb *urb) case -ESHUTDOWN: /* this urb is terminated, clean up */ dev_dbg(&port->dev, "urb shutting down with status: %d\n", - urb->status); + status); priv->write_urb_in_use = 0; return; default: /* error in the urb, so we have to resubmit it */ dbg("%s - Overflow in write", __func__); dbg("%s - nonzero write bulk status received: %d", - __func__, urb->status); + __func__, status); port->write_urb->transfer_buffer_length = 1; port->write_urb->dev = port->serial->dev; result = usb_submit_urb(port->write_urb, GFP_ATOMIC); diff --git a/drivers/usb/serial/usb_debug.c b/drivers/usb/serial/usb_debug.c index fc5d9952b03b..6c9cbb59552a 100644 --- a/drivers/usb/serial/usb_debug.c +++ b/drivers/usb/serial/usb_debug.c @@ -31,7 +31,7 @@ static struct usb_driver debug_driver = { .no_dynamic_id = 1, }; -int usb_debug_open(struct tty_struct *tty, struct usb_serial_port *port, +static int usb_debug_open(struct tty_struct *tty, struct usb_serial_port *port, struct file *filp) { port->bulk_out_size = USB_DEBUG_MAX_PACKET_SIZE; diff --git a/drivers/usb/storage/Kconfig b/drivers/usb/storage/Kconfig index c68b738900bd..9df6887b91f6 100644 --- a/drivers/usb/storage/Kconfig +++ b/drivers/usb/storage/Kconfig @@ -61,13 +61,6 @@ config USB_STORAGE_ISD200 - CyQ've CQ8060A CDRW drive - Planex eXtreme Drive RX-25HU USB-IDE cable (not model RX-25U) -config USB_STORAGE_DPCM - bool "Microtech/ZiO! CompactFlash/SmartMedia support" - depends on USB_STORAGE - help - Say Y here to support the Microtech/ZiO! CompactFlash reader. - There is a web page at <http://www.ziocorp.com/products/>. - config USB_STORAGE_USBAT bool "USBAT/USBAT02-based storage support" depends on USB_STORAGE @@ -90,12 +83,12 @@ config USB_STORAGE_USBAT - Sandisk ImageMate SDDR-05b config USB_STORAGE_SDDR09 - bool "SanDisk SDDR-09 (and other SmartMedia) support" + bool "SanDisk SDDR-09 (and other SmartMedia, including DPCM) support" depends on USB_STORAGE help Say Y here to include additional code to support the Sandisk SDDR-09 SmartMedia reader in the USB Mass Storage driver. - Also works for the Microtech Zio! SmartMedia reader. + Also works for the Microtech Zio! CompactFlash/SmartMedia reader. config USB_STORAGE_SDDR55 bool "SanDisk SDDR-55 SmartMedia support" diff --git a/drivers/usb/storage/Makefile b/drivers/usb/storage/Makefile index 7f8beb5366ae..b32069313390 100644 --- a/drivers/usb/storage/Makefile +++ b/drivers/usb/storage/Makefile @@ -14,7 +14,6 @@ usb-storage-obj-$(CONFIG_USB_STORAGE_USBAT) += shuttle_usbat.o usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR09) += sddr09.o usb-storage-obj-$(CONFIG_USB_STORAGE_SDDR55) += sddr55.o usb-storage-obj-$(CONFIG_USB_STORAGE_FREECOM) += freecom.o -usb-storage-obj-$(CONFIG_USB_STORAGE_DPCM) += dpcm.o usb-storage-obj-$(CONFIG_USB_STORAGE_ISD200) += isd200.o usb-storage-obj-$(CONFIG_USB_STORAGE_DATAFAB) += datafab.o usb-storage-obj-$(CONFIG_USB_STORAGE_JUMPSHOT) += jumpshot.o @@ -24,7 +23,7 @@ usb-storage-obj-$(CONFIG_USB_STORAGE_KARMA) += karma.o usb-storage-obj-$(CONFIG_USB_STORAGE_CYPRESS_ATACB) += cypress_atacb.o usb-storage-objs := scsiglue.o protocol.o transport.o usb.o \ - initializers.o sierra_ms.o $(usb-storage-obj-y) + initializers.o sierra_ms.o option_ms.o $(usb-storage-obj-y) ifneq ($(CONFIG_USB_LIBUSUAL),) obj-$(CONFIG_USB) += libusual.o diff --git a/drivers/usb/storage/dpcm.c b/drivers/usb/storage/dpcm.c deleted file mode 100644 index 939923471af4..000000000000 --- a/drivers/usb/storage/dpcm.c +++ /dev/null @@ -1,86 +0,0 @@ -/* Driver for Microtech DPCM-USB CompactFlash/SmartMedia reader - * - * DPCM driver v0.1: - * - * First release - * - * Current development and maintenance by: - * (c) 2000 Brian Webb (webbb@earthlink.net) - * - * This device contains both a CompactFlash card reader, which - * uses the Control/Bulk w/o Interrupt protocol and - * a SmartMedia card reader that uses the same protocol - * as the SDDR09. - * - * 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, 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 <scsi/scsi.h> -#include <scsi/scsi_cmnd.h> -#include <scsi/scsi_device.h> - -#include "usb.h" -#include "transport.h" -#include "protocol.h" -#include "debug.h" -#include "dpcm.h" -#include "sddr09.h" - -/* - * Transport for the Microtech DPCM-USB - * - */ -int dpcm_transport(struct scsi_cmnd *srb, struct us_data *us) -{ - int ret; - - if (srb == NULL) - return USB_STOR_TRANSPORT_ERROR; - - US_DEBUGP("dpcm_transport: LUN=%d\n", srb->device->lun); - - switch (srb->device->lun) { - case 0: - - /* - * LUN 0 corresponds to the CompactFlash card reader. - */ - ret = usb_stor_CB_transport(srb, us); - break; - -#ifdef CONFIG_USB_STORAGE_SDDR09 - case 1: - - /* - * LUN 1 corresponds to the SmartMedia card reader. - */ - - /* - * Set the LUN to 0 (just in case). - */ - srb->device->lun = 0; us->srb->device->lun = 0; - ret = sddr09_transport(srb, us); - srb->device->lun = 1; us->srb->device->lun = 1; - break; - -#endif - - default: - US_DEBUGP("dpcm_transport: Invalid LUN %d\n", srb->device->lun); - ret = USB_STOR_TRANSPORT_ERROR; - break; - } - return ret; -} diff --git a/drivers/usb/storage/dpcm.h b/drivers/usb/storage/dpcm.h deleted file mode 100644 index e7b7b0f120d7..000000000000 --- a/drivers/usb/storage/dpcm.h +++ /dev/null @@ -1,32 +0,0 @@ -/* Driver for Microtech DPCM-USB CompactFlash/SmartMedia reader - * - * DPCM driver v0.1: - * - * First release - * - * Current development and maintenance by: - * (c) 2000 Brian Webb (webbb@earthlink.net) - * - * See dpcm.c for more explanation - * - * 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, 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 _MICROTECH_DPCM_USB_H -#define _MICROTECH_DPCM_USB_H - -extern int dpcm_transport(struct scsi_cmnd *srb, struct us_data *us); - -#endif diff --git a/drivers/usb/storage/libusual.c b/drivers/usb/storage/libusual.c index d617e8ae6b00..f970b27ba308 100644 --- a/drivers/usb/storage/libusual.c +++ b/drivers/usb/storage/libusual.c @@ -46,6 +46,12 @@ static int usu_probe_thread(void *arg); { USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin,bcdDeviceMax), \ .driver_info = (flags)|(USB_US_TYPE_STOR<<24) } +#define COMPLIANT_DEV(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax, \ + vendorName, productName, useProtocol, useTransport, \ + initFunction, flags) \ +{ USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin, bcdDeviceMax), \ + .driver_info = (flags) } + #define USUAL_DEV(useProto, useTrans, useType) \ { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans), \ .driver_info = ((useType)<<24) } @@ -57,6 +63,7 @@ struct usb_device_id storage_usb_ids [] = { #undef USUAL_DEV #undef UNUSUAL_DEV +#undef COMPLIANT_DEV MODULE_DEVICE_TABLE(usb, storage_usb_ids); EXPORT_SYMBOL_GPL(storage_usb_ids); diff --git a/drivers/usb/storage/option_ms.c b/drivers/usb/storage/option_ms.c new file mode 100644 index 000000000000..353f922939a4 --- /dev/null +++ b/drivers/usb/storage/option_ms.c @@ -0,0 +1,147 @@ +/* + * Driver for Option High Speed Mobile Devices. + * + * (c) 2008 Dan Williams <dcbw@redhat.com> + * + * Inspiration taken from sierra_ms.c by Kevin Lloyd <klloyd@sierrawireless.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/usb.h> + +#include "usb.h" +#include "transport.h" +#include "option_ms.h" +#include "debug.h" + +#define ZCD_FORCE_MODEM 0x01 +#define ZCD_ALLOW_MS 0x02 + +static unsigned int option_zero_cd = ZCD_FORCE_MODEM; +module_param(option_zero_cd, uint, S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(option_zero_cd, "ZeroCD mode (1=Force Modem (default)," + " 2=Allow CD-Rom"); + +#define RESPONSE_LEN 1024 + +static int option_rezero(struct us_data *us, int ep_in, int ep_out) +{ + const unsigned char rezero_msg[] = { + 0x55, 0x53, 0x42, 0x43, 0x78, 0x56, 0x34, 0x12, + 0x01, 0x00, 0x00, 0x00, 0x80, 0x00, 0x06, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + char *buffer; + int result; + + US_DEBUGP("Option MS: %s", "DEVICE MODE SWITCH\n"); + + buffer = kzalloc(RESPONSE_LEN, GFP_KERNEL); + if (buffer == NULL) + return USB_STOR_TRANSPORT_ERROR; + + memcpy(buffer, rezero_msg, sizeof (rezero_msg)); + result = usb_stor_bulk_transfer_buf(us, + usb_sndbulkpipe(us->pusb_dev, ep_out), + buffer, sizeof (rezero_msg), NULL); + if (result != USB_STOR_XFER_GOOD) { + result = USB_STOR_XFER_ERROR; + goto out; + } + + /* Some of the devices need to be asked for a response, but we don't + * care what that response is. + */ + result = usb_stor_bulk_transfer_buf(us, + usb_sndbulkpipe(us->pusb_dev, ep_out), + buffer, RESPONSE_LEN, NULL); + result = USB_STOR_XFER_GOOD; + +out: + kfree(buffer); + return result; +} + +int option_ms_init(struct us_data *us) +{ + struct usb_device *udev; + struct usb_interface *intf; + struct usb_host_interface *iface_desc; + struct usb_endpoint_descriptor *endpoint = NULL; + u8 ep_in = 0, ep_out = 0; + int ep_in_size = 0, ep_out_size = 0; + int i, result; + + udev = us->pusb_dev; + intf = us->pusb_intf; + + /* Ensure it's really a ZeroCD device; devices that are already + * in modem mode return 0xFF for class, subclass, and protocol. + */ + if (udev->descriptor.bDeviceClass != 0 || + udev->descriptor.bDeviceSubClass != 0 || + udev->descriptor.bDeviceProtocol != 0) + return USB_STOR_TRANSPORT_GOOD; + + US_DEBUGP("Option MS: option_ms_init called\n"); + + /* Find the right mass storage interface */ + iface_desc = intf->cur_altsetting; + if (iface_desc->desc.bInterfaceClass != 0x8 || + iface_desc->desc.bInterfaceSubClass != 0x6 || + iface_desc->desc.bInterfaceProtocol != 0x50) { + US_DEBUGP("Option MS: mass storage interface not found, no action " + "required\n"); + return USB_STOR_TRANSPORT_GOOD; + } + + /* Find the mass storage bulk endpoints */ + for (i = 0; i < iface_desc->desc.bNumEndpoints && (!ep_in_size || !ep_out_size); ++i) { + endpoint = &iface_desc->endpoint[i].desc; + + if (usb_endpoint_is_bulk_in(endpoint)) { + ep_in = usb_endpoint_num(endpoint); + ep_in_size = le16_to_cpu(endpoint->wMaxPacketSize); + } else if (usb_endpoint_is_bulk_out(endpoint)) { + ep_out = usb_endpoint_num(endpoint); + ep_out_size = le16_to_cpu(endpoint->wMaxPacketSize); + } + } + + /* Can't find the mass storage endpoints */ + if (!ep_in_size || !ep_out_size) { + US_DEBUGP("Option MS: mass storage endpoints not found, no action " + "required\n"); + return USB_STOR_TRANSPORT_GOOD; + } + + /* Force Modem mode */ + if (option_zero_cd == ZCD_FORCE_MODEM) { + US_DEBUGP("Option MS: %s", "Forcing Modem Mode\n"); + result = option_rezero(us, ep_in, ep_out); + if (result != USB_STOR_XFER_GOOD) + US_DEBUGP("Option MS: Failed to switch to modem mode.\n"); + return -EIO; + } else if (option_zero_cd == ZCD_ALLOW_MS) { + /* Allow Mass Storage mode (keep CD-Rom) */ + US_DEBUGP("Option MS: %s", "Allowing Mass Storage Mode if device" + " requests it\n"); + } + + return USB_STOR_TRANSPORT_GOOD; +} + diff --git a/drivers/usb/storage/option_ms.h b/drivers/usb/storage/option_ms.h new file mode 100644 index 000000000000..b6e448cab039 --- /dev/null +++ b/drivers/usb/storage/option_ms.h @@ -0,0 +1,4 @@ +#ifndef _OPTION_MS_H_ +#define _OPTION_MS_H_ +extern int option_ms_init(struct us_data *us); +#endif diff --git a/drivers/usb/storage/protocol.c b/drivers/usb/storage/protocol.c index 3b3357e20ea7..be441d84bc64 100644 --- a/drivers/usb/storage/protocol.c +++ b/drivers/usb/storage/protocol.c @@ -56,9 +56,9 @@ * Protocol routines ***********************************************************************/ -void usb_stor_qic157_command(struct scsi_cmnd *srb, struct us_data *us) +void usb_stor_pad12_command(struct scsi_cmnd *srb, struct us_data *us) { - /* Pad the ATAPI command with zeros + /* Pad the SCSI command with zeros out to 12 bytes * * NOTE: This only works because a scsi_cmnd struct field contains * a unsigned char cmnd[16], so we know we have storage available @@ -73,26 +73,6 @@ void usb_stor_qic157_command(struct scsi_cmnd *srb, struct us_data *us) usb_stor_invoke_transport(srb, us); } -void usb_stor_ATAPI_command(struct scsi_cmnd *srb, struct us_data *us) -{ - /* Pad the ATAPI command with zeros - * - * NOTE: This only works because a scsi_cmnd struct field contains - * a unsigned char cmnd[16], so we know we have storage available - */ - - /* Pad the ATAPI command with zeros */ - for (; srb->cmd_len<12; srb->cmd_len++) - srb->cmnd[srb->cmd_len] = 0; - - /* set command length to 12 bytes */ - srb->cmd_len = 12; - - /* send the command to the transport layer */ - usb_stor_invoke_transport(srb, us); -} - - void usb_stor_ufi_command(struct scsi_cmnd *srb, struct us_data *us) { /* fix some commands -- this is a form of mode translation diff --git a/drivers/usb/storage/protocol.h b/drivers/usb/storage/protocol.h index 487056ffb516..ffc3e2af0156 100644 --- a/drivers/usb/storage/protocol.h +++ b/drivers/usb/storage/protocol.h @@ -40,8 +40,7 @@ #define _PROTOCOL_H_ /* Protocol handling routines */ -extern void usb_stor_ATAPI_command(struct scsi_cmnd*, struct us_data*); -extern void usb_stor_qic157_command(struct scsi_cmnd*, struct us_data*); +extern void usb_stor_pad12_command(struct scsi_cmnd*, struct us_data*); extern void usb_stor_ufi_command(struct scsi_cmnd*, struct us_data*); extern void usb_stor_transparent_scsi_command(struct scsi_cmnd*, struct us_data*); diff --git a/drivers/usb/storage/scsiglue.c b/drivers/usb/storage/scsiglue.c index 09779f6a8179..2a42b862aa9f 100644 --- a/drivers/usb/storage/scsiglue.c +++ b/drivers/usb/storage/scsiglue.c @@ -59,6 +59,13 @@ #include "transport.h" #include "protocol.h" +/* Vendor IDs for companies that seem to include the READ CAPACITY bug + * in all their devices + */ +#define VENDOR_ID_NOKIA 0x0421 +#define VENDOR_ID_NIKON 0x04b0 +#define VENDOR_ID_MOTOROLA 0x22b8 + /*********************************************************************** * Host functions ***********************************************************************/ @@ -129,11 +136,35 @@ static int slave_configure(struct scsi_device *sdev) max_sectors); } + /* Some USB host controllers can't do DMA; they have to use PIO. + * They indicate this by setting their dma_mask to NULL. For + * such controllers we need to make sure the block layer sets + * up bounce buffers in addressable memory. + */ + if (!us->pusb_dev->bus->controller->dma_mask) + blk_queue_bounce_limit(sdev->request_queue, BLK_BOUNCE_HIGH); + /* We can't put these settings in slave_alloc() because that gets * called before the device type is known. Consequently these * settings can't be overridden via the scsi devinfo mechanism. */ if (sdev->type == TYPE_DISK) { + /* Some vendors seem to put the READ CAPACITY bug into + * all their devices -- primarily makers of cell phones + * and digital cameras. Since these devices always use + * flash media and can be expected to have an even number + * of sectors, we will always enable the CAPACITY_HEURISTICS + * flag unless told otherwise. */ + switch (le16_to_cpu(us->pusb_dev->descriptor.idVendor)) { + case VENDOR_ID_NOKIA: + case VENDOR_ID_NIKON: + case VENDOR_ID_MOTOROLA: + if (!(us->fflags & (US_FL_FIX_CAPACITY | + US_FL_CAPACITY_OK))) + us->fflags |= US_FL_CAPACITY_HEURISTICS; + break; + } + /* Disk-type devices use MODE SENSE(6) if the protocol * (SubClass) is Transparent SCSI, otherwise they use * MODE SENSE(10). */ @@ -170,6 +201,10 @@ static int slave_configure(struct scsi_device *sdev) if (us->fflags & US_FL_CAPACITY_HEURISTICS) sdev->guess_capacity = 1; + /* assume SPC3 or latter devices support sense size > 18 */ + if (sdev->scsi_level > SCSI_SPC_2) + us->fflags |= US_FL_SANE_SENSE; + /* Some devices report a SCSI revision level above 2 but are * unable to handle the REPORT LUNS command (for which * support is mandatory at level 3). Since we already have @@ -196,6 +231,14 @@ static int slave_configure(struct scsi_device *sdev) * sector in a larger then 1 sector read, since the performance * impact is negible we set this flag for all USB disks */ sdev->last_sector_bug = 1; + + /* Enable last-sector hacks for single-target devices using + * the Bulk-only transport, unless we already know the + * capacity will be decremented or is correct. */ + if (!(us->fflags & (US_FL_FIX_CAPACITY | US_FL_CAPACITY_OK | + US_FL_SCM_MULT_TARG)) && + us->protocol == US_PR_BULK) + us->use_last_sector_hacks = 1; } else { /* Non-disk-type devices don't need to blacklist any pages diff --git a/drivers/usb/storage/sddr09.c b/drivers/usb/storage/sddr09.c index c5a54b872c24..531ae5c5abf3 100644 --- a/drivers/usb/storage/sddr09.c +++ b/drivers/usb/storage/sddr09.c @@ -45,6 +45,7 @@ #include <scsi/scsi.h> #include <scsi/scsi_cmnd.h> +#include <scsi/scsi_device.h> #include "usb.h" #include "transport.h" @@ -1446,6 +1447,48 @@ usb_stor_sddr09_dpcm_init(struct us_data *us) { } /* + * Transport for the Microtech DPCM-USB + */ +int dpcm_transport(struct scsi_cmnd *srb, struct us_data *us) +{ + int ret; + + US_DEBUGP("dpcm_transport: LUN=%d\n", srb->device->lun); + + switch (srb->device->lun) { + case 0: + + /* + * LUN 0 corresponds to the CompactFlash card reader. + */ + ret = usb_stor_CB_transport(srb, us); + break; + + case 1: + + /* + * LUN 1 corresponds to the SmartMedia card reader. + */ + + /* + * Set the LUN to 0 (just in case). + */ + srb->device->lun = 0; + ret = sddr09_transport(srb, us); + srb->device->lun = 1; + break; + + default: + US_DEBUGP("dpcm_transport: Invalid LUN %d\n", + srb->device->lun); + ret = USB_STOR_TRANSPORT_ERROR; + break; + } + return ret; +} + + +/* * Transport for the Sandisk SDDR-09 */ int sddr09_transport(struct scsi_cmnd *srb, struct us_data *us) diff --git a/drivers/usb/storage/sddr09.h b/drivers/usb/storage/sddr09.h index e50033ad7b19..b701172e12e3 100644 --- a/drivers/usb/storage/sddr09.h +++ b/drivers/usb/storage/sddr09.h @@ -28,8 +28,11 @@ /* Sandisk SDDR-09 stuff */ extern int sddr09_transport(struct scsi_cmnd *srb, struct us_data *us); +extern int usb_stor_sddr09_init(struct us_data *us); + +/* Microtech DPCM-USB stuff */ +extern int dpcm_transport(struct scsi_cmnd *srb, struct us_data *us); extern int usb_stor_sddr09_dpcm_init(struct us_data *us); -extern int usb_stor_sddr09_init(struct us_data *us); #endif diff --git a/drivers/usb/storage/transport.c b/drivers/usb/storage/transport.c index 79108d5d3171..1d5438e6363b 100644 --- a/drivers/usb/storage/transport.c +++ b/drivers/usb/storage/transport.c @@ -57,6 +57,9 @@ #include "scsiglue.h" #include "debug.h" +#include <linux/blkdev.h> +#include "../../scsi/sd.h" + /*********************************************************************** * Data transfer routines @@ -511,6 +514,110 @@ int usb_stor_bulk_transfer_sg(struct us_data* us, unsigned int pipe, * Transport routines ***********************************************************************/ +/* There are so many devices that report the capacity incorrectly, + * this routine was written to counteract some of the resulting + * problems. + */ +static void last_sector_hacks(struct us_data *us, struct scsi_cmnd *srb) +{ + struct gendisk *disk; + struct scsi_disk *sdkp; + u32 sector; + + /* To Report "Medium Error: Record Not Found */ + static unsigned char record_not_found[18] = { + [0] = 0x70, /* current error */ + [2] = MEDIUM_ERROR, /* = 0x03 */ + [7] = 0x0a, /* additional length */ + [12] = 0x14 /* Record Not Found */ + }; + + /* If last-sector problems can't occur, whether because the + * capacity was already decremented or because the device is + * known to report the correct capacity, then we don't need + * to do anything. + */ + if (!us->use_last_sector_hacks) + return; + + /* Was this command a READ(10) or a WRITE(10)? */ + if (srb->cmnd[0] != READ_10 && srb->cmnd[0] != WRITE_10) + goto done; + + /* Did this command access the last sector? */ + sector = (srb->cmnd[2] << 24) | (srb->cmnd[3] << 16) | + (srb->cmnd[4] << 8) | (srb->cmnd[5]); + disk = srb->request->rq_disk; + if (!disk) + goto done; + sdkp = scsi_disk(disk); + if (!sdkp) + goto done; + if (sector + 1 != sdkp->capacity) + goto done; + + if (srb->result == SAM_STAT_GOOD && scsi_get_resid(srb) == 0) { + + /* The command succeeded. If the capacity is odd + * (i.e., if the sector number is even) then the + * "always-even" heuristic would be wrong for this + * device. Issue a WARN() so that the kerneloops.org + * project will be notified and we will then know to + * mark the device with a CAPACITY_OK flag. Hopefully + * this will occur for only a few devices. + * + * Use the sign of us->last_sector_hacks to tell whether + * the warning has already been issued; we don't need + * more than one warning per device. + */ + if (!(sector & 1) && us->use_last_sector_hacks > 0) { + unsigned vid = le16_to_cpu( + us->pusb_dev->descriptor.idVendor); + unsigned pid = le16_to_cpu( + us->pusb_dev->descriptor.idProduct); + unsigned rev = le16_to_cpu( + us->pusb_dev->descriptor.bcdDevice); + + WARN(1, "%s: Successful last sector success at %u, " + "device %04x:%04x:%04x\n", + sdkp->disk->disk_name, sector, + vid, pid, rev); + us->use_last_sector_hacks = -1; + } + + } else { + /* The command failed. Allow up to 3 retries in case this + * is some normal sort of failure. After that, assume the + * capacity is wrong and we're trying to access the sector + * beyond the end. Replace the result code and sense data + * with values that will cause the SCSI core to fail the + * command immediately, instead of going into an infinite + * (or even just a very long) retry loop. + */ + if (++us->last_sector_retries < 3) + return; + srb->result = SAM_STAT_CHECK_CONDITION; + memcpy(srb->sense_buffer, record_not_found, + sizeof(record_not_found)); + + /* In theory we might want to issue a WARN() here if the + * capacity is even, since it could indicate the device + * has the READ CAPACITY bug _and_ the real capacity is + * odd. But it could also indicate that the device + * simply can't access its last sector, a failure mode + * which is surprisingly common. So no warning. + */ + } + + done: + /* Don't reset the retry counter for TEST UNIT READY commands, + * because they get issued after device resets which might be + * caused by a failed last-sector access. + */ + if (srb->cmnd[0] != TEST_UNIT_READY) + us->last_sector_retries = 0; +} + /* Invoke the transport and basic error-handling/recovery methods * * This is used by the protocol layers to actually send the message to @@ -544,6 +651,7 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) /* if the transport provided its own sense data, don't auto-sense */ if (result == USB_STOR_TRANSPORT_NO_SENSE) { srb->result = SAM_STAT_CHECK_CONDITION; + last_sector_hacks(us, srb); return; } @@ -579,6 +687,20 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) } /* + * Determine if this device is SAT by seeing if the + * command executed successfully. Otherwise we'll have + * to wait for at least one CHECK_CONDITION to determine + * SANE_SENSE support + */ + if ((srb->cmnd[0] == ATA_16 || srb->cmnd[0] == ATA_12) && + result == USB_STOR_TRANSPORT_GOOD && + !(us->fflags & US_FL_SANE_SENSE) && + !(srb->cmnd[2] & 0x20)) { + US_DEBUGP("-- SAT supported, increasing auto-sense\n"); + us->fflags |= US_FL_SANE_SENSE; + } + + /* * A short transfer on a command where we don't expect it * is unusual, but it doesn't mean we need to auto-sense. */ @@ -595,10 +717,15 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) if (need_auto_sense) { int temp_result; struct scsi_eh_save ses; + int sense_size = US_SENSE_SIZE; + + /* device supports and needs bigger sense buffer */ + if (us->fflags & US_FL_SANE_SENSE) + sense_size = ~0; US_DEBUGP("Issuing auto-REQUEST_SENSE\n"); - scsi_eh_prep_cmnd(srb, &ses, NULL, 0, US_SENSE_SIZE); + scsi_eh_prep_cmnd(srb, &ses, NULL, 0, sense_size); /* FIXME: we must do the protocol translation here */ if (us->subclass == US_SC_RBC || us->subclass == US_SC_SCSI || @@ -632,6 +759,25 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) return; } + /* If the sense data returned is larger than 18-bytes then we + * assume this device supports requesting more in the future. + * The response code must be 70h through 73h inclusive. + */ + if (srb->sense_buffer[7] > (US_SENSE_SIZE - 8) && + !(us->fflags & US_FL_SANE_SENSE) && + (srb->sense_buffer[0] & 0x7C) == 0x70) { + US_DEBUGP("-- SANE_SENSE support enabled\n"); + us->fflags |= US_FL_SANE_SENSE; + + /* Indicate to the user that we truncated their sense + * because we didn't know it supported larger sense. + */ + US_DEBUGP("-- Sense data truncated to %i from %i\n", + US_SENSE_SIZE, + srb->sense_buffer[7] + 8); + srb->sense_buffer[7] = (US_SENSE_SIZE - 8); + } + US_DEBUGP("-- Result from auto-sense is %d\n", temp_result); US_DEBUGP("-- code: 0x%x, key: 0x%x, ASC: 0x%x, ASCQ: 0x%x\n", srb->sense_buffer[0], @@ -667,6 +813,7 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) scsi_bufflen(srb) - scsi_get_resid(srb) < srb->underflow) srb->result = (DID_ERROR << 16) | (SUGGEST_RETRY << 24); + last_sector_hacks(us, srb); return; /* Error and abort processing: try to resynchronize with the device @@ -694,6 +841,7 @@ void usb_stor_invoke_transport(struct scsi_cmnd *srb, struct us_data *us) us->transport_reset(us); } clear_bit(US_FLIDX_RESETTING, &us->dflags); + last_sector_hacks(us, srb); } /* Stop the current URB transfer */ @@ -718,10 +866,10 @@ void usb_stor_stop_transport(struct us_data *us) } /* - * Control/Bulk/Interrupt transport + * Control/Bulk and Control/Bulk/Interrupt transport */ -int usb_stor_CBI_transport(struct scsi_cmnd *srb, struct us_data *us) +int usb_stor_CB_transport(struct scsi_cmnd *srb, struct us_data *us) { unsigned int transfer_length = scsi_bufflen(srb); unsigned int pipe = 0; @@ -763,6 +911,13 @@ int usb_stor_CBI_transport(struct scsi_cmnd *srb, struct us_data *us) } /* STATUS STAGE */ + + /* NOTE: CB does not have a status stage. Silly, I know. So + * we have to catch this at a higher level. + */ + if (us->protocol != US_PR_CBI) + return USB_STOR_TRANSPORT_GOOD; + result = usb_stor_intr_transfer(us, us->iobuf, 2); US_DEBUGP("Got interrupt data (0x%x, 0x%x)\n", us->iobuf[0], us->iobuf[1]); @@ -817,56 +972,6 @@ int usb_stor_CBI_transport(struct scsi_cmnd *srb, struct us_data *us) } /* - * Control/Bulk transport - */ -int usb_stor_CB_transport(struct scsi_cmnd *srb, struct us_data *us) -{ - unsigned int transfer_length = scsi_bufflen(srb); - int result; - - /* COMMAND STAGE */ - /* let's send the command via the control pipe */ - result = usb_stor_ctrl_transfer(us, us->send_ctrl_pipe, - US_CBI_ADSC, - USB_TYPE_CLASS | USB_RECIP_INTERFACE, 0, - us->ifnum, srb->cmnd, srb->cmd_len); - - /* check the return code for the command */ - US_DEBUGP("Call to usb_stor_ctrl_transfer() returned %d\n", result); - - /* if we stalled the command, it means command failed */ - if (result == USB_STOR_XFER_STALLED) { - return USB_STOR_TRANSPORT_FAILED; - } - - /* Uh oh... serious problem here */ - if (result != USB_STOR_XFER_GOOD) { - return USB_STOR_TRANSPORT_ERROR; - } - - /* DATA STAGE */ - /* transfer the data payload for this command, if one exists*/ - if (transfer_length) { - unsigned int pipe = srb->sc_data_direction == DMA_FROM_DEVICE ? - us->recv_bulk_pipe : us->send_bulk_pipe; - result = usb_stor_bulk_srb(us, pipe, srb); - US_DEBUGP("CB data stage result is 0x%x\n", result); - - /* if we stalled the data transfer it means command failed */ - if (result == USB_STOR_XFER_STALLED) - return USB_STOR_TRANSPORT_FAILED; - if (result > USB_STOR_XFER_STALLED) - return USB_STOR_TRANSPORT_ERROR; - } - - /* STATUS STAGE */ - /* NOTE: CB does not have a status stage. Silly, I know. So - * we have to catch this at a higher level. - */ - return USB_STOR_TRANSPORT_GOOD; -} - -/* * Bulk only transport */ @@ -1173,10 +1278,9 @@ int usb_stor_Bulk_reset(struct us_data *us) */ int usb_stor_port_reset(struct us_data *us) { - int result, rc_lock; + int result; - result = rc_lock = - usb_lock_device_for_reset(us->pusb_dev, us->pusb_intf); + result = usb_lock_device_for_reset(us->pusb_dev, us->pusb_intf); if (result < 0) US_DEBUGP("unable to lock device for reset: %d\n", result); else { @@ -1189,8 +1293,7 @@ int usb_stor_port_reset(struct us_data *us) US_DEBUGP("usb_reset_device returns %d\n", result); } - if (rc_lock) - usb_unlock_device(us->pusb_dev); + usb_unlock_device(us->pusb_dev); } return result; } diff --git a/drivers/usb/storage/transport.h b/drivers/usb/storage/transport.h index e70b88182f0e..242ff5e791a5 100644 --- a/drivers/usb/storage/transport.h +++ b/drivers/usb/storage/transport.h @@ -113,8 +113,6 @@ struct bulk_cs_wrap { #define US_CBI_ADSC 0 -extern int usb_stor_CBI_transport(struct scsi_cmnd *, struct us_data*); - extern int usb_stor_CB_transport(struct scsi_cmnd *, struct us_data*); extern int usb_stor_CB_reset(struct us_data*); diff --git a/drivers/usb/storage/unusual_devs.h b/drivers/usb/storage/unusual_devs.h index bfcc1fe82518..a7f9513fa19d 100644 --- a/drivers/usb/storage/unusual_devs.h +++ b/drivers/usb/storage/unusual_devs.h @@ -27,7 +27,8 @@ /* IMPORTANT NOTE: This file must be included in another file which does * the following thing for it to work: - * The macro UNUSUAL_DEV() must be defined before this file is included + * The UNUSUAL_DEV, COMPLIANT_DEV, and USUAL_DEV macros must be defined + * before this file is included. */ /* If you edit this file, please try to keep it sorted first by VendorID, @@ -46,6 +47,12 @@ * <usb-storage@lists.one-eyed-alien.net> */ +/* Note: If you add an entry only in order to set the CAPACITY_OK flag, + * use the COMPLIANT_DEV macro instead of UNUSUAL_DEV. This is + * because such entries mark devices which actually work correctly, + * as opposed to devices that do something strangely or wrongly. + */ + /* patch submitted by Vivian Bregier <Vivian.Bregier@imag.fr> */ UNUSUAL_DEV( 0x03eb, 0x2002, 0x0100, 0x0100, @@ -85,6 +92,13 @@ UNUSUAL_DEV( 0x03f0, 0x0307, 0x0001, 0x0001, US_SC_8070, US_PR_USBAT, init_usbat_cd, 0), #endif +/* Reported by Ben Efros <ben@pc-doctor.com> */ +UNUSUAL_DEV( 0x03f0, 0x070c, 0x0000, 0x0000, + "HP", + "Personal Media Drive", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_SANE_SENSE ), + /* Reported by Grant Grundler <grundler@parisc-linux.org> * HP r707 camera in "Disk" mode with 2.00.23 or 2.00.24 firmware. */ @@ -160,34 +174,6 @@ UNUSUAL_DEV( 0x0421, 0x0019, 0x0592, 0x0592, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_MAX_SECTORS_64 ), -/* Reported by Filip Joelsson <filip@blueturtle.nu> */ -UNUSUAL_DEV( 0x0421, 0x005d, 0x0001, 0x0600, - "Nokia", - "Nokia 3110c", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - -/* Reported by Ozan Sener <themgzzy@gmail.com> */ -UNUSUAL_DEV( 0x0421, 0x0060, 0x0551, 0x0551, - "Nokia", - "3500c", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - -/* Reported by CSECSY Laszlo <boobaa@frugalware.org> */ -UNUSUAL_DEV( 0x0421, 0x0063, 0x0001, 0x0601, - "Nokia", - "Nokia 3109c", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - -/* Patch for Nokia 5310 capacity */ -UNUSUAL_DEV( 0x0421, 0x006a, 0x0000, 0x0701, - "Nokia", - "5310", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - /* Reported by Mario Rettig <mariorettig@web.de> */ UNUSUAL_DEV( 0x0421, 0x042e, 0x0100, 0x0100, "Nokia", @@ -253,35 +239,6 @@ UNUSUAL_DEV( 0x0421, 0x0495, 0x0370, 0x0370, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_MAX_SECTORS_64 ), -/* Reported by Cedric Godin <cedric@belbone.be> */ -UNUSUAL_DEV( 0x0421, 0x04b9, 0x0500, 0x0551, - "Nokia", - "5300", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - -/* Reported by Richard Nauber <RichardNauber@web.de> */ -UNUSUAL_DEV( 0x0421, 0x04fa, 0x0550, 0x0660, - "Nokia", - "6300", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - -/* Patch for Nokia 5310 capacity */ -UNUSUAL_DEV( 0x0421, 0x006a, 0x0000, 0x0591, - "Nokia", - "5310", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - -/* Submitted by Ricky Wong Yung Fei <evilbladewarrior@gmail.com> */ -/* Nokia 7610 Supernova - Too many sectors reported in usb storage mode */ -UNUSUAL_DEV( 0x0421, 0x00f5, 0x0000, 0x0470, - "Nokia", - "7610 Supernova", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY ), - /* Reported by Olaf Hering <olh@suse.de> from novell bug #105878 */ UNUSUAL_DEV( 0x0424, 0x0fdc, 0x0210, 0x0210, "SMSC", @@ -289,11 +246,17 @@ UNUSUAL_DEV( 0x0424, 0x0fdc, 0x0210, 0x0210, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_SINGLE_LUN ), -#ifdef CONFIG_USB_STORAGE_DPCM +#ifdef CONFIG_USB_STORAGE_SDDR09 UNUSUAL_DEV( 0x0436, 0x0005, 0x0100, 0x0100, "Microtech", "CameraMate (DPCM_USB)", US_SC_SCSI, US_PR_DPCM_USB, NULL, 0 ), +#else +UNUSUAL_DEV( 0x0436, 0x0005, 0x0100, 0x0100, + "Microtech", + "CameraMate", + US_SC_SCSI, US_PR_CB, NULL, + US_FL_SINGLE_LUN ), #endif /* Patch submitted by Daniel Drake <dsd@gentoo.org> @@ -388,6 +351,15 @@ UNUSUAL_DEV( 0x04a4, 0x0004, 0x0001, 0x0001, "DVD-CAM DZ-MV100A Camcorder", US_SC_SCSI, US_PR_CB, NULL, US_FL_SINGLE_LUN), +/* BENQ DC5330 + * Reported by Manuel Fombuena <mfombuena@ya.com> and + * Frank Copeland <fjc@thingy.apana.org.au> */ +UNUSUAL_DEV( 0x04a5, 0x3010, 0x0100, 0x0100, + "Tekom Technologies, Inc", + "300_CAMERA", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + /* Patch for Nikon coolpix 2000 * Submitted by Fabien Cosse <fabien.cosse@wanadoo.fr>*/ UNUSUAL_DEV( 0x04b0, 0x0301, 0x0010, 0x0010, @@ -396,83 +368,6 @@ UNUSUAL_DEV( 0x04b0, 0x0301, 0x0010, 0x0010, US_SC_DEVICE, US_PR_DEVICE,NULL, US_FL_NOT_LOCKABLE ), -/* Reported by Stefan de Konink <skinkie@xs4all.nl> */ -UNUSUAL_DEV( 0x04b0, 0x0401, 0x0200, 0x0200, - "NIKON", - "NIKON DSC D100", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Tobias Kunze Briseno <t-linux@fictive.com> */ -UNUSUAL_DEV( 0x04b0, 0x0403, 0x0200, 0x0200, - "NIKON", - "NIKON DSC D2H", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Milinevsky Dmitry <niam.niam@gmail.com> */ -UNUSUAL_DEV( 0x04b0, 0x0409, 0x0100, 0x0100, - "NIKON", - "NIKON DSC D50", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Andreas Bockhold <andreas@bockionline.de> */ -UNUSUAL_DEV( 0x04b0, 0x0405, 0x0100, 0x0100, - "NIKON", - "NIKON DSC D70", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Jamie Kitson <jamie@staberinde.fsnet.co.uk> */ -UNUSUAL_DEV( 0x04b0, 0x040d, 0x0100, 0x0100, - "NIKON", - "NIKON DSC D70s", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Graber and Mike Pagano <mpagano-kernel@mpagano.com> */ -UNUSUAL_DEV( 0x04b0, 0x040f, 0x0100, 0x0200, - "NIKON", - "NIKON DSC D200", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Emil Larsson <emil@swip.net> */ -UNUSUAL_DEV( 0x04b0, 0x0411, 0x0100, 0x0111, - "NIKON", - "NIKON DSC D80", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Ortwin Glueck <odi@odi.ch> */ -UNUSUAL_DEV( 0x04b0, 0x0413, 0x0110, 0x0111, - "NIKON", - "NIKON DSC D40", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Paul Check <paul@openstreet.com> */ -UNUSUAL_DEV( 0x04b0, 0x0415, 0x0100, 0x0100, - "NIKON", - "NIKON DSC D2Xs", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by Shan Destromp (shansan@gmail.com) */ -UNUSUAL_DEV( 0x04b0, 0x0417, 0x0100, 0x0100, - "NIKON", - "NIKON DSC D40X", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* Reported by paul ready <lxtwin@homecall.co.uk> */ -UNUSUAL_DEV( 0x04b0, 0x0419, 0x0100, 0x0200, - "NIKON", - "NIKON DSC D300", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - /* Reported by Doug Maxey (dwm@austin.ibm.com) */ UNUSUAL_DEV( 0x04b3, 0x4001, 0x0110, 0x0110, "IBM", @@ -480,15 +375,6 @@ UNUSUAL_DEV( 0x04b3, 0x4001, 0x0110, 0x0110, US_SC_DEVICE, US_PR_CB, NULL, US_FL_MAX_SECTORS_MIN), -/* BENQ DC5330 - * Reported by Manuel Fombuena <mfombuena@ya.com> and - * Frank Copeland <fjc@thingy.apana.org.au> */ -UNUSUAL_DEV( 0x04a5, 0x3010, 0x0100, 0x0100, - "Tekom Technologies, Inc", - "300_CAMERA", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_IGNORE_RESIDUE ), - #ifdef CONFIG_USB_STORAGE_CYPRESS_ATACB /* CY7C68300 : support atacb */ UNUSUAL_DEV( 0x04b4, 0x6830, 0x0000, 0x9999, @@ -594,6 +480,12 @@ UNUSUAL_DEV( 0x04e6, 0x0005, 0x0100, 0x0208, "eUSB SmartMedia / CompactFlash Adapter", US_SC_SCSI, US_PR_DPCM_USB, usb_stor_sddr09_dpcm_init, 0), +#else +UNUSUAL_DEV( 0x04e6, 0x0005, 0x0100, 0x0208, + "SCM Microsystems", + "eUSB CompactFlash Adapter", + US_SC_SCSI, US_PR_CB, NULL, + US_FL_SINGLE_LUN), #endif /* Reported by Markus Demleitner <msdemlei@cl.uni-heidelberg.de> */ @@ -685,6 +577,13 @@ UNUSUAL_DEV( 0x0525, 0xa140, 0x0100, 0x0100, US_SC_8070, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY ), +/* Added by Alan Stern <stern@rowland.harvard.edu> */ +COMPLIANT_DEV(0x0525, 0xa4a5, 0x0000, 0x9999, + "Linux", + "File-backed Storage Gadget", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_CAPACITY_OK ), + /* Yakumo Mega Image 37 * Submitted by Stephan Fuhrmann <atomenergie@t-online.de> */ UNUSUAL_DEV( 0x052b, 0x1801, 0x0100, 0x0100, @@ -807,15 +706,15 @@ UNUSUAL_DEV( 0x054c, 0x006d, 0x0000, 0x9999, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY ), -/* Submitted by Mike Alborn <malborn@deandra.homeip.net> */ -UNUSUAL_DEV( 0x054c, 0x016a, 0x0000, 0x9999, +/* Submitted by Frank Engel <frankie@cse.unsw.edu.au> */ +UNUSUAL_DEV( 0x054c, 0x0099, 0x0000, 0x9999, "Sony", "PEG Mass Storage", US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY ), - -/* Submitted by Frank Engel <frankie@cse.unsw.edu.au> */ -UNUSUAL_DEV( 0x054c, 0x0099, 0x0000, 0x9999, + +/* Submitted by Mike Alborn <malborn@deandra.homeip.net> */ +UNUSUAL_DEV( 0x054c, 0x016a, 0x0000, 0x9999, "Sony", "PEG Mass Storage", US_SC_DEVICE, US_PR_DEVICE, NULL, @@ -966,6 +865,18 @@ UNUSUAL_DEV( 0x05ac, 0x120a, 0x0000, 0x9999, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_CAPACITY ), +/* Reported by Dan Williams <dcbw@redhat.com> + * Option N.V. mobile broadband modems + * Ignore driver CD mode and force into modem mode by default. + */ + +/* Globetrotter HSDPA; mass storage shows up as Qualcomm for vendor */ +UNUSUAL_DEV( 0x05c6, 0x1000, 0x0000, 0x9999, + "Option N.V.", + "Mass Storage", + US_SC_DEVICE, US_PR_DEVICE, option_ms_init, + 0), + #ifdef CONFIG_USB_STORAGE_JUMPSHOT UNUSUAL_DEV( 0x05dc, 0x0001, 0x0000, 0x0001, "Lexar", @@ -1004,6 +915,13 @@ UNUSUAL_DEV( 0x05e3, 0x0702, 0x0000, 0xffff, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_GO_SLOW | US_FL_MAX_SECTORS_64 ), +/* Reported by Ben Efros <ben@pc-doctor.com> */ +UNUSUAL_DEV( 0x05e3, 0x0723, 0x9451, 0x9451, + "Genesys Logic", + "USB to SATA", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_SANE_SENSE ), + /* Reported by Hanno Boeck <hanno@gmx.de> * Taken from the Lycoris Kernel */ UNUSUAL_DEV( 0x0636, 0x0003, 0x0000, 0x9999, @@ -1040,7 +958,7 @@ UNUSUAL_DEV( 0x067b, 0x2507, 0x0100, 0x0100, US_FL_FIX_CAPACITY | US_FL_GO_SLOW ), /* Reported by Alex Butcher <alex.butcher@assursys.co.uk> */ -UNUSUAL_DEV( 0x067b, 0x3507, 0x0001, 0x0001, +UNUSUAL_DEV( 0x067b, 0x3507, 0x0001, 0x0101, "Prolific Technology Inc.", "ATAPI-6 Bridge Controller", US_SC_DEVICE, US_PR_DEVICE, NULL, @@ -1161,11 +1079,17 @@ UNUSUAL_DEV( 0x07af, 0x0005, 0x0100, 0x0100, US_SC_DEVICE, US_PR_DEVICE, usb_stor_euscsi_init, US_FL_SCM_MULT_TARG ), -#ifdef CONFIG_USB_STORAGE_DPCM +#ifdef CONFIG_USB_STORAGE_SDDR09 UNUSUAL_DEV( 0x07af, 0x0006, 0x0100, 0x0100, "Microtech", "CameraMate (DPCM_USB)", US_SC_SCSI, US_PR_DPCM_USB, NULL, 0 ), +#else +UNUSUAL_DEV( 0x07af, 0x0006, 0x0100, 0x0100, + "Microtech", + "CameraMate", + US_SC_SCSI, US_PR_CB, NULL, + US_FL_SINGLE_LUN ), #endif #ifdef CONFIG_USB_STORAGE_ALAUDA @@ -1320,6 +1244,13 @@ UNUSUAL_DEV( 0x0840, 0x0082, 0x0001, 0x0001, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_CAPACITY), +/* Reported and patched by Nguyen Anh Quynh <aquynh@gmail.com> */ +UNUSUAL_DEV( 0x0840, 0x0084, 0x0001, 0x0001, + "Argosy", + "Storage", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + /* Entry and supporting patch by Theodore Kilgore <kilgota@auburn.edu>. * Flag will support Bulk devices which use a standards-violating 32-byte * Command Block Wrapper. Here, the "DC2MEGA" cameras (several brands) with @@ -1343,17 +1274,6 @@ UNUSUAL_DEV( 0x0851, 0x1543, 0x0200, 0x0200, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_NOT_LOCKABLE), -/* Andrew Lunn <andrew@lunn.ch> - * PanDigital Digital Picture Frame. Does not like ALLOW_MEDIUM_REMOVAL - * on LUN 4. - * Note: Vend:Prod clash with "Ltd Maxell WS30 Slim Digital Camera" -*/ -UNUSUAL_DEV( 0x0851, 0x1543, 0x0200, 0x0200, - "PanDigital", - "Photo Frame", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_NOT_LOCKABLE), - /* Submitted by Jan De Luyck <lkml@kcore.org> */ UNUSUAL_DEV( 0x08bd, 0x1100, 0x0000, 0x0000, "CITIZEN", @@ -1425,6 +1345,13 @@ UNUSUAL_DEV( 0x0a17, 0x006, 0x0000, 0xffff, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY ), +/* Reported by Jaak Ristioja <Ristioja@gmail.com> */ +UNUSUAL_DEV( 0x0a17, 0x006e, 0x0100, 0x0100, + "Pentax", + "K10D", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY ), + /* These are virtual windows driver CDs, which the zd1211rw driver * automatically converts into WLAN devices. */ UNUSUAL_DEV( 0x0ace, 0x2011, 0x0101, 0x0101, @@ -1439,6 +1366,18 @@ UNUSUAL_DEV( 0x0ace, 0x20ff, 0x0101, 0x0101, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_IGNORE_DEVICE ), +/* Reported by Dan Williams <dcbw@redhat.com> + * Option N.V. mobile broadband modems + * Ignore driver CD mode and force into modem mode by default. + */ + +/* iCON 225 */ +UNUSUAL_DEV( 0x0af0, 0x6971, 0x0000, 0x9999, + "Option N.V.", + "Mass Storage", + US_SC_DEVICE, US_PR_DEVICE, option_ms_init, + 0), + /* Reported by F. Aben <f.aben@option.com> * This device (wrongly) has a vendor-specific device descriptor. * The entry is needed so usb-storage can bind to it's mass-storage @@ -1449,6 +1388,13 @@ UNUSUAL_DEV( 0x0af0, 0x7401, 0x0000, 0x0000, US_SC_DEVICE, US_PR_DEVICE, NULL, 0 ), +/* Reported by Ben Efros <ben@pc-doctor.com> */ +UNUSUAL_DEV( 0x0bc2, 0x3010, 0x0000, 0x0000, + "Seagate", + "FreeAgent Pro", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_SANE_SENSE ), + #ifdef CONFIG_USB_STORAGE_ISD200 UNUSUAL_DEV( 0x0bf6, 0xa001, 0x0100, 0x0110, "ATI", @@ -1472,6 +1418,22 @@ UNUSUAL_DEV( 0x0c0b, 0xa109, 0x0000, 0xffff, US_FL_SINGLE_LUN ), #endif +UNUSUAL_DEV( 0x0d49, 0x7310, 0x0000, 0x9999, + "Maxtor", + "USB to SATA", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_SANE_SENSE), + +/* + * Pete Zaitcev <zaitcev@yahoo.com>, bz#164688. + * The device blatantly ignores LUN and returns 1 in GetMaxLUN. + */ +UNUSUAL_DEV( 0x0c45, 0x1060, 0x0100, 0x0100, + "Unknown", + "Unknown", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_SINGLE_LUN ), + /* Submitted by: Nick Sillik <n.sillik@temple.edu> * Needed for OneTouch extension to usb-storage * @@ -1489,16 +1451,6 @@ UNUSUAL_DEV( 0x0c0b, 0xa109, 0x0000, 0xffff, 0), #endif -/* - * Pete Zaitcev <zaitcev@yahoo.com>, bz#164688. - * The device blatantly ignores LUN and returns 1 in GetMaxLUN. - */ -UNUSUAL_DEV( 0x0c45, 0x1060, 0x0100, 0x0100, - "Unknown", - "Unknown", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_SINGLE_LUN ), - /* Submitted by Joris Struyve <joris@struyve.be> */ UNUSUAL_DEV( 0x0d96, 0x410a, 0x0001, 0xffff, "Medion", @@ -1516,6 +1468,13 @@ UNUSUAL_DEV( 0x0d96, 0x5200, 0x0001, 0x0200, "JD 5200 z3", US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_INQUIRY), +/* Reported by Jason Johnston <killean@shaw.ca> */ +UNUSUAL_DEV( 0x0dc4, 0x0073, 0x0000, 0x0000, + "Macpower Technology Co.LTD.", + "USB 2.0 3.5\" DEVICE", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_FIX_CAPACITY), + /* Reported by Lubomir Blaha <tritol@trilogic.cz> * I _REALLY_ don't know what 3rd, 4th number and all defines mean, but this * works for me. Can anybody correct these values? (I able to test corrected @@ -1638,13 +1597,6 @@ UNUSUAL_DEV( 0x0fce, 0xe030, 0x0000, 0x0000, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_FIX_CAPACITY | US_FL_IGNORE_RESIDUE ), -/* Reported by Ricardo Barberis <ricardo@dattatec.com> */ -UNUSUAL_DEV( 0x0fce, 0xe092, 0x0000, 0x0000, - "Sony Ericsson", - "P1i", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_IGNORE_RESIDUE ), - /* Reported by Emmanuel Vasilakis <evas@forthnet.gr> */ UNUSUAL_DEV( 0x0fce, 0xe031, 0x0000, 0x0000, "Sony Ericsson", @@ -1652,6 +1604,13 @@ UNUSUAL_DEV( 0x0fce, 0xe031, 0x0000, 0x0000, US_SC_DEVICE, US_PR_DEVICE, NULL, US_FL_IGNORE_RESIDUE | US_FL_FIX_CAPACITY ), +/* Reported by Ricardo Barberis <ricardo@dattatec.com> */ +UNUSUAL_DEV( 0x0fce, 0xe092, 0x0000, 0x0000, + "Sony Ericsson", + "P1i", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE ), + /* Reported by Kevin Cernekee <kpc-usbdev@gelato.uiuc.edu> * Tested on hardware version 1.10. * Entry is needed only for the initializer function override. @@ -1664,6 +1623,12 @@ UNUSUAL_DEV( 0x1019, 0x0c55, 0x0000, 0x0110, US_SC_DEVICE, US_PR_DEVICE, usb_stor_ucr61s2b_init, 0 ), +UNUSUAL_DEV( 0x1058, 0x0704, 0x0000, 0x9999, + "Western Digital", + "External HDD", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_SANE_SENSE), + /* Reported by Fabio Venturi <f.venturi@tdnet.it> * The device reports a vendor-specific bDeviceClass. */ @@ -2053,10 +2018,10 @@ UNUSUAL_DEV( 0x14cd, 0x6600, 0x0201, 0x0201, * JMicron responds to USN and several other SCSI ioctls with a * residue that causes subsequent I/O requests to fail. */ UNUSUAL_DEV( 0x152d, 0x2329, 0x0100, 0x0100, - "JMicron", - "USB to ATA/ATAPI Bridge", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_IGNORE_RESIDUE ), + "JMicron", + "USB to ATA/ATAPI Bridge", + US_SC_DEVICE, US_PR_DEVICE, NULL, + US_FL_IGNORE_RESIDUE | US_FL_SANE_SENSE ), /* Reported by Robert Schedel <r.schedel@yahoo.de> * Note: this is a 'super top' device like the above 14cd/6600 device */ @@ -2086,27 +2051,6 @@ UNUSUAL_DEV( 0x22b8, 0x3010, 0x0001, 0x0001, US_FL_FIX_CAPACITY | US_FL_IGNORE_RESIDUE ), /* - * Patch by Pete Zaitcev <zaitcev@redhat.com> - * Report by Mark Patton. Red Hat bz#208928. - * Added support for rev 0x0002 (Motorola ROKR W5) - * by Javier Smaldone <javier@smaldone.com.ar> - */ -UNUSUAL_DEV( 0x22b8, 0x4810, 0x0001, 0x0002, - "Motorola", - "RAZR V3i/ROKR W5", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* - * Patch by Jost Diederichs <jost@qdusa.com> - */ -UNUSUAL_DEV(0x22b8, 0x6410, 0x0001, 0x9999, - "Motorola Inc.", - "Motorola Phone (RAZRV3xx)", - US_SC_DEVICE, US_PR_DEVICE, NULL, - US_FL_FIX_CAPACITY), - -/* * Patch by Constantin Baranov <const@tltsu.ru> * Report by Andreas Koenecke. * Motorola ROKR Z6. diff --git a/drivers/usb/storage/usb.c b/drivers/usb/storage/usb.c index 27016fd2cad1..4becf495ca2d 100644 --- a/drivers/usb/storage/usb.c +++ b/drivers/usb/storage/usb.c @@ -75,9 +75,6 @@ #ifdef CONFIG_USB_STORAGE_SDDR55 #include "sddr55.h" #endif -#ifdef CONFIG_USB_STORAGE_DPCM -#include "dpcm.h" -#endif #ifdef CONFIG_USB_STORAGE_FREECOM #include "freecom.h" #endif @@ -103,6 +100,7 @@ #include "cypress_atacb.h" #endif #include "sierra_ms.h" +#include "option_ms.h" /* Some informational data */ MODULE_AUTHOR("Matthew Dharm <mdharm-usb@one-eyed-alien.net>"); @@ -113,6 +111,10 @@ static unsigned int delay_use = 5; module_param(delay_use, uint, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(delay_use, "seconds to delay before using a new device"); +static char quirks[128]; +module_param_string(quirks, quirks, sizeof(quirks), S_IRUGO | S_IWUSR); +MODULE_PARM_DESC(quirks, "supplemental list of device IDs and their quirks"); + /* * The entries in this table correspond, line for line, @@ -126,6 +128,8 @@ MODULE_PARM_DESC(delay_use, "seconds to delay before using a new device"); { USB_DEVICE_VER(id_vendor, id_product, bcdDeviceMin,bcdDeviceMax), \ .driver_info = (flags)|(USB_US_TYPE_STOR<<24) } +#define COMPLIANT_DEV UNUSUAL_DEV + #define USUAL_DEV(useProto, useTrans, useType) \ { USB_INTERFACE_INFO(USB_CLASS_MASS_STORAGE, useProto, useTrans), \ .driver_info = (USB_US_TYPE_STOR<<24) } @@ -134,6 +138,7 @@ static struct usb_device_id storage_usb_ids [] = { # include "unusual_devs.h" #undef UNUSUAL_DEV +#undef COMPLIANT_DEV #undef USUAL_DEV /* Terminating entry */ { } @@ -164,6 +169,8 @@ MODULE_DEVICE_TABLE (usb, storage_usb_ids); .initFunction = init_function, \ } +#define COMPLIANT_DEV UNUSUAL_DEV + #define USUAL_DEV(use_protocol, use_transport, use_type) \ { \ .useProtocol = use_protocol, \ @@ -173,6 +180,7 @@ MODULE_DEVICE_TABLE (usb, storage_usb_ids); static struct us_unusual_dev us_unusual_dev_list[] = { # include "unusual_devs.h" # undef UNUSUAL_DEV +# undef COMPLIANT_DEV # undef USUAL_DEV /* Terminating entry */ @@ -464,13 +472,83 @@ static int associate_dev(struct us_data *us, struct usb_interface *intf) US_DEBUGP("I/O buffer allocation failed\n"); return -ENOMEM; } + return 0; +} - us->sensebuf = kmalloc(US_SENSE_SIZE, GFP_KERNEL); - if (!us->sensebuf) { - US_DEBUGP("Sense buffer allocation failed\n"); - return -ENOMEM; +/* Works only for digits and letters, but small and fast */ +#define TOLOWER(x) ((x) | 0x20) + +/* Adjust device flags based on the "quirks=" module parameter */ +static void adjust_quirks(struct us_data *us) +{ + char *p; + u16 vid = le16_to_cpu(us->pusb_dev->descriptor.idVendor); + u16 pid = le16_to_cpu(us->pusb_dev->descriptor.idProduct); + unsigned f = 0; + unsigned int mask = (US_FL_SANE_SENSE | US_FL_FIX_CAPACITY | + US_FL_CAPACITY_HEURISTICS | US_FL_IGNORE_DEVICE | + US_FL_NOT_LOCKABLE | US_FL_MAX_SECTORS_64 | + US_FL_CAPACITY_OK | US_FL_IGNORE_RESIDUE | + US_FL_SINGLE_LUN | US_FL_NO_WP_DETECT); + + p = quirks; + while (*p) { + /* Each entry consists of VID:PID:flags */ + if (vid == simple_strtoul(p, &p, 16) && + *p == ':' && + pid == simple_strtoul(p+1, &p, 16) && + *p == ':') + break; + + /* Move forward to the next entry */ + while (*p) { + if (*p++ == ',') + break; + } } - return 0; + if (!*p) /* No match */ + return; + + /* Collect the flags */ + while (*++p && *p != ',') { + switch (TOLOWER(*p)) { + case 'a': + f |= US_FL_SANE_SENSE; + break; + case 'c': + f |= US_FL_FIX_CAPACITY; + break; + case 'h': + f |= US_FL_CAPACITY_HEURISTICS; + break; + case 'i': + f |= US_FL_IGNORE_DEVICE; + break; + case 'l': + f |= US_FL_NOT_LOCKABLE; + break; + case 'm': + f |= US_FL_MAX_SECTORS_64; + break; + case 'o': + f |= US_FL_CAPACITY_OK; + break; + case 'r': + f |= US_FL_IGNORE_RESIDUE; + break; + case 's': + f |= US_FL_SINGLE_LUN; + break; + case 'w': + f |= US_FL_NO_WP_DETECT; + break; + /* Ignore unrecognized flag characters */ + } + } + us->fflags = (us->fflags & ~mask) | f; + dev_info(&us->pusb_intf->dev, "Quirks match for " + "vid %04x pid %04x: %x\n", + vid, pid, f); } /* Find an unusual_dev descriptor (always succeeds in the current code) */ @@ -497,6 +575,7 @@ static int get_device_info(struct us_data *us, const struct usb_device_id *id) idesc->bInterfaceProtocol : unusual_dev->useTransport; us->fflags = USB_US_ORIG_FLAGS(id->driver_info); + adjust_quirks(us); if (us->fflags & US_FL_IGNORE_DEVICE) { printk(KERN_INFO USB_STORAGE "device ignored\n"); @@ -562,7 +641,7 @@ static int get_transport(struct us_data *us) case US_PR_CBI: us->transport_name = "Control/Bulk/Interrupt"; - us->transport = usb_stor_CBI_transport; + us->transport = usb_stor_CB_transport; us->transport_reset = usb_stor_CB_reset; us->max_lun = 7; break; @@ -675,19 +754,19 @@ static int get_protocol(struct us_data *us) case US_SC_8020: us->protocol_name = "8020i"; - us->proto_handler = usb_stor_ATAPI_command; + us->proto_handler = usb_stor_pad12_command; us->max_lun = 0; break; case US_SC_QIC: us->protocol_name = "QIC-157"; - us->proto_handler = usb_stor_qic157_command; + us->proto_handler = usb_stor_pad12_command; us->max_lun = 0; break; case US_SC_8070: us->protocol_name = "8070i"; - us->proto_handler = usb_stor_ATAPI_command; + us->proto_handler = usb_stor_pad12_command; us->max_lun = 0; break; @@ -840,8 +919,6 @@ static void dissociate_dev(struct us_data *us) { US_DEBUGP("-- %s\n", __func__); - kfree(us->sensebuf); - /* Free the device-related DMA-mapped buffers */ if (us->cr) usb_buffer_free(us->pusb_dev, sizeof(*us->cr), us->cr, @@ -1064,6 +1141,7 @@ static struct usb_driver usb_storage_driver = { static int __init usb_stor_init(void) { int retval; + printk(KERN_INFO "Initializing USB Mass Storage driver...\n"); /* register the driver, return usb_register return code if error */ diff --git a/drivers/usb/storage/usb.h b/drivers/usb/storage/usb.h index a4ad73bd832d..65e674e4be99 100644 --- a/drivers/usb/storage/usb.h +++ b/drivers/usb/storage/usb.h @@ -138,7 +138,6 @@ struct us_data { struct usb_ctrlrequest *cr; /* control requests */ struct usb_sg_request current_sg; /* scatter-gather req. */ unsigned char *iobuf; /* I/O buffer */ - unsigned char *sensebuf; /* sense data buffer */ dma_addr_t cr_dma; /* buffer DMA addresses */ dma_addr_t iobuf_dma; struct task_struct *ctl_thread; /* the control thread */ @@ -155,6 +154,10 @@ struct us_data { #ifdef CONFIG_PM pm_hook suspend_resume_hook; #endif + + /* hacks for READ CAPACITY bug handling */ + int use_last_sector_hacks; + int last_sector_retries; }; /* Convert between us_data and the corresponding Scsi_Host */ diff --git a/drivers/usb/wusbcore/rh.c b/drivers/usb/wusbcore/rh.c index 95c6fa3bf6b2..3937bf6f8cef 100644 --- a/drivers/usb/wusbcore/rh.c +++ b/drivers/usb/wusbcore/rh.c @@ -326,7 +326,7 @@ static int wusbhc_rh_clear_port_feat(struct wusbhc *wusbhc, u16 feature, static int wusbhc_rh_get_port_status(struct wusbhc *wusbhc, u16 port_idx, u32 *_buf, u16 wLength) { - u16 *buf = (u16 *) _buf; + __le16 *buf = (__le16 *)_buf; if (port_idx > wusbhc->ports_max) return -EINVAL; diff --git a/drivers/uwb/i1480/dfu/usb.c b/drivers/uwb/i1480/dfu/usb.c index 686795e97195..c7080d497311 100644 --- a/drivers/uwb/i1480/dfu/usb.c +++ b/drivers/uwb/i1480/dfu/usb.c @@ -387,7 +387,7 @@ int i1480_usb_probe(struct usb_interface *iface, const struct usb_device_id *id) goto error_create; } - /* setup the fops and upload the firmare */ + /* setup the fops and upload the firmware */ i1480->pre_fw_name = "i1480-pre-phy-0.0.bin"; i1480->mac_fw_name = "i1480-usb-0.0.bin"; i1480->mac_fw_name_deprecate = "ptc-0.0.bin"; diff --git a/drivers/video/console/vgacon.c b/drivers/video/console/vgacon.c index e6210725b9ab..d012edda6d11 100644 --- a/drivers/video/console/vgacon.c +++ b/drivers/video/console/vgacon.c @@ -1332,7 +1332,7 @@ static void vgacon_save_screen(struct vc_data *c) c->vc_y = screen_info.orig_y; } - /* We can't copy in more then the size of the video buffer, + /* We can't copy in more than the size of the video buffer, * or we'll be copying in VGA BIOS */ if (!vga_is_gfx) |