diff options
Diffstat (limited to 'drivers/staging')
55 files changed, 6093 insertions, 1040 deletions
diff --git a/drivers/staging/Kconfig b/drivers/staging/Kconfig index 7696a664f8a5..5589616082e7 100644 --- a/drivers/staging/Kconfig +++ b/drivers/staging/Kconfig @@ -49,6 +49,8 @@ source "drivers/staging/go7007/Kconfig" source "drivers/staging/cx25821/Kconfig" +source "drivers/staging/tm6000/Kconfig" + source "drivers/staging/usbip/Kconfig" source "drivers/staging/winbond/Kconfig" diff --git a/drivers/staging/Makefile b/drivers/staging/Makefile index ea2e70e2fed4..ec45d4bb8c11 100644 --- a/drivers/staging/Makefile +++ b/drivers/staging/Makefile @@ -7,6 +7,7 @@ obj-$(CONFIG_ET131X) += et131x/ obj-$(CONFIG_SLICOSS) += slicoss/ obj-$(CONFIG_VIDEO_GO7007) += go7007/ obj-$(CONFIG_VIDEO_CX25821) += cx25821/ +obj-$(CONFIG_VIDEO_TM6000) += tm6000/ obj-$(CONFIG_USB_IP_COMMON) += usbip/ obj-$(CONFIG_W35UND) += winbond/ obj-$(CONFIG_PRISM2_USB) += wlan-ng/ diff --git a/drivers/staging/arlan/arlan-main.c b/drivers/staging/arlan/arlan-main.c index 921a082487a1..88fdd53cf5d3 100644 --- a/drivers/staging/arlan/arlan-main.c +++ b/drivers/staging/arlan/arlan-main.c @@ -1455,10 +1455,10 @@ static void arlan_rx_interrupt(struct net_device *dev, u_char rxStatus, u_short #ifdef ARLAN_MULTICAST if (!(dev->flags & IFF_ALLMULTI) && !(dev->flags & IFF_PROMISC) && - dev->mc_list) + !netdev_mc_empty(dev)) { char hw_dst_addr[6]; - struct dev_mc_list *dmi = dev->mc_list; + struct dev_mc_list *dmi; int i; memcpy_fromio(hw_dst_addr, arlan->ultimateDestAddress, 6); @@ -1469,20 +1469,15 @@ static void arlan_rx_interrupt(struct net_device *dev, u_char rxStatus, u_short printk(KERN_ERR "%s mcast 0x0100 \n", dev->name); else if (hw_dst_addr[1] == 0x40) printk(KERN_ERR "%s m/bcast 0x0140 \n", dev->name); - while (dmi) - { - if (dmi->dmi_addrlen == 6) { - if (arlan_debug & ARLAN_DEBUG_HEADER_DUMP) - printk(KERN_ERR "%s mcl %pM\n", - dev->name, dmi->dmi_addr); - for (i = 0; i < 6; i++) - if (dmi->dmi_addr[i] != hw_dst_addr[i]) - break; - if (i == 6) + netdev_for_each_mc_entry(dmi, dev) { + if (arlan_debug & ARLAN_DEBUG_HEADER_DUMP) + printk(KERN_ERR "%s mcl %pM\n", + dev->name, dmi->dmi_addr); + for (i = 0; i < 6; i++) + if (dmi->dmi_addr[i] != hw_dst_addr[i]) break; - } else - printk(KERN_ERR "%s: invalid multicast address length given.\n", dev->name); - dmi = dmi->next; + if (i == 6) + break; } /* we reach here if multicast filtering is on and packet * is multicast and not for receive */ diff --git a/drivers/staging/et131x/et131x_netdev.c b/drivers/staging/et131x/et131x_netdev.c index 226641acd954..40f8954dde47 100644 --- a/drivers/staging/et131x/et131x_netdev.c +++ b/drivers/staging/et131x/et131x_netdev.c @@ -404,9 +404,9 @@ void et131x_multicast(struct net_device *netdev) { struct et131x_adapter *adapter = netdev_priv(netdev); uint32_t PacketFilter = 0; - uint32_t count; unsigned long flags; - struct dev_mc_list *mclist = netdev->mc_list; + struct dev_mc_list *mclist; + int i; spin_lock_irqsave(&adapter->Lock, flags); @@ -437,11 +437,11 @@ void et131x_multicast(struct net_device *netdev) adapter->PacketFilter |= ET131X_PACKET_TYPE_ALL_MULTICAST; } - if (netdev->mc_count > NIC_MAX_MCAST_LIST) { + if (netdev_mc_count(netdev) > NIC_MAX_MCAST_LIST) { adapter->PacketFilter |= ET131X_PACKET_TYPE_ALL_MULTICAST; } - if (netdev->mc_count < 1) { + if (netdev_mc_count(netdev) < 1) { adapter->PacketFilter &= ~ET131X_PACKET_TYPE_ALL_MULTICAST; adapter->PacketFilter &= ~ET131X_PACKET_TYPE_MULTICAST; } else { @@ -449,12 +449,13 @@ void et131x_multicast(struct net_device *netdev) } /* Set values in the private adapter struct */ - adapter->MCAddressCount = netdev->mc_count; - - if (netdev->mc_count) { - count = netdev->mc_count - 1; - memcpy(adapter->MCList[count], mclist->dmi_addr, ETH_ALEN); + i = 0; + netdev_for_each_mc_addr(mclist, netdev) { + if (i == NIC_MAX_MCAST_LIST) + break; + memcpy(adapter->MCList[i++], mclist->dmi_addr, ETH_ALEN); } + adapter->MCAddressCount = i; /* Are the new flags different from the previous ones? If not, then no * action is required diff --git a/drivers/staging/go7007/s2250-board.c b/drivers/staging/go7007/s2250-board.c index 977df137a0cc..dc89502ea1b7 100644 --- a/drivers/staging/go7007/s2250-board.c +++ b/drivers/staging/go7007/s2250-board.c @@ -159,7 +159,7 @@ static int write_reg(struct i2c_client *client, u8 reg, u8 value) struct go7007 *go = i2c_get_adapdata(client->adapter); struct go7007_usb *usb; int rc; - int dev_addr = client->addr; + int dev_addr = client->addr << 1; /* firmware wants 8-bit address */ u8 *buf; if (go == NULL) diff --git a/drivers/staging/netwave/netwave_cs.c b/drivers/staging/netwave/netwave_cs.c index e61e6b9440ab..e936717d1f4b 100644 --- a/drivers/staging/netwave/netwave_cs.c +++ b/drivers/staging/netwave/netwave_cs.c @@ -1341,15 +1341,15 @@ static void set_multicast_list(struct net_device *dev) #ifdef PCMCIA_DEBUG { xstatic int old; - if (old != dev->mc_count) { - old = dev->mc_count; + if (old != netdev_mc_count(dev)) { + old = netdev_mc_count(dev); pr_debug("%s: setting Rx mode to %d addresses.\n", - dev->name, dev->mc_count); + dev->name, netdev_mc_count(dev)); } } #endif - if (dev->mc_count || (dev->flags & IFF_ALLMULTI)) { + if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI)) { /* Multicast Mode */ rcvMode = rxConfRxEna + rxConfAMP + rxConfBcast; } else if (dev->flags & IFF_PROMISC) { diff --git a/drivers/staging/octeon/Makefile b/drivers/staging/octeon/Makefile index c0a583cc2227..87447c102fa0 100644 --- a/drivers/staging/octeon/Makefile +++ b/drivers/staging/octeon/Makefile @@ -14,7 +14,6 @@ obj-${CONFIG_OCTEON_ETHERNET} := octeon-ethernet.o octeon-ethernet-objs := ethernet.o octeon-ethernet-objs += ethernet-mdio.o octeon-ethernet-objs += ethernet-mem.o -octeon-ethernet-objs += ethernet-proc.o octeon-ethernet-objs += ethernet-rgmii.o octeon-ethernet-objs += ethernet-rx.o octeon-ethernet-objs += ethernet-sgmii.o diff --git a/drivers/staging/octeon/ethernet-defines.h b/drivers/staging/octeon/ethernet-defines.h index f13131b03c33..6a2cd50a17df 100644 --- a/drivers/staging/octeon/ethernet-defines.h +++ b/drivers/staging/octeon/ethernet-defines.h @@ -41,17 +41,10 @@ * Tells the driver to populate the packet buffers with kernel skbuffs. * This allows the driver to receive packets without copying them. It also * means that 32bit userspace can't access the packet buffers. - * USE_32BIT_SHARED - * This define tells the driver to allocate memory for buffers from the - * 32bit sahred region instead of the kernel memory space. * USE_HW_TCPUDP_CHECKSUM * Controls if the Octeon TCP/UDP checksum engine is used for packet * output. If this is zero, the kernel will perform the checksum in * software. - * USE_MULTICORE_RECEIVE - * Process receive interrupts on multiple cores. This spreads the network - * load across the first 8 processors. If ths is zero, only one core - * processes incomming packets. * USE_ASYNC_IOBDMA * Use asynchronous IO access to hardware. This uses Octeon's asynchronous * IOBDMAs to issue IO accesses without stalling. Set this to zero @@ -75,29 +68,15 @@ #define CONFIG_CAVIUM_RESERVE32 0 #endif -#if CONFIG_CAVIUM_RESERVE32 -#define USE_32BIT_SHARED 1 -#define USE_SKBUFFS_IN_HW 0 -#define REUSE_SKBUFFS_WITHOUT_FREE 0 -#else -#define USE_32BIT_SHARED 0 #define USE_SKBUFFS_IN_HW 1 #ifdef CONFIG_NETFILTER #define REUSE_SKBUFFS_WITHOUT_FREE 0 #else #define REUSE_SKBUFFS_WITHOUT_FREE 1 #endif -#endif - -/* Max interrupts per second per core */ -#define INTERRUPT_LIMIT 10000 -/* Don't limit the number of interrupts */ -/*#define INTERRUPT_LIMIT 0 */ #define USE_HW_TCPUDP_CHECKSUM 1 -#define USE_MULTICORE_RECEIVE 1 - /* Enable Random Early Dropping under load */ #define USE_RED 1 #define USE_ASYNC_IOBDMA (CONFIG_CAVIUM_OCTEON_CVMSEG_SIZE > 0) @@ -115,21 +94,12 @@ /* Use this to not have FPA frees control L2 */ /*#define DONT_WRITEBACK(x) 0 */ -/* Maximum number of packets to process per interrupt. */ -#define MAX_RX_PACKETS 120 /* Maximum number of SKBs to try to free per xmit packet. */ -#define MAX_SKB_TO_FREE 10 #define MAX_OUT_QUEUE_DEPTH 1000 -#ifndef CONFIG_SMP -#undef USE_MULTICORE_RECEIVE -#define USE_MULTICORE_RECEIVE 0 -#endif - -#define IP_PROTOCOL_TCP 6 -#define IP_PROTOCOL_UDP 0x11 +#define FAU_TOTAL_TX_TO_CLEAN (CVMX_FAU_REG_END - sizeof(uint32_t)) +#define FAU_NUM_PACKET_BUFFERS_TO_FREE (FAU_TOTAL_TX_TO_CLEAN - sizeof(uint32_t)) -#define FAU_NUM_PACKET_BUFFERS_TO_FREE (CVMX_FAU_REG_END - sizeof(uint32_t)) #define TOTAL_NUMBER_OF_PORTS (CVMX_PIP_NUM_INPUT_PORTS+1) diff --git a/drivers/staging/octeon/ethernet-mdio.c b/drivers/staging/octeon/ethernet-mdio.c index 05a5cc0f43ed..7e0be8d00dc3 100644 --- a/drivers/staging/octeon/ethernet-mdio.c +++ b/drivers/staging/octeon/ethernet-mdio.c @@ -96,11 +96,11 @@ const struct ethtool_ops cvm_oct_ethtool_ops = { }; /** - * IOCTL support for PHY control - * + * cvm_oct_ioctl - IOCTL support for PHY control * @dev: Device to change * @rq: the request * @cmd: the command + * * Returns Zero on success */ int cvm_oct_ioctl(struct net_device *dev, struct ifreq *rq, int cmd) @@ -153,7 +153,7 @@ static void cvm_oct_adjust_link(struct net_device *dev) /** - * Setup the PHY + * cvm_oct_phy_setup_device - setup the PHY * * @dev: Device to setup * diff --git a/drivers/staging/octeon/ethernet-mdio.h b/drivers/staging/octeon/ethernet-mdio.h index 55d0614a7cd9..a417d4fce12c 100644 --- a/drivers/staging/octeon/ethernet-mdio.h +++ b/drivers/staging/octeon/ethernet-mdio.h @@ -32,7 +32,6 @@ #include <linux/ip.h> #include <linux/string.h> #include <linux/ethtool.h> -#include <linux/mii.h> #include <linux/seq_file.h> #include <linux/proc_fs.h> #include <net/dst.h> diff --git a/drivers/staging/octeon/ethernet-mem.c b/drivers/staging/octeon/ethernet-mem.c index b595903e2af1..00cc91df6b46 100644 --- a/drivers/staging/octeon/ethernet-mem.c +++ b/drivers/staging/octeon/ethernet-mem.c @@ -4,7 +4,7 @@ * Contact: support@caviumnetworks.com * This file is part of the OCTEON SDK * - * Copyright (c) 2003-2007 Cavium Networks + * Copyright (c) 2003-2010 Cavium Networks * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 2, as @@ -26,8 +26,6 @@ **********************************************************************/ #include <linux/kernel.h> #include <linux/netdevice.h> -#include <linux/mii.h> -#include <net/dst.h> #include <asm/octeon/octeon.h> @@ -36,18 +34,19 @@ #include "cvmx-fpa.h" /** - * Fill the supplied hardware pool with skbuffs - * + * cvm_oct_fill_hw_skbuff - fill the supplied hardware pool with skbuffs * @pool: Pool to allocate an skbuff for * @size: Size of the buffer needed for the pool * @elements: Number of buffers to allocate + * + * Returns the actual number of buffers allocated. */ static int cvm_oct_fill_hw_skbuff(int pool, int size, int elements) { int freed = elements; while (freed) { - struct sk_buff *skb = dev_alloc_skb(size + 128); + struct sk_buff *skb = dev_alloc_skb(size + 256); if (unlikely(skb == NULL)) { pr_warning ("Failed to allocate skb for hardware pool %d\n", @@ -55,7 +54,7 @@ static int cvm_oct_fill_hw_skbuff(int pool, int size, int elements) break; } - skb_reserve(skb, 128 - (((unsigned long)skb->data) & 0x7f)); + skb_reserve(skb, 256 - (((unsigned long)skb->data) & 0x7f)); *(struct sk_buff **)(skb->data - sizeof(void *)) = skb; cvmx_fpa_free(skb->data, pool, DONT_WRITEBACK(size / 128)); freed--; @@ -64,8 +63,7 @@ static int cvm_oct_fill_hw_skbuff(int pool, int size, int elements) } /** - * Free the supplied hardware pool of skbuffs - * + * cvm_oct_free_hw_skbuff- free hardware pool skbuffs * @pool: Pool to allocate an skbuff for * @size: Size of the buffer needed for the pool * @elements: Number of buffers to allocate @@ -93,96 +91,76 @@ static void cvm_oct_free_hw_skbuff(int pool, int size, int elements) } /** - * This function fills a hardware pool with memory. Depending - * on the config defines, this memory might come from the - * kernel or global 32bit memory allocated with - * cvmx_bootmem_alloc. - * + * cvm_oct_fill_hw_memory - fill a hardware pool with memory. * @pool: Pool to populate * @size: Size of each buffer in the pool * @elements: Number of buffers to allocate + * + * Returns the actual number of buffers allocated. */ static int cvm_oct_fill_hw_memory(int pool, int size, int elements) { char *memory; + char *fpa; int freed = elements; - if (USE_32BIT_SHARED) { - extern uint64_t octeon_reserve32_memory; - - memory = - cvmx_bootmem_alloc_range(elements * size, 128, - octeon_reserve32_memory, - octeon_reserve32_memory + - (CONFIG_CAVIUM_RESERVE32 << 20) - - 1); - if (memory == NULL) - panic("Unable to allocate %u bytes for FPA pool %d\n", - elements * size, pool); - - pr_notice("Memory range %p - %p reserved for " - "hardware\n", memory, - memory + elements * size - 1); - - while (freed) { - cvmx_fpa_free(memory, pool, 0); - memory += size; - freed--; - } - } else { - while (freed) { - /* We need to force alignment to 128 bytes here */ - memory = kmalloc(size + 127, GFP_ATOMIC); - if (unlikely(memory == NULL)) { - pr_warning("Unable to allocate %u bytes for " - "FPA pool %d\n", - elements * size, pool); - break; - } - memory = (char *)(((unsigned long)memory + 127) & -128); - cvmx_fpa_free(memory, pool, 0); - freed--; + while (freed) { + /* + * FPA memory must be 128 byte aligned. Since we are + * aligning we need to save the original pointer so we + * can feed it to kfree when the memory is returned to + * the kernel. + * + * We allocate an extra 256 bytes to allow for + * alignment and space for the original pointer saved + * just before the block. + */ + memory = kmalloc(size + 256, GFP_ATOMIC); + if (unlikely(memory == NULL)) { + pr_warning("Unable to allocate %u bytes for FPA pool %d\n", + elements * size, pool); + break; } + fpa = (char *)(((unsigned long)memory + 256) & ~0x7fUL); + *((char **)fpa - 1) = memory; + cvmx_fpa_free(fpa, pool, 0); + freed--; } return elements - freed; } /** - * Free memory previously allocated with cvm_oct_fill_hw_memory - * + * cvm_oct_free_hw_memory - Free memory allocated by cvm_oct_fill_hw_memory * @pool: FPA pool to free * @size: Size of each buffer in the pool * @elements: Number of buffers that should be in the pool */ static void cvm_oct_free_hw_memory(int pool, int size, int elements) { - if (USE_32BIT_SHARED) { - pr_warning("Warning: 32 shared memory is not freeable\n"); - } else { - char *memory; - do { - memory = cvmx_fpa_alloc(pool); - if (memory) { - elements--; - kfree(phys_to_virt(cvmx_ptr_to_phys(memory))); - } - } while (memory); + char *memory; + char *fpa; + do { + fpa = cvmx_fpa_alloc(pool); + if (fpa) { + elements--; + fpa = (char *)phys_to_virt(cvmx_ptr_to_phys(fpa)); + memory = *((char **)fpa - 1); + kfree(memory); + } + } while (fpa); - if (elements < 0) - pr_warning("Freeing of pool %u had too many " - "buffers (%d)\n", - pool, elements); - else if (elements > 0) - pr_warning("Warning: Freeing of pool %u is " - "missing %d buffers\n", - pool, elements); - } + if (elements < 0) + pr_warning("Freeing of pool %u had too many buffers (%d)\n", + pool, elements); + else if (elements > 0) + pr_warning("Warning: Freeing of pool %u is missing %d buffers\n", + pool, elements); } int cvm_oct_mem_fill_fpa(int pool, int size, int elements) { int freed; - if (USE_SKBUFFS_IN_HW) + if (USE_SKBUFFS_IN_HW && pool == CVMX_FPA_PACKET_POOL) freed = cvm_oct_fill_hw_skbuff(pool, size, elements); else freed = cvm_oct_fill_hw_memory(pool, size, elements); @@ -191,7 +169,7 @@ int cvm_oct_mem_fill_fpa(int pool, int size, int elements) void cvm_oct_mem_empty_fpa(int pool, int size, int elements) { - if (USE_SKBUFFS_IN_HW) + if (USE_SKBUFFS_IN_HW && pool == CVMX_FPA_PACKET_POOL) cvm_oct_free_hw_skbuff(pool, size, elements); else cvm_oct_free_hw_memory(pool, size, elements); diff --git a/drivers/staging/octeon/ethernet-proc.c b/drivers/staging/octeon/ethernet-proc.c deleted file mode 100644 index 16308d484d3b..000000000000 --- a/drivers/staging/octeon/ethernet-proc.c +++ /dev/null @@ -1,144 +0,0 @@ -/********************************************************************** - * Author: Cavium Networks - * - * Contact: support@caviumnetworks.com - * This file is part of the OCTEON SDK - * - * Copyright (c) 2003-2007 Cavium Networks - * - * This file 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 file is distributed in the hope that it will be useful, but - * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or - * NONINFRINGEMENT. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this file; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * or visit http://www.gnu.org/licenses/. - * - * This file may also be available under a different license from Cavium. - * Contact Cavium Networks for more information -**********************************************************************/ -#include <linux/kernel.h> -#include <linux/seq_file.h> -#include <linux/proc_fs.h> -#include <net/dst.h> - -#include <asm/octeon/octeon.h> - -#include "octeon-ethernet.h" -#include "ethernet-defines.h" - -#include "cvmx-helper.h" -#include "cvmx-pip.h" - -/** - * User is reading /proc/octeon_ethernet_stats - * - * @m: - * @v: - * Returns - */ -static int cvm_oct_stats_show(struct seq_file *m, void *v) -{ - struct octeon_ethernet *priv; - int port; - - for (port = 0; port < TOTAL_NUMBER_OF_PORTS; port++) { - - if (cvm_oct_device[port]) { - priv = netdev_priv(cvm_oct_device[port]); - - seq_printf(m, "\nOcteon Port %d (%s)\n", port, - cvm_oct_device[port]->name); - seq_printf(m, - "rx_packets: %12lu\t" - "tx_packets: %12lu\n", - priv->stats.rx_packets, - priv->stats.tx_packets); - seq_printf(m, - "rx_bytes: %12lu\t" - "tx_bytes: %12lu\n", - priv->stats.rx_bytes, priv->stats.tx_bytes); - seq_printf(m, - "rx_errors: %12lu\t" - "tx_errors: %12lu\n", - priv->stats.rx_errors, - priv->stats.tx_errors); - seq_printf(m, - "rx_dropped: %12lu\t" - "tx_dropped: %12lu\n", - priv->stats.rx_dropped, - priv->stats.tx_dropped); - seq_printf(m, - "rx_length_errors: %12lu\t" - "tx_aborted_errors: %12lu\n", - priv->stats.rx_length_errors, - priv->stats.tx_aborted_errors); - seq_printf(m, - "rx_over_errors: %12lu\t" - "tx_carrier_errors: %12lu\n", - priv->stats.rx_over_errors, - priv->stats.tx_carrier_errors); - seq_printf(m, - "rx_crc_errors: %12lu\t" - "tx_fifo_errors: %12lu\n", - priv->stats.rx_crc_errors, - priv->stats.tx_fifo_errors); - seq_printf(m, - "rx_frame_errors: %12lu\t" - "tx_heartbeat_errors: %12lu\n", - priv->stats.rx_frame_errors, - priv->stats.tx_heartbeat_errors); - seq_printf(m, - "rx_fifo_errors: %12lu\t" - "tx_window_errors: %12lu\n", - priv->stats.rx_fifo_errors, - priv->stats.tx_window_errors); - seq_printf(m, - "rx_missed_errors: %12lu\t" - "multicast: %12lu\n", - priv->stats.rx_missed_errors, - priv->stats.multicast); - } - } - - return 0; -} - -/** - * /proc/octeon_ethernet_stats was openned. Use the single_open iterator - * - * @inode: - * @file: - * Returns - */ -static int cvm_oct_stats_open(struct inode *inode, struct file *file) -{ - return single_open(file, cvm_oct_stats_show, NULL); -} - -static const struct file_operations cvm_oct_stats_operations = { - .open = cvm_oct_stats_open, - .read = seq_read, - .llseek = seq_lseek, - .release = single_release, -}; - -void cvm_oct_proc_initialize(void) -{ - struct proc_dir_entry *entry = - create_proc_entry("octeon_ethernet_stats", 0, NULL); - if (entry) - entry->proc_fops = &cvm_oct_stats_operations; -} - -void cvm_oct_proc_shutdown(void) -{ - remove_proc_entry("octeon_ethernet_stats", NULL); -} diff --git a/drivers/staging/octeon/ethernet-proc.h b/drivers/staging/octeon/ethernet-proc.h deleted file mode 100644 index 82c7d9f78bc4..000000000000 --- a/drivers/staging/octeon/ethernet-proc.h +++ /dev/null @@ -1,29 +0,0 @@ -/********************************************************************* - * Author: Cavium Networks - * - * Contact: support@caviumnetworks.com - * This file is part of the OCTEON SDK - * - * Copyright (c) 2003-2007 Cavium Networks - * - * This file 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 file is distributed in the hope that it will be useful, but - * AS-IS and WITHOUT ANY WARRANTY; without even the implied warranty - * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, TITLE, or - * NONINFRINGEMENT. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License - * along with this file; if not, write to the Free Software - * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA - * or visit http://www.gnu.org/licenses/. - * - * This file may also be available under a different license from Cavium. - * Contact Cavium Networks for more information -*********************************************************************/ - -void cvm_oct_proc_initialize(void); -void cvm_oct_proc_shutdown(void); diff --git a/drivers/staging/octeon/ethernet-rgmii.c b/drivers/staging/octeon/ethernet-rgmii.c index 3820f1ec11d1..a0d4d4b98bdc 100644 --- a/drivers/staging/octeon/ethernet-rgmii.c +++ b/drivers/staging/octeon/ethernet-rgmii.c @@ -26,7 +26,7 @@ **********************************************************************/ #include <linux/kernel.h> #include <linux/netdevice.h> -#include <linux/mii.h> +#include <linux/phy.h> #include <net/dst.h> #include <asm/octeon/octeon.h> @@ -48,14 +48,20 @@ static int number_rgmii_ports; static void cvm_oct_rgmii_poll(struct net_device *dev) { struct octeon_ethernet *priv = netdev_priv(dev); - unsigned long flags; + unsigned long flags = 0; cvmx_helper_link_info_t link_info; + int use_global_register_lock = (priv->phydev == NULL); - /* - * Take the global register lock since we are going to touch - * registers that affect more than one port. - */ - spin_lock_irqsave(&global_register_lock, flags); + BUG_ON(in_interrupt()); + if (use_global_register_lock) { + /* + * Take the global register lock since we are going to + * touch registers that affect more than one port. + */ + spin_lock_irqsave(&global_register_lock, flags); + } else { + mutex_lock(&priv->phydev->bus->mdio_lock); + } link_info = cvmx_helper_link_get(priv->port); if (link_info.u64 == priv->link_info) { @@ -115,7 +121,11 @@ static void cvm_oct_rgmii_poll(struct net_device *dev) dev->name); } } - spin_unlock_irqrestore(&global_register_lock, flags); + + if (use_global_register_lock) + spin_unlock_irqrestore(&global_register_lock, flags); + else + mutex_unlock(&priv->phydev->bus->mdio_lock); return; } @@ -151,7 +161,12 @@ static void cvm_oct_rgmii_poll(struct net_device *dev) link_info = cvmx_helper_link_autoconf(priv->port); priv->link_info = link_info.u64; } - spin_unlock_irqrestore(&global_register_lock, flags); + + if (use_global_register_lock) + spin_unlock_irqrestore(&global_register_lock, flags); + else { + mutex_unlock(&priv->phydev->bus->mdio_lock); + } if (priv->phydev == NULL) { /* Tell core. */ @@ -213,8 +228,11 @@ static irqreturn_t cvm_oct_rgmii_rml_interrupt(int cpl, void *dev_id) struct net_device *dev = cvm_oct_device[cvmx_helper_get_ipd_port (interface, index)]; - if (dev) - cvm_oct_rgmii_poll(dev); + struct octeon_ethernet *priv = netdev_priv(dev); + + if (dev && !atomic_read(&cvm_oct_poll_queue_stopping)) + queue_work(cvm_oct_poll_queue, &priv->port_work); + gmx_rx_int_reg.u64 = 0; gmx_rx_int_reg.s.phy_dupx = 1; gmx_rx_int_reg.s.phy_link = 1; @@ -252,8 +270,11 @@ static irqreturn_t cvm_oct_rgmii_rml_interrupt(int cpl, void *dev_id) struct net_device *dev = cvm_oct_device[cvmx_helper_get_ipd_port (interface, index)]; - if (dev) - cvm_oct_rgmii_poll(dev); + struct octeon_ethernet *priv = netdev_priv(dev); + + if (dev && !atomic_read(&cvm_oct_poll_queue_stopping)) + queue_work(cvm_oct_poll_queue, &priv->port_work); + gmx_rx_int_reg.u64 = 0; gmx_rx_int_reg.s.phy_dupx = 1; gmx_rx_int_reg.s.phy_link = 1; @@ -302,6 +323,12 @@ int cvm_oct_rgmii_stop(struct net_device *dev) return 0; } +static void cvm_oct_rgmii_immediate_poll(struct work_struct *work) +{ + struct octeon_ethernet *priv = container_of(work, struct octeon_ethernet, port_work); + cvm_oct_rgmii_poll(cvm_oct_device[priv->port]); +} + int cvm_oct_rgmii_init(struct net_device *dev) { struct octeon_ethernet *priv = netdev_priv(dev); @@ -309,7 +336,7 @@ int cvm_oct_rgmii_init(struct net_device *dev) cvm_oct_common_init(dev); dev->netdev_ops->ndo_stop(dev); - + INIT_WORK(&priv->port_work, cvm_oct_rgmii_immediate_poll); /* * Due to GMX errata in CN3XXX series chips, it is necessary * to take the link down immediately when the PHY changes @@ -397,4 +424,5 @@ void cvm_oct_rgmii_uninit(struct net_device *dev) number_rgmii_ports--; if (number_rgmii_ports == 0) free_irq(OCTEON_IRQ_RML, &number_rgmii_ports); + cancel_work_sync(&priv->port_work); } diff --git a/drivers/staging/octeon/ethernet-rx.c b/drivers/staging/octeon/ethernet-rx.c index 1b237b7e689d..cb38f9eb2cc0 100644 --- a/drivers/staging/octeon/ethernet-rx.c +++ b/drivers/staging/octeon/ethernet-rx.c @@ -4,7 +4,7 @@ * Contact: support@caviumnetworks.com * This file is part of the OCTEON SDK * - * Copyright (c) 2003-2007 Cavium Networks + * Copyright (c) 2003-2010 Cavium Networks * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 2, as @@ -27,16 +27,14 @@ #include <linux/module.h> #include <linux/kernel.h> #include <linux/cache.h> +#include <linux/cpumask.h> #include <linux/netdevice.h> #include <linux/init.h> #include <linux/etherdevice.h> #include <linux/ip.h> #include <linux/string.h> #include <linux/prefetch.h> -#include <linux/ethtool.h> -#include <linux/mii.h> -#include <linux/seq_file.h> -#include <linux/proc_fs.h> +#include <linux/smp.h> #include <net/dst.h> #ifdef CONFIG_XFRM #include <linux/xfrm.h> @@ -48,8 +46,9 @@ #include <asm/octeon/octeon.h> #include "ethernet-defines.h" -#include "octeon-ethernet.h" #include "ethernet-mem.h" +#include "ethernet-rx.h" +#include "octeon-ethernet.h" #include "ethernet-util.h" #include "cvmx-helper.h" @@ -61,62 +60,88 @@ #include "cvmx-gmxx-defs.h" -struct cvm_tasklet_wrapper { - struct tasklet_struct t; -}; +struct cvm_napi_wrapper { + struct napi_struct napi; +} ____cacheline_aligned_in_smp; -/* - * Aligning the tasklet_struct on cachline boundries seems to decrease - * throughput even though in theory it would reduce contantion on the - * cache lines containing the locks. - */ +static struct cvm_napi_wrapper cvm_oct_napi[NR_CPUS] __cacheline_aligned_in_smp; -static struct cvm_tasklet_wrapper cvm_oct_tasklet[NR_CPUS]; +struct cvm_oct_core_state { + int baseline_cores; + /* + * The number of additional cores that could be processing + * input packtes. + */ + atomic_t available_cores; + cpumask_t cpu_state; +} ____cacheline_aligned_in_smp; -/** - * Interrupt handler. The interrupt occurs whenever the POW - * transitions from 0->1 packets in our group. - * - * @cpl: - * @dev_id: - * @regs: - * Returns - */ -irqreturn_t cvm_oct_do_interrupt(int cpl, void *dev_id) +static struct cvm_oct_core_state core_state __cacheline_aligned_in_smp; + +static void cvm_oct_enable_napi(void *_) { - /* Acknowledge the interrupt */ - if (INTERRUPT_LIMIT) - cvmx_write_csr(CVMX_POW_WQ_INT, 1 << pow_receive_group); - else - cvmx_write_csr(CVMX_POW_WQ_INT, 0x10001 << pow_receive_group); - preempt_disable(); - tasklet_schedule(&cvm_oct_tasklet[smp_processor_id()].t); - preempt_enable(); - return IRQ_HANDLED; + int cpu = smp_processor_id(); + napi_schedule(&cvm_oct_napi[cpu].napi); +} + +static void cvm_oct_enable_one_cpu(void) +{ + int v; + int cpu; + + /* Check to see if more CPUs are available for receive processing... */ + v = atomic_sub_if_positive(1, &core_state.available_cores); + if (v < 0) + return; + + /* ... if a CPU is available, Turn on NAPI polling for that CPU. */ + for_each_online_cpu(cpu) { + if (!cpu_test_and_set(cpu, core_state.cpu_state)) { + v = smp_call_function_single(cpu, cvm_oct_enable_napi, + NULL, 0); + if (v) + panic("Can't enable NAPI."); + break; + } + } +} + +static void cvm_oct_no_more_work(void) +{ + int cpu = smp_processor_id(); + + /* + * CPU zero is special. It always has the irq enabled when + * waiting for incoming packets. + */ + if (cpu == 0) { + enable_irq(OCTEON_IRQ_WORKQ0 + pow_receive_group); + return; + } + + cpu_clear(cpu, core_state.cpu_state); + atomic_add(1, &core_state.available_cores); } -#ifdef CONFIG_NET_POLL_CONTROLLER /** - * This is called when the kernel needs to manually poll the - * device. For Octeon, this is simply calling the interrupt - * handler. We actually poll all the devices, not just the - * one supplied. + * cvm_oct_do_interrupt - interrupt handler. + * + * The interrupt occurs whenever the POW has packets in our group. * - * @dev: Device to poll. Unused */ -void cvm_oct_poll_controller(struct net_device *dev) +static irqreturn_t cvm_oct_do_interrupt(int cpl, void *dev_id) { - preempt_disable(); - tasklet_schedule(&cvm_oct_tasklet[smp_processor_id()].t); - preempt_enable(); + /* Disable the IRQ and start napi_poll. */ + disable_irq_nosync(OCTEON_IRQ_WORKQ0 + pow_receive_group); + cvm_oct_enable_napi(NULL); + + return IRQ_HANDLED; } -#endif /** - * This is called on receive errors, and determines if the packet - * can be dropped early-on in cvm_oct_tasklet_rx(). - * + * cvm_oct_check_rcv_error - process receive errors * @work: Work queue entry pointing to the packet. + * * Returns Non-zero if the packet can be dropped, zero otherwise. */ static inline int cvm_oct_check_rcv_error(cvmx_wqe_t *work) @@ -199,19 +224,20 @@ static inline int cvm_oct_check_rcv_error(cvmx_wqe_t *work) } /** - * Tasklet function that is scheduled on a core when an interrupt occurs. + * cvm_oct_napi_poll - the NAPI poll function. + * @napi: The NAPI instance, or null if called from cvm_oct_poll_controller + * @budget: Maximum number of packets to receive. * - * @unused: + * Returns the number of packets processed. */ -void cvm_oct_tasklet_rx(unsigned long unused) +static int cvm_oct_napi_poll(struct napi_struct *napi, int budget) { - const int coreid = cvmx_get_core_num(); - uint64_t old_group_mask; - uint64_t old_scratch; - int rx_count = 0; - int number_to_free; - int num_freed; - int packet_not_copied; + const int coreid = cvmx_get_core_num(); + uint64_t old_group_mask; + uint64_t old_scratch; + int rx_count = 0; + int did_work_request = 0; + int packet_not_copied; /* Prefetch cvm_oct_device since we know we need it soon */ prefetch(cvm_oct_device); @@ -227,59 +253,63 @@ void cvm_oct_tasklet_rx(unsigned long unused) cvmx_write_csr(CVMX_POW_PP_GRP_MSKX(coreid), (old_group_mask & ~0xFFFFull) | 1 << pow_receive_group); - if (USE_ASYNC_IOBDMA) + if (USE_ASYNC_IOBDMA) { cvmx_pow_work_request_async(CVMX_SCR_SCRATCH, CVMX_POW_NO_WAIT); + did_work_request = 1; + } - while (1) { + while (rx_count < budget) { struct sk_buff *skb = NULL; + struct sk_buff **pskb = NULL; int skb_in_hw; cvmx_wqe_t *work; - if (USE_ASYNC_IOBDMA) { + if (USE_ASYNC_IOBDMA && did_work_request) work = cvmx_pow_work_response_async(CVMX_SCR_SCRATCH); - } else { - if ((INTERRUPT_LIMIT == 0) - || likely(rx_count < MAX_RX_PACKETS)) - work = - cvmx_pow_work_request_sync - (CVMX_POW_NO_WAIT); - else - work = NULL; - } + else + work = cvmx_pow_work_request_sync(CVMX_POW_NO_WAIT); + prefetch(work); - if (work == NULL) + did_work_request = 0; + if (work == NULL) { + union cvmx_pow_wq_int wq_int; + wq_int.u64 = 0; + wq_int.s.iq_dis = 1 << pow_receive_group; + wq_int.s.wq_int = 1 << pow_receive_group; + cvmx_write_csr(CVMX_POW_WQ_INT, wq_int.u64); break; + } + pskb = (struct sk_buff **)(cvm_oct_get_buffer_ptr(work->packet_ptr) - sizeof(void *)); + prefetch(pskb); - /* - * Limit each core to processing MAX_RX_PACKETS - * packets without a break. This way the RX can't - * starve the TX task. - */ - if (USE_ASYNC_IOBDMA) { - - if ((INTERRUPT_LIMIT == 0) - || likely(rx_count < MAX_RX_PACKETS)) - cvmx_pow_work_request_async_nocheck - (CVMX_SCR_SCRATCH, CVMX_POW_NO_WAIT); - else { - cvmx_scratch_write64(CVMX_SCR_SCRATCH, - 0x8000000000000000ull); - cvmx_pow_tag_sw_null_nocheck(); - } + if (USE_ASYNC_IOBDMA && rx_count < (budget - 1)) { + cvmx_pow_work_request_async_nocheck(CVMX_SCR_SCRATCH, CVMX_POW_NO_WAIT); + did_work_request = 1; + } + + if (rx_count == 0) { + /* + * First time through, see if there is enough + * work waiting to merit waking another + * CPU. + */ + union cvmx_pow_wq_int_cntx counts; + int backlog; + int cores_in_use = core_state.baseline_cores - atomic_read(&core_state.available_cores); + counts.u64 = cvmx_read_csr(CVMX_POW_WQ_INT_CNTX(pow_receive_group)); + backlog = counts.s.iq_cnt + counts.s.ds_cnt; + if (backlog > budget * cores_in_use && napi != NULL) + cvm_oct_enable_one_cpu(); } skb_in_hw = USE_SKBUFFS_IN_HW && work->word2.s.bufs == 1; if (likely(skb_in_hw)) { - skb = - *(struct sk_buff - **)(cvm_oct_get_buffer_ptr(work->packet_ptr) - - sizeof(void *)); + skb = *pskb; prefetch(&skb->head); prefetch(&skb->len); } prefetch(cvm_oct_device[work->ipprt]); - rx_count++; /* Immediately throw away all packets with receive errors */ if (unlikely(work->word2.snoip.rcv_error)) { if (cvm_oct_check_rcv_error(work)) @@ -292,39 +322,27 @@ void cvm_oct_tasklet_rx(unsigned long unused) * buffer. */ if (likely(skb_in_hw)) { - /* - * This calculation was changed in case the - * skb header is using a different address - * aliasing type than the buffer. It doesn't - * make any differnece now, but the new one is - * more correct. - */ - skb->data = - skb->head + work->packet_ptr.s.addr - - cvmx_ptr_to_phys(skb->head); + skb->data = skb->head + work->packet_ptr.s.addr - cvmx_ptr_to_phys(skb->head); prefetch(skb->data); skb->len = work->len; skb_set_tail_pointer(skb, skb->len); packet_not_copied = 1; } else { - /* * We have to copy the packet. First allocate * an skbuff for it. */ skb = dev_alloc_skb(work->len); if (!skb) { - DEBUGPRINT("Port %d failed to allocate " - "skbuff, packet dropped\n", - work->ipprt); + DEBUGPRINT("Port %d failed to allocate skbuff, packet dropped\n", + work->ipprt); cvm_oct_free_work(work); continue; } /* * Check if we've received a packet that was - * entirely stored in the work entry. This is - * untested. + * entirely stored in the work entry. */ if (unlikely(work->word2.s.bufs == 0)) { uint8_t *ptr = work->packet_data; @@ -343,15 +361,13 @@ void cvm_oct_tasklet_rx(unsigned long unused) /* No packet buffers to free */ } else { int segments = work->word2.s.bufs; - union cvmx_buf_ptr segment_ptr = - work->packet_ptr; + union cvmx_buf_ptr segment_ptr = work->packet_ptr; int len = work->len; while (segments--) { union cvmx_buf_ptr next_ptr = - *(union cvmx_buf_ptr *) - cvmx_phys_to_ptr(segment_ptr.s. - addr - 8); + *(union cvmx_buf_ptr *)cvmx_phys_to_ptr(segment_ptr.s.addr - 8); + /* * Octeon Errata PKI-100: The segment size is * wrong. Until it is fixed, calculate the @@ -361,22 +377,18 @@ void cvm_oct_tasklet_rx(unsigned long unused) * one: int segment_size = * segment_ptr.s.size; */ - int segment_size = - CVMX_FPA_PACKET_POOL_SIZE - - (segment_ptr.s.addr - - (((segment_ptr.s.addr >> 7) - - segment_ptr.s.back) << 7)); - /* Don't copy more than what is left - in the packet */ + int segment_size = CVMX_FPA_PACKET_POOL_SIZE - + (segment_ptr.s.addr - (((segment_ptr.s.addr >> 7) - segment_ptr.s.back) << 7)); + /* + * Don't copy more than what + * is left in the packet. + */ if (segment_size > len) segment_size = len; /* Copy the data into the packet */ memcpy(skb_put(skb, segment_size), - cvmx_phys_to_ptr(segment_ptr.s. - addr), + cvmx_phys_to_ptr(segment_ptr.s.addr), segment_size); - /* Reduce the amount of bytes left - to copy */ len -= segment_size; segment_ptr = next_ptr; } @@ -389,16 +401,15 @@ void cvm_oct_tasklet_rx(unsigned long unused) struct net_device *dev = cvm_oct_device[work->ipprt]; struct octeon_ethernet *priv = netdev_priv(dev); - /* Only accept packets for devices - that are currently up */ + /* + * Only accept packets for devices that are + * currently up. + */ if (likely(dev->flags & IFF_UP)) { skb->protocol = eth_type_trans(skb, dev); skb->dev = dev; - if (unlikely - (work->word2.s.not_IP - || work->word2.s.IP_exc - || work->word2.s.L4_error)) + if (unlikely(work->word2.s.not_IP || work->word2.s.IP_exc || work->word2.s.L4_error)) skb->ip_summed = CHECKSUM_NONE; else skb->ip_summed = CHECKSUM_UNNECESSARY; @@ -414,15 +425,13 @@ void cvm_oct_tasklet_rx(unsigned long unused) #endif } netif_receive_skb(skb); + rx_count++; } else { + /* Drop any packet received for a device that isn't up */ /* - * Drop any packet received for a - * device that isn't up. - */ - /* - DEBUGPRINT("%s: Device not up, packet dropped\n", - dev->name); - */ + DEBUGPRINT("%s: Device not up, packet dropped\n", + dev->name); + */ #ifdef CONFIG_64BIT atomic64_add(1, (atomic64_t *)&priv->stats.rx_dropped); #else @@ -435,9 +444,8 @@ void cvm_oct_tasklet_rx(unsigned long unused) * Drop any packet received for a device that * doesn't exist. */ - DEBUGPRINT("Port %d not controlled by Linux, packet " - "dropped\n", - work->ipprt); + DEBUGPRINT("Port %d not controlled by Linux, packet dropped\n", + work->ipprt); dev_kfree_skb_irq(skb); } /* @@ -459,47 +467,93 @@ void cvm_oct_tasklet_rx(unsigned long unused) cvm_oct_free_work(work); } } - /* Restore the original POW group mask */ cvmx_write_csr(CVMX_POW_PP_GRP_MSKX(coreid), old_group_mask); if (USE_ASYNC_IOBDMA) { /* Restore the scratch area */ cvmx_scratch_write64(CVMX_SCR_SCRATCH, old_scratch); } + cvm_oct_rx_refill_pool(0); - if (USE_SKBUFFS_IN_HW) { - /* Refill the packet buffer pool */ - number_to_free = - cvmx_fau_fetch_and_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); - - if (number_to_free > 0) { - cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, - -number_to_free); - num_freed = - cvm_oct_mem_fill_fpa(CVMX_FPA_PACKET_POOL, - CVMX_FPA_PACKET_POOL_SIZE, - number_to_free); - if (num_freed != number_to_free) { - cvmx_fau_atomic_add32 - (FAU_NUM_PACKET_BUFFERS_TO_FREE, - number_to_free - num_freed); - } - } + if (rx_count < budget && napi != NULL) { + /* No more work */ + napi_complete(napi); + cvm_oct_no_more_work(); } + return rx_count; } +#ifdef CONFIG_NET_POLL_CONTROLLER +/** + * cvm_oct_poll_controller - poll for receive packets + * device. + * + * @dev: Device to poll. Unused + */ +void cvm_oct_poll_controller(struct net_device *dev) +{ + cvm_oct_napi_poll(NULL, 16); +} +#endif + void cvm_oct_rx_initialize(void) { int i; - /* Initialize all of the tasklets */ - for (i = 0; i < NR_CPUS; i++) - tasklet_init(&cvm_oct_tasklet[i].t, cvm_oct_tasklet_rx, 0); + struct net_device *dev_for_napi = NULL; + union cvmx_pow_wq_int_thrx int_thr; + union cvmx_pow_wq_int_pc int_pc; + + for (i = 0; i < TOTAL_NUMBER_OF_PORTS; i++) { + if (cvm_oct_device[i]) { + dev_for_napi = cvm_oct_device[i]; + break; + } + } + + if (NULL == dev_for_napi) + panic("No net_devices were allocated."); + + if (max_rx_cpus > 1 && max_rx_cpus < num_online_cpus()) + atomic_set(&core_state.available_cores, max_rx_cpus); + else + atomic_set(&core_state.available_cores, num_online_cpus()); + core_state.baseline_cores = atomic_read(&core_state.available_cores); + + core_state.cpu_state = CPU_MASK_NONE; + for_each_possible_cpu(i) { + netif_napi_add(dev_for_napi, &cvm_oct_napi[i].napi, + cvm_oct_napi_poll, rx_napi_weight); + napi_enable(&cvm_oct_napi[i].napi); + } + /* Register an IRQ hander for to receive POW interrupts */ + i = request_irq(OCTEON_IRQ_WORKQ0 + pow_receive_group, + cvm_oct_do_interrupt, 0, "Ethernet", cvm_oct_device); + + if (i) + panic("Could not acquire Ethernet IRQ %d\n", + OCTEON_IRQ_WORKQ0 + pow_receive_group); + + disable_irq_nosync(OCTEON_IRQ_WORKQ0 + pow_receive_group); + + int_thr.u64 = 0; + int_thr.s.tc_en = 1; + int_thr.s.tc_thr = 1; + /* Enable POW interrupt when our port has at least one packet */ + cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), int_thr.u64); + + int_pc.u64 = 0; + int_pc.s.pc_thr = 5; + cvmx_write_csr(CVMX_POW_WQ_INT_PC, int_pc.u64); + + + /* Scheduld NAPI now. This will indirectly enable interrupts. */ + cvm_oct_enable_one_cpu(); } void cvm_oct_rx_shutdown(void) { int i; - /* Shutdown all of the tasklets */ - for (i = 0; i < NR_CPUS; i++) - tasklet_kill(&cvm_oct_tasklet[i].t); + /* Shutdown all of the NAPIs */ + for_each_possible_cpu(i) + netif_napi_del(&cvm_oct_napi[i].napi); } diff --git a/drivers/staging/octeon/ethernet-rx.h b/drivers/staging/octeon/ethernet-rx.h index a9b72b87a7a6..a0743b85d54e 100644 --- a/drivers/staging/octeon/ethernet-rx.h +++ b/drivers/staging/octeon/ethernet-rx.h @@ -24,10 +24,29 @@ * This file may also be available under a different license from Cavium. * Contact Cavium Networks for more information *********************************************************************/ +#include "cvmx-fau.h" -irqreturn_t cvm_oct_do_interrupt(int cpl, void *dev_id); void cvm_oct_poll_controller(struct net_device *dev); -void cvm_oct_tasklet_rx(unsigned long unused); - void cvm_oct_rx_initialize(void); void cvm_oct_rx_shutdown(void); + +static inline void cvm_oct_rx_refill_pool(int fill_threshold) +{ + int number_to_free; + int num_freed; + /* Refill the packet buffer pool */ + number_to_free = + cvmx_fau_fetch_and_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); + + if (number_to_free > fill_threshold) { + cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, + -number_to_free); + num_freed = cvm_oct_mem_fill_fpa(CVMX_FPA_PACKET_POOL, + CVMX_FPA_PACKET_POOL_SIZE, + number_to_free); + if (num_freed != number_to_free) { + cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, + number_to_free - num_freed); + } + } +} diff --git a/drivers/staging/octeon/ethernet-sgmii.c b/drivers/staging/octeon/ethernet-sgmii.c index 6061d01eca2d..2d8589eb461e 100644 --- a/drivers/staging/octeon/ethernet-sgmii.c +++ b/drivers/staging/octeon/ethernet-sgmii.c @@ -26,7 +26,6 @@ **********************************************************************/ #include <linux/kernel.h> #include <linux/netdevice.h> -#include <linux/mii.h> #include <net/dst.h> #include <asm/octeon/octeon.h> diff --git a/drivers/staging/octeon/ethernet-spi.c b/drivers/staging/octeon/ethernet-spi.c index 00dc0f4bad19..b58b8971f939 100644 --- a/drivers/staging/octeon/ethernet-spi.c +++ b/drivers/staging/octeon/ethernet-spi.c @@ -26,7 +26,6 @@ **********************************************************************/ #include <linux/kernel.h> #include <linux/netdevice.h> -#include <linux/mii.h> #include <net/dst.h> #include <asm/octeon/octeon.h> diff --git a/drivers/staging/octeon/ethernet-tx.c b/drivers/staging/octeon/ethernet-tx.c index 535294105f65..afc2b734d554 100644 --- a/drivers/staging/octeon/ethernet-tx.c +++ b/drivers/staging/octeon/ethernet-tx.c @@ -4,7 +4,7 @@ * Contact: support@caviumnetworks.com * This file is part of the OCTEON SDK * - * Copyright (c) 2003-2007 Cavium Networks + * Copyright (c) 2003-2010 Cavium Networks * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 2, as @@ -31,10 +31,6 @@ #include <linux/etherdevice.h> #include <linux/ip.h> #include <linux/string.h> -#include <linux/ethtool.h> -#include <linux/mii.h> -#include <linux/seq_file.h> -#include <linux/proc_fs.h> #include <net/dst.h> #ifdef CONFIG_XFRM #include <linux/xfrm.h> @@ -52,11 +48,14 @@ #include "cvmx-wqe.h" #include "cvmx-fau.h" +#include "cvmx-pip.h" #include "cvmx-pko.h" #include "cvmx-helper.h" #include "cvmx-gmxx-defs.h" +#define CVM_OCT_SKB_CB(skb) ((u64 *)((skb)->cb)) + /* * You can define GET_SKBUFF_QOS() to override how the skbuff output * function determines which output queue is used. The default @@ -68,12 +67,81 @@ #define GET_SKBUFF_QOS(skb) 0 #endif +static void cvm_oct_tx_do_cleanup(unsigned long arg); +static DECLARE_TASKLET(cvm_oct_tx_cleanup_tasklet, cvm_oct_tx_do_cleanup, 0); + +/* Maximum number of SKBs to try to free per xmit packet. */ +#define MAX_SKB_TO_FREE (MAX_OUT_QUEUE_DEPTH * 2) + +static inline int32_t cvm_oct_adjust_skb_to_free(int32_t skb_to_free, int fau) +{ + int32_t undo; + undo = skb_to_free > 0 ? MAX_SKB_TO_FREE : skb_to_free + MAX_SKB_TO_FREE; + if (undo > 0) + cvmx_fau_atomic_add32(fau, -undo); + skb_to_free = -skb_to_free > MAX_SKB_TO_FREE ? MAX_SKB_TO_FREE : -skb_to_free; + return skb_to_free; +} + +static void cvm_oct_kick_tx_poll_watchdog(void) +{ + union cvmx_ciu_timx ciu_timx; + ciu_timx.u64 = 0; + ciu_timx.s.one_shot = 1; + ciu_timx.s.len = cvm_oct_tx_poll_interval; + cvmx_write_csr(CVMX_CIU_TIMX(1), ciu_timx.u64); +} + +void cvm_oct_free_tx_skbs(struct net_device *dev) +{ + int32_t skb_to_free; + int qos, queues_per_port; + int total_freed = 0; + int total_remaining = 0; + unsigned long flags; + struct octeon_ethernet *priv = netdev_priv(dev); + + queues_per_port = cvmx_pko_get_num_queues(priv->port); + /* Drain any pending packets in the free list */ + for (qos = 0; qos < queues_per_port; qos++) { + if (skb_queue_len(&priv->tx_free_list[qos]) == 0) + continue; + skb_to_free = cvmx_fau_fetch_and_add32(priv->fau+qos*4, MAX_SKB_TO_FREE); + skb_to_free = cvm_oct_adjust_skb_to_free(skb_to_free, priv->fau+qos*4); + + + total_freed += skb_to_free; + if (skb_to_free > 0) { + struct sk_buff *to_free_list = NULL; + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + while (skb_to_free > 0) { + struct sk_buff *t = __skb_dequeue(&priv->tx_free_list[qos]); + t->next = to_free_list; + to_free_list = t; + skb_to_free--; + } + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); + /* Do the actual freeing outside of the lock. */ + while (to_free_list) { + struct sk_buff *t = to_free_list; + to_free_list = to_free_list->next; + dev_kfree_skb_any(t); + } + } + total_remaining += skb_queue_len(&priv->tx_free_list[qos]); + } + if (total_freed >= 0 && netif_queue_stopped(dev)) + netif_wake_queue(dev); + if (total_remaining) + cvm_oct_kick_tx_poll_watchdog(); +} + /** - * Packet transmit - * + * cvm_oct_xmit - transmit a packet * @skb: Packet to send * @dev: Device info structure - * Returns Always returns zero + * + * Returns Always returns NETDEV_TX_OK */ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) { @@ -81,13 +149,15 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) union cvmx_buf_ptr hw_buffer; uint64_t old_scratch; uint64_t old_scratch2; - int dropped; int qos; - int queue_it_up; + int i; + enum {QUEUE_CORE, QUEUE_HW, QUEUE_DROP} queue_type; struct octeon_ethernet *priv = netdev_priv(dev); + struct sk_buff *to_free_list; int32_t skb_to_free; - int32_t undo; int32_t buffers_to_free; + u32 total_to_clean; + unsigned long flags; #if REUSE_SKBUFFS_WITHOUT_FREE unsigned char *fpa_head; #endif @@ -98,9 +168,6 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) */ prefetch(priv); - /* Start off assuming no drop */ - dropped = 0; - /* * The check on CVMX_PKO_QUEUES_PER_PORT_* is designed to * completely remove "qos" in the event neither interface @@ -135,6 +202,28 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) } /* + * We have space for 6 segment pointers, If there will be more + * than that, we must linearize. + */ + if (unlikely(skb_shinfo(skb)->nr_frags > 5)) { + if (unlikely(__skb_linearize(skb))) { + queue_type = QUEUE_DROP; + if (USE_ASYNC_IOBDMA) { + /* Get the number of skbuffs in use by the hardware */ + CVMX_SYNCIOBDMA; + skb_to_free = cvmx_scratch_read64(CVMX_SCR_SCRATCH); + } else { + /* Get the number of skbuffs in use by the hardware */ + skb_to_free = cvmx_fau_fetch_and_add32(priv->fau + qos * 4, + MAX_SKB_TO_FREE); + } + skb_to_free = cvm_oct_adjust_skb_to_free(skb_to_free, priv->fau + qos * 4); + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + goto skip_xmit; + } + } + + /* * The CN3XXX series of parts has an errata (GMX-401) which * causes the GMX block to hang if a collision occurs towards * the end of a <68 byte packet. As a workaround for this, we @@ -162,13 +251,6 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) } } - /* Build the PKO buffer pointer */ - hw_buffer.u64 = 0; - hw_buffer.s.addr = cvmx_ptr_to_phys(skb->data); - hw_buffer.s.pool = 0; - hw_buffer.s.size = - (unsigned long)skb_end_pointer(skb) - (unsigned long)skb->head; - /* Build the PKO command */ pko_command.u64 = 0; pko_command.s.n2 = 1; /* Don't pollute L2 with the outgoing packet */ @@ -178,7 +260,31 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) pko_command.s.subone0 = 1; pko_command.s.dontfree = 1; - pko_command.s.reg0 = priv->fau + qos * 4; + + /* Build the PKO buffer pointer */ + hw_buffer.u64 = 0; + if (skb_shinfo(skb)->nr_frags == 0) { + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)skb->data); + hw_buffer.s.pool = 0; + hw_buffer.s.size = skb->len; + } else { + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)skb->data); + hw_buffer.s.pool = 0; + hw_buffer.s.size = skb_headlen(skb); + CVM_OCT_SKB_CB(skb)[0] = hw_buffer.u64; + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + struct skb_frag_struct *fs = skb_shinfo(skb)->frags + i; + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)(page_address(fs->page) + fs->page_offset)); + hw_buffer.s.size = fs->size; + CVM_OCT_SKB_CB(skb)[i + 1] = hw_buffer.u64; + } + hw_buffer.s.addr = XKPHYS_TO_PHYS((u64)CVM_OCT_SKB_CB(skb)); + hw_buffer.s.size = skb_shinfo(skb)->nr_frags + 1; + pko_command.s.segs = skb_shinfo(skb)->nr_frags + 1; + pko_command.s.gather = 1; + goto dont_put_skbuff_in_hw; + } + /* * See if we can put this skb in the FPA pool. Any strange * behavior from the Linux networking stack will most likely @@ -190,7 +296,7 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) * shown a 25% increase in performance under some loads. */ #if REUSE_SKBUFFS_WITHOUT_FREE - fpa_head = skb->head + 128 - ((unsigned long)skb->head & 0x7f); + fpa_head = skb->head + 256 - ((unsigned long)skb->head & 0x7f); if (unlikely(skb->data < fpa_head)) { /* * printk("TX buffer beginning can't meet FPA @@ -248,10 +354,9 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) * We can use this buffer in the FPA. We don't need the FAU * update anymore */ - pko_command.s.reg0 = 0; pko_command.s.dontfree = 0; - hw_buffer.s.back = (skb->data - fpa_head) >> 7; + hw_buffer.s.back = ((unsigned long)skb->data >> 7) - ((unsigned long)fpa_head >> 7); *(struct sk_buff **)(fpa_head - sizeof(void *)) = skb; /* @@ -272,16 +377,16 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev) skb->tc_verd = 0; #endif /* CONFIG_NET_CLS_ACT */ #endif /* CONFIG_NET_SCHED */ +#endif /* REUSE_SKBUFFS_WITHOUT_FREE */ dont_put_skbuff_in_hw: -#endif /* REUSE_SKBUFFS_WITHOUT_FREE */ /* Check if we can use the hardware checksumming */ if (USE_HW_TCPUDP_CHECKSUM && (skb->protocol == htons(ETH_P_IP)) && (ip_hdr(skb)->version == 4) && (ip_hdr(skb)->ihl == 5) && ((ip_hdr(skb)->frag_off == 0) || (ip_hdr(skb)->frag_off == 1 << 14)) - && ((ip_hdr(skb)->protocol == IP_PROTOCOL_TCP) - || (ip_hdr(skb)->protocol == IP_PROTOCOL_UDP))) { + && ((ip_hdr(skb)->protocol == IPPROTO_TCP) + || (ip_hdr(skb)->protocol == IPPROTO_UDP))) { /* Use hardware checksum calc */ pko_command.s.ipoffp1 = sizeof(struct ethhdr) + 1; } @@ -299,89 +404,116 @@ dont_put_skbuff_in_hw: cvmx_fau_fetch_and_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); } - /* - * We try to claim MAX_SKB_TO_FREE buffers. If there were not - * that many available, we have to un-claim (undo) any that - * were in excess. If skb_to_free is positive we will free - * that many buffers. - */ - undo = skb_to_free > 0 ? - MAX_SKB_TO_FREE : skb_to_free + MAX_SKB_TO_FREE; - if (undo > 0) - cvmx_fau_atomic_add32(priv->fau+qos*4, -undo); - skb_to_free = -skb_to_free > MAX_SKB_TO_FREE ? - MAX_SKB_TO_FREE : -skb_to_free; + skb_to_free = cvm_oct_adjust_skb_to_free(skb_to_free, priv->fau+qos*4); /* * If we're sending faster than the receive can free them then * don't do the HW free. */ - if ((buffers_to_free < -100) && !pko_command.s.dontfree) { + if ((buffers_to_free < -100) && !pko_command.s.dontfree) pko_command.s.dontfree = 1; - pko_command.s.reg0 = priv->fau + qos * 4; + + if (pko_command.s.dontfree) { + queue_type = QUEUE_CORE; + pko_command.s.reg0 = priv->fau+qos*4; + } else { + queue_type = QUEUE_HW; } + if (USE_ASYNC_IOBDMA) + cvmx_fau_async_fetch_and_add32(CVMX_SCR_SCRATCH, FAU_TOTAL_TX_TO_CLEAN, 1); - cvmx_pko_send_packet_prepare(priv->port, priv->queue + qos, - CVMX_PKO_LOCK_CMD_QUEUE); + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); /* Drop this packet if we have too many already queued to the HW */ - if (unlikely - (skb_queue_len(&priv->tx_free_list[qos]) >= MAX_OUT_QUEUE_DEPTH)) { - /* - DEBUGPRINT("%s: Tx dropped. Too many queued\n", dev->name); - */ - dropped = 1; + if (unlikely(skb_queue_len(&priv->tx_free_list[qos]) >= MAX_OUT_QUEUE_DEPTH)) { + if (dev->tx_queue_len != 0) { + /* Drop the lock when notifying the core. */ + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); + netif_stop_queue(dev); + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + } else { + /* If not using normal queueing. */ + queue_type = QUEUE_DROP; + goto skip_xmit; + } } + + cvmx_pko_send_packet_prepare(priv->port, priv->queue + qos, + CVMX_PKO_LOCK_NONE); + /* Send the packet to the output queue */ - else if (unlikely - (cvmx_pko_send_packet_finish - (priv->port, priv->queue + qos, pko_command, hw_buffer, - CVMX_PKO_LOCK_CMD_QUEUE))) { + if (unlikely(cvmx_pko_send_packet_finish(priv->port, + priv->queue + qos, + pko_command, hw_buffer, + CVMX_PKO_LOCK_NONE))) { DEBUGPRINT("%s: Failed to send the packet\n", dev->name); - dropped = 1; + queue_type = QUEUE_DROP; + } +skip_xmit: + to_free_list = NULL; + + switch (queue_type) { + case QUEUE_DROP: + skb->next = to_free_list; + to_free_list = skb; + priv->stats.tx_dropped++; + break; + case QUEUE_HW: + cvmx_fau_atomic_add32(FAU_NUM_PACKET_BUFFERS_TO_FREE, -1); + break; + case QUEUE_CORE: + __skb_queue_tail(&priv->tx_free_list[qos], skb); + break; + default: + BUG(); + } + + while (skb_to_free > 0) { + struct sk_buff *t = __skb_dequeue(&priv->tx_free_list[qos]); + t->next = to_free_list; + to_free_list = t; + skb_to_free--; + } + + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); + + /* Do the actual freeing outside of the lock. */ + while (to_free_list) { + struct sk_buff *t = to_free_list; + to_free_list = to_free_list->next; + dev_kfree_skb_any(t); } if (USE_ASYNC_IOBDMA) { + CVMX_SYNCIOBDMA; + total_to_clean = cvmx_scratch_read64(CVMX_SCR_SCRATCH); /* Restore the scratch area */ cvmx_scratch_write64(CVMX_SCR_SCRATCH, old_scratch); cvmx_scratch_write64(CVMX_SCR_SCRATCH + 8, old_scratch2); - } - - queue_it_up = 0; - if (unlikely(dropped)) { - dev_kfree_skb_any(skb); - priv->stats.tx_dropped++; } else { - if (USE_SKBUFFS_IN_HW) { - /* Put this packet on the queue to be freed later */ - if (pko_command.s.dontfree) - queue_it_up = 1; - else - cvmx_fau_atomic_add32 - (FAU_NUM_PACKET_BUFFERS_TO_FREE, -1); - } else { - /* Put this packet on the queue to be freed later */ - queue_it_up = 1; - } + total_to_clean = cvmx_fau_fetch_and_add32(FAU_TOTAL_TX_TO_CLEAN, 1); } - if (queue_it_up) { - spin_lock(&priv->tx_free_list[qos].lock); - __skb_queue_tail(&priv->tx_free_list[qos], skb); - cvm_oct_free_tx_skbs(priv, skb_to_free, qos, 0); - spin_unlock(&priv->tx_free_list[qos].lock); - } else { - cvm_oct_free_tx_skbs(priv, skb_to_free, qos, 1); + if (total_to_clean & 0x3ff) { + /* + * Schedule the cleanup tasklet every 1024 packets for + * the pathological case of high traffic on one port + * delaying clean up of packets on a different port + * that is blocked waiting for the cleanup. + */ + tasklet_schedule(&cvm_oct_tx_cleanup_tasklet); } - return 0; + cvm_oct_kick_tx_poll_watchdog(); + + return NETDEV_TX_OK; } /** - * Packet transmit to the POW - * + * cvm_oct_xmit_pow - transmit a packet to the POW * @skb: Packet to send * @dev: Device info structure + * Returns Always returns zero */ int cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev) @@ -459,8 +591,8 @@ int cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev) work->word2.s.dec_ipcomp = 0; /* FIXME */ #endif work->word2.s.tcp_or_udp = - (ip_hdr(skb)->protocol == IP_PROTOCOL_TCP) - || (ip_hdr(skb)->protocol == IP_PROTOCOL_UDP); + (ip_hdr(skb)->protocol == IPPROTO_TCP) + || (ip_hdr(skb)->protocol == IPPROTO_UDP); #if 0 /* FIXME */ work->word2.s.dec_ipsec = 0; @@ -529,116 +661,63 @@ int cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev) } /** - * Transmit a work queue entry out of the ethernet port. Both - * the work queue entry and the packet data can optionally be - * freed. The work will be freed on error as well. - * - * @dev: Device to transmit out. - * @work_queue_entry: - * Work queue entry to send - * @do_free: True if the work queue entry and packet data should be - * freed. If false, neither will be freed. - * @qos: Index into the queues for this port to transmit on. This - * is used to implement QoS if their are multiple queues per - * port. This parameter must be between 0 and the number of - * queues per port minus 1. Values outside of this range will - * be change to zero. + * cvm_oct_tx_shutdown_dev - free all skb that are currently queued for TX. + * @dev: Device being shutdown * - * Returns Zero on success, negative on failure. */ -int cvm_oct_transmit_qos(struct net_device *dev, void *work_queue_entry, - int do_free, int qos) +void cvm_oct_tx_shutdown_dev(struct net_device *dev) { - unsigned long flags; - union cvmx_buf_ptr hw_buffer; - cvmx_pko_command_word0_t pko_command; - int dropped; struct octeon_ethernet *priv = netdev_priv(dev); - cvmx_wqe_t *work = work_queue_entry; + unsigned long flags; + int qos; - if (!(dev->flags & IFF_UP)) { - DEBUGPRINT("%s: Device not up\n", dev->name); - if (do_free) - cvm_oct_free_work(work); - return -1; + for (qos = 0; qos < 16; qos++) { + spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); + while (skb_queue_len(&priv->tx_free_list[qos])) + dev_kfree_skb_any(__skb_dequeue + (&priv->tx_free_list[qos])); + spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); } +} - /* The check on CVMX_PKO_QUEUES_PER_PORT_* is designed to completely - remove "qos" in the event neither interface supports - multiple queues per port */ - if ((CVMX_PKO_QUEUES_PER_PORT_INTERFACE0 > 1) || - (CVMX_PKO_QUEUES_PER_PORT_INTERFACE1 > 1)) { - if (qos <= 0) - qos = 0; - else if (qos >= cvmx_pko_get_num_queues(priv->port)) - qos = 0; - } else - qos = 0; - - /* Start off assuming no drop */ - dropped = 0; - - local_irq_save(flags); - cvmx_pko_send_packet_prepare(priv->port, priv->queue + qos, - CVMX_PKO_LOCK_CMD_QUEUE); - - /* Build the PKO buffer pointer */ - hw_buffer.u64 = 0; - hw_buffer.s.addr = work->packet_ptr.s.addr; - hw_buffer.s.pool = CVMX_FPA_PACKET_POOL; - hw_buffer.s.size = CVMX_FPA_PACKET_POOL_SIZE; - hw_buffer.s.back = work->packet_ptr.s.back; +static void cvm_oct_tx_do_cleanup(unsigned long arg) +{ + int port; - /* Build the PKO command */ - pko_command.u64 = 0; - pko_command.s.n2 = 1; /* Don't pollute L2 with the outgoing packet */ - pko_command.s.dontfree = !do_free; - pko_command.s.segs = work->word2.s.bufs; - pko_command.s.total_bytes = work->len; + for (port = 0; port < TOTAL_NUMBER_OF_PORTS; port++) { + if (cvm_oct_device[port]) { + struct net_device *dev = cvm_oct_device[port]; + cvm_oct_free_tx_skbs(dev); + } + } +} - /* Check if we can use the hardware checksumming */ - if (unlikely(work->word2.s.not_IP || work->word2.s.IP_exc)) - pko_command.s.ipoffp1 = 0; - else - pko_command.s.ipoffp1 = sizeof(struct ethhdr) + 1; +static irqreturn_t cvm_oct_tx_cleanup_watchdog(int cpl, void *dev_id) +{ + /* Disable the interrupt. */ + cvmx_write_csr(CVMX_CIU_TIMX(1), 0); + /* Do the work in the tasklet. */ + tasklet_schedule(&cvm_oct_tx_cleanup_tasklet); + return IRQ_HANDLED; +} - /* Send the packet to the output queue */ - if (unlikely - (cvmx_pko_send_packet_finish - (priv->port, priv->queue + qos, pko_command, hw_buffer, - CVMX_PKO_LOCK_CMD_QUEUE))) { - DEBUGPRINT("%s: Failed to send the packet\n", dev->name); - dropped = -1; - } - local_irq_restore(flags); +void cvm_oct_tx_initialize(void) +{ + int i; - if (unlikely(dropped)) { - if (do_free) - cvm_oct_free_work(work); - priv->stats.tx_dropped++; - } else if (do_free) - cvmx_fpa_free(work, CVMX_FPA_WQE_POOL, DONT_WRITEBACK(1)); + /* Disable the interrupt. */ + cvmx_write_csr(CVMX_CIU_TIMX(1), 0); + /* Register an IRQ hander for to receive CIU_TIMX(1) interrupts */ + i = request_irq(OCTEON_IRQ_TIMER1, + cvm_oct_tx_cleanup_watchdog, 0, + "Ethernet", cvm_oct_device); - return dropped; + if (i) + panic("Could not acquire Ethernet IRQ %d\n", OCTEON_IRQ_TIMER1); } -EXPORT_SYMBOL(cvm_oct_transmit_qos); -/** - * This function frees all skb that are currently queued for TX. - * - * @dev: Device being shutdown - */ -void cvm_oct_tx_shutdown(struct net_device *dev) +void cvm_oct_tx_shutdown(void) { - struct octeon_ethernet *priv = netdev_priv(dev); - unsigned long flags; - int qos; - - for (qos = 0; qos < 16; qos++) { - spin_lock_irqsave(&priv->tx_free_list[qos].lock, flags); - while (skb_queue_len(&priv->tx_free_list[qos])) - dev_kfree_skb_any(__skb_dequeue - (&priv->tx_free_list[qos])); - spin_unlock_irqrestore(&priv->tx_free_list[qos].lock, flags); - } + /* Free the interrupt handler */ + free_irq(OCTEON_IRQ_TIMER1, cvm_oct_device); } diff --git a/drivers/staging/octeon/ethernet-tx.h b/drivers/staging/octeon/ethernet-tx.h index c0bebf750bc0..547680c6c371 100644 --- a/drivers/staging/octeon/ethernet-tx.h +++ b/drivers/staging/octeon/ethernet-tx.h @@ -29,29 +29,6 @@ int cvm_oct_xmit(struct sk_buff *skb, struct net_device *dev); int cvm_oct_xmit_pow(struct sk_buff *skb, struct net_device *dev); int cvm_oct_transmit_qos(struct net_device *dev, void *work_queue_entry, int do_free, int qos); -void cvm_oct_tx_shutdown(struct net_device *dev); - -/** - * Free dead transmit skbs. - * - * @priv: The driver data - * @skb_to_free: The number of SKBs to free (free none if negative). - * @qos: The queue to free from. - * @take_lock: If true, acquire the skb list lock. - */ -static inline void cvm_oct_free_tx_skbs(struct octeon_ethernet *priv, - int skb_to_free, - int qos, int take_lock) -{ - /* Free skbuffs not in use by the hardware. */ - if (skb_to_free > 0) { - if (take_lock) - spin_lock(&priv->tx_free_list[qos].lock); - while (skb_to_free > 0) { - dev_kfree_skb(__skb_dequeue(&priv->tx_free_list[qos])); - skb_to_free--; - } - if (take_lock) - spin_unlock(&priv->tx_free_list[qos].lock); - } -} +void cvm_oct_tx_initialize(void); +void cvm_oct_tx_shutdown(void); +void cvm_oct_tx_shutdown_dev(struct net_device *dev); diff --git a/drivers/staging/octeon/ethernet-util.h b/drivers/staging/octeon/ethernet-util.h index 37b665918000..23467563fe57 100644 --- a/drivers/staging/octeon/ethernet-util.h +++ b/drivers/staging/octeon/ethernet-util.h @@ -30,10 +30,9 @@ } while (0) /** - * Given a packet data address, return a pointer to the - * beginning of the packet buffer. - * + * cvm_oct_get_buffer_ptr - convert packet data address to pointer * @packet_ptr: Packet data hardware address + * * Returns Packet buffer pointer */ static inline void *cvm_oct_get_buffer_ptr(union cvmx_buf_ptr packet_ptr) @@ -43,9 +42,7 @@ static inline void *cvm_oct_get_buffer_ptr(union cvmx_buf_ptr packet_ptr) } /** - * Given an IPD/PKO port number, return the logical interface it is - * on. - * + * INTERFACE - convert IPD port to locgical interface * @ipd_port: Port to check * * Returns Logical interface @@ -65,9 +62,7 @@ static inline int INTERFACE(int ipd_port) } /** - * Given an IPD/PKO port number, return the port's index on a - * logical interface. - * + * INDEX - convert IPD/PKO port number to the port's interface index * @ipd_port: Port to check * * Returns Index into interface port list diff --git a/drivers/staging/octeon/ethernet-xaui.c b/drivers/staging/octeon/ethernet-xaui.c index ee3dc41b2c53..3fca1cc31ed8 100644 --- a/drivers/staging/octeon/ethernet-xaui.c +++ b/drivers/staging/octeon/ethernet-xaui.c @@ -26,7 +26,6 @@ **********************************************************************/ #include <linux/kernel.h> #include <linux/netdevice.h> -#include <linux/mii.h> #include <net/dst.h> #include <asm/octeon/octeon.h> diff --git a/drivers/staging/octeon/ethernet.c b/drivers/staging/octeon/ethernet.c index 4cfd4b136b32..4a2161f70c7f 100644 --- a/drivers/staging/octeon/ethernet.c +++ b/drivers/staging/octeon/ethernet.c @@ -29,7 +29,6 @@ #include <linux/module.h> #include <linux/netdevice.h> #include <linux/etherdevice.h> -#include <linux/delay.h> #include <linux/phy.h> #include <net/dst.h> @@ -43,8 +42,6 @@ #include "ethernet-tx.h" #include "ethernet-mdio.h" #include "ethernet-util.h" -#include "ethernet-proc.h" - #include "cvmx-pip.h" #include "cvmx-pko.h" @@ -104,13 +101,15 @@ MODULE_PARM_DESC(pow_send_list, "\n" "\t\"eth2,spi3,spi7\" would cause these three devices to transmit\n" "\tusing the pow_send_group."); -static int disable_core_queueing = 1; -module_param(disable_core_queueing, int, 0444); -MODULE_PARM_DESC(disable_core_queueing, "\n" - "\tWhen set the networking core's tx_queue_len is set to zero. This\n" - "\tallows packets to be sent without lock contention in the packet\n" - "\tscheduler resulting in some cases in improved throughput.\n"); +int max_rx_cpus = -1; +module_param(max_rx_cpus, int, 0444); +MODULE_PARM_DESC(max_rx_cpus, "\n" + "\t\tThe maximum number of CPUs to use for packet reception.\n" + "\t\tUse -1 to use all available CPUs."); +int rx_napi_weight = 32; +module_param(rx_napi_weight, int, 0444); +MODULE_PARM_DESC(rx_napi_weight, "The NAPI WEIGHT parameter."); /* * The offset from mac_addr_base that should be used for the next port @@ -122,9 +121,16 @@ MODULE_PARM_DESC(disable_core_queueing, "\n" static unsigned int cvm_oct_mac_addr_offset; /** - * Periodic timer to check auto negotiation + * cvm_oct_poll_queue - Workqueue for polling operations. + */ +struct workqueue_struct *cvm_oct_poll_queue; + +/** + * cvm_oct_poll_queue_stopping - flag to indicate polling should stop. + * + * Set to one right before cvm_oct_poll_queue is destroyed. */ -static struct timer_list cvm_oct_poll_timer; +atomic_t cvm_oct_poll_queue_stopping = ATOMIC_INIT(0); /** * Array of every ethernet device owned by this driver indexed by @@ -132,65 +138,44 @@ static struct timer_list cvm_oct_poll_timer; */ struct net_device *cvm_oct_device[TOTAL_NUMBER_OF_PORTS]; -/** - * Periodic timer tick for slow management operations - * - * @arg: Device to check - */ -static void cvm_do_timer(unsigned long arg) +u64 cvm_oct_tx_poll_interval; + +static void cvm_oct_rx_refill_worker(struct work_struct *work); +static DECLARE_DELAYED_WORK(cvm_oct_rx_refill_work, cvm_oct_rx_refill_worker); + +static void cvm_oct_rx_refill_worker(struct work_struct *work) { - int32_t skb_to_free, undo; - int queues_per_port; - int qos; - struct octeon_ethernet *priv; - static int port; + /* + * FPA 0 may have been drained, try to refill it if we need + * more than num_packet_buffers / 2, otherwise normal receive + * processing will refill it. If it were drained, no packets + * could be received so cvm_oct_napi_poll would never be + * invoked to do the refill. + */ + cvm_oct_rx_refill_pool(num_packet_buffers / 2); - if (port >= CVMX_PIP_NUM_INPUT_PORTS) { - /* - * All ports have been polled. Start the next - * iteration through the ports in one second. - */ - port = 0; - mod_timer(&cvm_oct_poll_timer, jiffies + HZ); - return; - } - if (!cvm_oct_device[port]) - goto out; + if (!atomic_read(&cvm_oct_poll_queue_stopping)) + queue_delayed_work(cvm_oct_poll_queue, + &cvm_oct_rx_refill_work, HZ); +} + +static void cvm_oct_periodic_worker(struct work_struct *work) +{ + struct octeon_ethernet *priv = container_of(work, + struct octeon_ethernet, + port_periodic_work.work); - priv = netdev_priv(cvm_oct_device[port]); if (priv->poll) - priv->poll(cvm_oct_device[port]); - - queues_per_port = cvmx_pko_get_num_queues(port); - /* Drain any pending packets in the free list */ - for (qos = 0; qos < queues_per_port; qos++) { - if (skb_queue_len(&priv->tx_free_list[qos]) == 0) - continue; - skb_to_free = cvmx_fau_fetch_and_add32(priv->fau + qos * 4, - MAX_SKB_TO_FREE); - undo = skb_to_free > 0 ? - MAX_SKB_TO_FREE : skb_to_free + MAX_SKB_TO_FREE; - if (undo > 0) - cvmx_fau_atomic_add32(priv->fau+qos*4, -undo); - skb_to_free = -skb_to_free > MAX_SKB_TO_FREE ? - MAX_SKB_TO_FREE : -skb_to_free; - cvm_oct_free_tx_skbs(priv, skb_to_free, qos, 1); - } - cvm_oct_device[port]->netdev_ops->ndo_get_stats(cvm_oct_device[port]); + priv->poll(cvm_oct_device[priv->port]); -out: - port++; - /* Poll the next port in a 50th of a second. - This spreads the polling of ports out a little bit */ - mod_timer(&cvm_oct_poll_timer, jiffies + HZ / 50); -} + cvm_oct_device[priv->port]->netdev_ops->ndo_get_stats(cvm_oct_device[priv->port]); + + if (!atomic_read(&cvm_oct_poll_queue_stopping)) + queue_delayed_work(cvm_oct_poll_queue, &priv->port_periodic_work, HZ); + } -/** - * Configure common hardware for all interfaces - */ static __init void cvm_oct_configure_common_hw(void) { - int r; /* Setup the FPA */ cvmx_fpa_enable(); cvm_oct_mem_fill_fpa(CVMX_FPA_PACKET_POOL, CVMX_FPA_PACKET_POOL_SIZE, @@ -205,28 +190,13 @@ static __init void cvm_oct_configure_common_hw(void) cvmx_helper_setup_red(num_packet_buffers / 4, num_packet_buffers / 8); - /* Enable the MII interface */ - if (!octeon_is_simulation()) - cvmx_write_csr(CVMX_SMIX_EN(0), 1); - - /* Register an IRQ hander for to receive POW interrupts */ - r = request_irq(OCTEON_IRQ_WORKQ0 + pow_receive_group, - cvm_oct_do_interrupt, IRQF_SHARED, "Ethernet", - cvm_oct_device); - -#if defined(CONFIG_SMP) && 0 - if (USE_MULTICORE_RECEIVE) { - irq_set_affinity(OCTEON_IRQ_WORKQ0 + pow_receive_group, - cpu_online_mask); - } -#endif } /** - * Free a work queue entry received in a intercept callback. + * cvm_oct_free_work- Free a work queue entry + * + * @work_queue_entry: Work queue entry to free * - * @work_queue_entry: - * Work queue entry to free * Returns Zero on success, Negative on failure. */ int cvm_oct_free_work(void *work_queue_entry) @@ -253,9 +223,9 @@ int cvm_oct_free_work(void *work_queue_entry) EXPORT_SYMBOL(cvm_oct_free_work); /** - * Get the low level ethernet statistics - * + * cvm_oct_common_get_stats - get the low level ethernet statistics * @dev: Device to get the statistics from + * * Returns Pointer to the statistics */ static struct net_device_stats *cvm_oct_common_get_stats(struct net_device *dev) @@ -299,8 +269,7 @@ static struct net_device_stats *cvm_oct_common_get_stats(struct net_device *dev) } /** - * Change the link MTU. Unimplemented - * + * cvm_oct_common_change_mtu - change the link MTU * @dev: Device to change * @new_mtu: The new MTU * @@ -364,8 +333,7 @@ static int cvm_oct_common_change_mtu(struct net_device *dev, int new_mtu) } /** - * Set the multicast list. Currently unimplemented. - * + * cvm_oct_common_set_multicast_list - set the multicast list * @dev: Device to work on */ static void cvm_oct_common_set_multicast_list(struct net_device *dev) @@ -382,7 +350,7 @@ static void cvm_oct_common_set_multicast_list(struct net_device *dev) control.u64 = 0; control.s.bcst = 1; /* Allow broadcast MAC addresses */ - if (dev->mc_list || (dev->flags & IFF_ALLMULTI) || + if (!netdev_mc_empty(dev) || (dev->flags & IFF_ALLMULTI) || (dev->flags & IFF_PROMISC)) /* Force accept multicast packets */ control.s.mcst = 2; @@ -420,10 +388,10 @@ static void cvm_oct_common_set_multicast_list(struct net_device *dev) } /** - * Set the hardware MAC address for a device - * - * @dev: Device to change the MAC address for - * @addr: Address structure to change it too. MAC address is addr + 2. + * cvm_oct_common_set_mac_address - set the hardware MAC address for a device + * @dev: The device in question. + * @addr: Address structure to change it too. + * Returns Zero on success */ static int cvm_oct_common_set_mac_address(struct net_device *dev, void *addr) @@ -470,9 +438,9 @@ static int cvm_oct_common_set_mac_address(struct net_device *dev, void *addr) } /** - * Per network device initialization - * + * cvm_oct_common_init - per network device initialization * @dev: Device to initialize + * * Returns Zero on success */ int cvm_oct_common_init(struct net_device *dev) @@ -510,8 +478,11 @@ int cvm_oct_common_init(struct net_device *dev) && (always_use_pow || strstr(pow_send_list, dev->name))) priv->queue = -1; - if (priv->queue != -1 && USE_HW_TCPUDP_CHECKSUM) - dev->features |= NETIF_F_IP_CSUM; + if (priv->queue != -1) { + dev->features |= NETIF_F_SG; + if (USE_HW_TCPUDP_CHECKSUM) + dev->features |= NETIF_F_IP_CSUM; + } /* We do our own locking, Linux doesn't need to */ dev->features |= NETIF_F_LLTX; @@ -625,12 +596,6 @@ static const struct net_device_ops cvm_oct_pow_netdev_ops = { extern void octeon_mdiobus_force_mod_depencency(void); -/** - * Module/ driver initialization. Creates the linux network - * devices. - * - * Returns Zero on success - */ static int __init cvm_oct_init_module(void) { int num_interfaces; @@ -648,8 +613,12 @@ static int __init cvm_oct_init_module(void) else cvm_oct_mac_addr_offset = 0; - cvm_oct_proc_initialize(); - cvm_oct_rx_initialize(); + cvm_oct_poll_queue = create_singlethread_workqueue("octeon-ethernet"); + if (cvm_oct_poll_queue == NULL) { + pr_err("octeon-ethernet: Cannot create workqueue"); + return -ENOMEM; + } + cvm_oct_configure_common_hw(); cvmx_helper_initialize_packet_io_global(); @@ -682,6 +651,9 @@ static int __init cvm_oct_init_module(void) */ cvmx_fau_atomic_write32(FAU_NUM_PACKET_BUFFERS_TO_FREE, 0); + /* Initialize the FAU used for counting tx SKBs that need to be freed */ + cvmx_fau_atomic_write32(FAU_TOTAL_TX_TO_CLEAN, 0); + if ((pow_send_group != -1)) { struct net_device *dev; pr_info("\tConfiguring device for POW only access\n"); @@ -689,7 +661,6 @@ static int __init cvm_oct_init_module(void) if (dev) { /* Initialize the device private structure. */ struct octeon_ethernet *priv = netdev_priv(dev); - memset(priv, 0, sizeof(struct octeon_ethernet)); dev->netdev_ops = &cvm_oct_pow_netdev_ops; priv->imode = CVMX_HELPER_INTERFACE_MODE_DISABLED; @@ -700,19 +671,16 @@ static int __init cvm_oct_init_module(void) skb_queue_head_init(&priv->tx_free_list[qos]); if (register_netdev(dev) < 0) { - pr_err("Failed to register ethernet " - "device for POW\n"); + pr_err("Failed to register ethernet device for POW\n"); kfree(dev); } else { cvm_oct_device[CVMX_PIP_NUM_INPUT_PORTS] = dev; - pr_info("%s: POW send group %d, receive " - "group %d\n", - dev->name, pow_send_group, - pow_receive_group); + pr_info("%s: POW send group %d, receive group %d\n", + dev->name, pow_send_group, + pow_receive_group); } } else { - pr_err("Failed to allocate ethernet device " - "for POW\n"); + pr_err("Failed to allocate ethernet device for POW\n"); } } @@ -730,17 +698,15 @@ static int __init cvm_oct_init_module(void) struct net_device *dev = alloc_etherdev(sizeof(struct octeon_ethernet)); if (!dev) { - pr_err("Failed to allocate ethernet device " - "for port %d\n", port); + pr_err("Failed to allocate ethernet device for port %d\n", port); continue; } - if (disable_core_queueing) - dev->tx_queue_len = 0; /* Initialize the device private structure. */ priv = netdev_priv(dev); - memset(priv, 0, sizeof(struct octeon_ethernet)); + INIT_DELAYED_WORK(&priv->port_periodic_work, + cvm_oct_periodic_worker); priv->imode = imode; priv->port = port; priv->queue = cvmx_pko_get_base_queue(priv->port); @@ -803,44 +769,25 @@ static int __init cvm_oct_init_module(void) fau -= cvmx_pko_get_num_queues(priv->port) * sizeof(uint32_t); + queue_delayed_work(cvm_oct_poll_queue, + &priv->port_periodic_work, HZ); } } } - if (INTERRUPT_LIMIT) { - /* - * Set the POW timer rate to give an interrupt at most - * INTERRUPT_LIMIT times per second. - */ - cvmx_write_csr(CVMX_POW_WQ_INT_PC, - octeon_bootinfo->eclock_hz / (INTERRUPT_LIMIT * - 16 * 256) << 8); + cvm_oct_tx_initialize(); + cvm_oct_rx_initialize(); - /* - * Enable POW timer interrupt. It will count when - * there are packets available. - */ - cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), - 0x1ful << 24); - } else { - /* Enable POW interrupt when our port has at least one packet */ - cvmx_write_csr(CVMX_POW_WQ_INT_THRX(pow_receive_group), 0x1001); - } + /* + * 150 uS: about 10 1500-byte packtes at 1GE. + */ + cvm_oct_tx_poll_interval = 150 * (octeon_get_clock_rate() / 1000000); - /* Enable the poll timer for checking RGMII status */ - init_timer(&cvm_oct_poll_timer); - cvm_oct_poll_timer.data = 0; - cvm_oct_poll_timer.function = cvm_do_timer; - mod_timer(&cvm_oct_poll_timer, jiffies + HZ); + queue_delayed_work(cvm_oct_poll_queue, &cvm_oct_rx_refill_work, HZ); return 0; } -/** - * Module / driver shutdown - * - * Returns Zero on success - */ static void __exit cvm_oct_cleanup_module(void) { int port; @@ -853,22 +800,31 @@ static void __exit cvm_oct_cleanup_module(void) /* Free the interrupt handler */ free_irq(OCTEON_IRQ_WORKQ0 + pow_receive_group, cvm_oct_device); - del_timer(&cvm_oct_poll_timer); + atomic_inc_return(&cvm_oct_poll_queue_stopping); + cancel_delayed_work_sync(&cvm_oct_rx_refill_work); + cvm_oct_rx_shutdown(); + cvm_oct_tx_shutdown(); + cvmx_pko_disable(); /* Free the ethernet devices */ for (port = 0; port < TOTAL_NUMBER_OF_PORTS; port++) { if (cvm_oct_device[port]) { - cvm_oct_tx_shutdown(cvm_oct_device[port]); - unregister_netdev(cvm_oct_device[port]); - kfree(cvm_oct_device[port]); + struct net_device *dev = cvm_oct_device[port]; + struct octeon_ethernet *priv = netdev_priv(dev); + cancel_delayed_work_sync(&priv->port_periodic_work); + + cvm_oct_tx_shutdown_dev(dev); + unregister_netdev(dev); + kfree(dev); cvm_oct_device[port] = NULL; } } + destroy_workqueue(cvm_oct_poll_queue); + cvmx_pko_shutdown(); - cvm_oct_proc_shutdown(); cvmx_ipd_free_ptr(); diff --git a/drivers/staging/octeon/octeon-ethernet.h b/drivers/staging/octeon/octeon-ethernet.h index 402a15b9bb0e..d58192563552 100644 --- a/drivers/staging/octeon/octeon-ethernet.h +++ b/drivers/staging/octeon/octeon-ethernet.h @@ -4,7 +4,7 @@ * Contact: support@caviumnetworks.com * This file is part of the OCTEON SDK * - * Copyright (c) 2003-2007 Cavium Networks + * Copyright (c) 2003-2010 Cavium Networks * * This file is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, Version 2, as @@ -57,58 +57,12 @@ struct octeon_ethernet { uint64_t link_info; /* Called periodically to check link status */ void (*poll) (struct net_device *dev); + struct delayed_work port_periodic_work; + struct work_struct port_work; /* may be unused. */ }; -/** - * Free a work queue entry received in a intercept callback. - * - * @work_queue_entry: - * Work queue entry to free - * Returns Zero on success, Negative on failure. - */ int cvm_oct_free_work(void *work_queue_entry); -/** - * Transmit a work queue entry out of the ethernet port. Both - * the work queue entry and the packet data can optionally be - * freed. The work will be freed on error as well. - * - * @dev: Device to transmit out. - * @work_queue_entry: - * Work queue entry to send - * @do_free: True if the work queue entry and packet data should be - * freed. If false, neither will be freed. - * @qos: Index into the queues for this port to transmit on. This - * is used to implement QoS if their are multiple queues per - * port. This parameter must be between 0 and the number of - * queues per port minus 1. Values outside of this range will - * be change to zero. - * - * Returns Zero on success, negative on failure. - */ -int cvm_oct_transmit_qos(struct net_device *dev, void *work_queue_entry, - int do_free, int qos); - -/** - * Transmit a work queue entry out of the ethernet port. Both - * the work queue entry and the packet data can optionally be - * freed. The work will be freed on error as well. This simply - * wraps cvmx_oct_transmit_qos() for backwards compatability. - * - * @dev: Device to transmit out. - * @work_queue_entry: - * Work queue entry to send - * @do_free: True if the work queue entry and packet data should be - * freed. If false, neither will be freed. - * - * Returns Zero on success, negative on failure. - */ -static inline int cvm_oct_transmit(struct net_device *dev, - void *work_queue_entry, int do_free) -{ - return cvm_oct_transmit_qos(dev, work_queue_entry, do_free, 0); -} - extern int cvm_oct_rgmii_init(struct net_device *dev); extern void cvm_oct_rgmii_uninit(struct net_device *dev); extern int cvm_oct_rgmii_open(struct net_device *dev); @@ -134,5 +88,11 @@ extern int pow_send_group; extern int pow_receive_group; extern char pow_send_list[]; extern struct net_device *cvm_oct_device[]; +extern struct workqueue_struct *cvm_oct_poll_queue; +extern atomic_t cvm_oct_poll_queue_stopping; +extern u64 cvm_oct_tx_poll_interval; + +extern int max_rx_cpus; +extern int rx_napi_weight; #endif diff --git a/drivers/staging/phison/phison.c b/drivers/staging/phison/phison.c index ff30cb0321b9..0c495eacb75b 100644 --- a/drivers/staging/phison/phison.c +++ b/drivers/staging/phison/phison.c @@ -62,7 +62,7 @@ static int phison_init_one(struct pci_dev *pdev, const struct pci_device_id *id) }; const struct ata_port_info *ppi[] = { &info, NULL }; - ret = ata_pci_sff_init_one(pdev, ppi, &phison_sht, NULL); + ret = ata_pci_sff_init_one(pdev, ppi, &phison_sht, NULL, 0); dev_dbg(&pdev->dev, "phison_init_one(), ret = %x\n", ret); diff --git a/drivers/staging/rtl8187se/ieee80211/ieee80211.h b/drivers/staging/rtl8187se/ieee80211/ieee80211.h index c4ab683e8f61..4cd95c3dc947 100644 --- a/drivers/staging/rtl8187se/ieee80211/ieee80211.h +++ b/drivers/staging/rtl8187se/ieee80211/ieee80211.h @@ -479,15 +479,6 @@ struct ieee80211_header_data { u16 seq_ctrl; }; -struct ieee80211_hdr_3addr { - u16 frame_ctl; - u16 duration_id; - u8 addr1[ETH_ALEN]; - u8 addr2[ETH_ALEN]; - u8 addr3[ETH_ALEN]; - u16 seq_ctl; -} __attribute__ ((packed)); - struct ieee80211_hdr_4addr { u16 frame_ctl; u16 duration_id; diff --git a/drivers/staging/rtl8187se/ieee80211/ieee80211_softmac.c b/drivers/staging/rtl8187se/ieee80211/ieee80211_softmac.c index e8cfcb85727e..c2f472ee6eb6 100644 --- a/drivers/staging/rtl8187se/ieee80211/ieee80211_softmac.c +++ b/drivers/staging/rtl8187se/ieee80211/ieee80211_softmac.c @@ -203,7 +203,7 @@ inline void softmac_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *ieee enqueue_mgmt(ieee,skb); }else{ - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0]<<4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0]<<4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -220,7 +220,7 @@ inline void softmac_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *ieee spin_unlock_irqrestore(&ieee->lock, flags); spin_lock_irqsave(&ieee->mgmt_tx_lock, flags); - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -246,7 +246,7 @@ inline void softmac_ps_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *i if(single){ - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -259,7 +259,7 @@ inline void softmac_ps_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *i }else{ - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -287,7 +287,7 @@ inline struct sk_buff *ieee80211_disassociate_skb( return NULL; disass = (struct ieee80211_disassoc_frame *) skb_put(skb,sizeof(struct ieee80211_disassoc_frame)); - disass->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_DISASSOC); + disass->header.frame_control = cpu_to_le16(IEEE80211_STYPE_DISASSOC); disass->header.duration_id = 0; memcpy(disass->header.addr1, beacon->bssid, ETH_ALEN); @@ -905,7 +905,7 @@ struct sk_buff* ieee80211_assoc_resp(struct ieee80211_device *ieee, u8 *dest) assoc = (struct ieee80211_assoc_response_frame *) skb_put(skb,sizeof(struct ieee80211_assoc_response_frame)); - assoc->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_ASSOC_RESP); + assoc->header.frame_control = cpu_to_le16(IEEE80211_STYPE_ASSOC_RESP); memcpy(assoc->header.addr1, dest,ETH_ALEN); memcpy(assoc->header.addr3, ieee->dev->dev_addr, ETH_ALEN); memcpy(assoc->header.addr2, ieee->dev->dev_addr, ETH_ALEN); @@ -981,7 +981,7 @@ struct sk_buff* ieee80211_null_func(struct ieee80211_device *ieee,short pwr) memcpy(hdr->addr2, ieee->dev->dev_addr, ETH_ALEN); memcpy(hdr->addr3, ieee->current_network.bssid, ETH_ALEN); - hdr->frame_ctl = cpu_to_le16(IEEE80211_FTYPE_DATA | + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC | IEEE80211_FCTL_TODS | (pwr ? IEEE80211_FCTL_PM:0)); @@ -1084,7 +1084,7 @@ inline struct sk_buff *ieee80211_association_req(struct ieee80211_network *beaco skb_put(skb, sizeof(struct ieee80211_assoc_request_frame)); - hdr->header.frame_ctl = IEEE80211_STYPE_ASSOC_REQ; + hdr->header.frame_control = IEEE80211_STYPE_ASSOC_REQ; hdr->header.duration_id= 37; //FIXME memcpy(hdr->header.addr1, beacon->bssid, ETH_ALEN); memcpy(hdr->header.addr2, ieee->dev->dev_addr, ETH_ALEN); @@ -1786,11 +1786,11 @@ ieee80211_rx_frame_softmac(struct ieee80211_device *ieee, struct sk_buff *skb, tasklet_schedule(&ieee->ps_task); - if(WLAN_FC_GET_STYPE(header->frame_ctl) != IEEE80211_STYPE_PROBE_RESP && - WLAN_FC_GET_STYPE(header->frame_ctl) != IEEE80211_STYPE_BEACON) + if (WLAN_FC_GET_STYPE(header->frame_control) != IEEE80211_STYPE_PROBE_RESP && + WLAN_FC_GET_STYPE(header->frame_control) != IEEE80211_STYPE_BEACON) ieee->last_rx_ps_time = jiffies; - switch (WLAN_FC_GET_STYPE(header->frame_ctl)) { + switch (WLAN_FC_GET_STYPE(header->frame_control)) { case IEEE80211_STYPE_ASSOC_RESP: case IEEE80211_STYPE_REASSOC_RESP: @@ -2064,7 +2064,7 @@ void ieee80211_rtl_wake_queue(struct ieee80211_device *ieee) header = (struct ieee80211_hdr_3addr *) skb->data; - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; diff --git a/drivers/staging/rtl8187se/r8180_core.c b/drivers/staging/rtl8187se/r8180_core.c index b49eed81debc..b1757acabedc 100644 --- a/drivers/staging/rtl8187se/r8180_core.c +++ b/drivers/staging/rtl8187se/r8180_core.c @@ -1783,7 +1783,7 @@ rate) struct r8180_priv *priv = (struct r8180_priv *)ieee80211_priv(dev); int mode; struct ieee80211_hdr_3addr *h = (struct ieee80211_hdr_3addr *) skb->data; - short morefrag = (h->frame_ctl) & IEEE80211_FCTL_MOREFRAGS; + short morefrag = (h->frame_control) & IEEE80211_FCTL_MOREFRAGS; unsigned long flags; int priority; @@ -2052,7 +2052,7 @@ short rtl8180_tx(struct net_device *dev, u8* txbuf, int len, int priority, TxDescDuration = ThisFrameTime + aSifsTime + AckTime; } - if(!(frag_hdr->frame_ctl & IEEE80211_FCTL_MOREFRAGS)) { //no more fragment + if (!(frag_hdr->frame_control & IEEE80211_FCTL_MOREFRAGS)) { // ThisFrame-ACK. Duration = aSifsTime + AckTime; } else { // One or more fragments remained. diff --git a/drivers/staging/rtl8192su/ieee80211/ieee80211.h b/drivers/staging/rtl8192su/ieee80211/ieee80211.h index 159fddf628e2..e644bf4e91ce 100644 --- a/drivers/staging/rtl8192su/ieee80211/ieee80211.h +++ b/drivers/staging/rtl8192su/ieee80211/ieee80211.h @@ -605,16 +605,6 @@ struct ieee80211_hdr_2addr { u8 payload[0]; } __attribute__ ((packed)); -struct ieee80211_hdr_3addr { - __le16 frame_ctl; - __le16 duration_id; - u8 addr1[ETH_ALEN]; - u8 addr2[ETH_ALEN]; - u8 addr3[ETH_ALEN]; - __le16 seq_ctl; - u8 payload[0]; -} __attribute__ ((packed)); - struct ieee80211_hdr_4addr { __le16 frame_ctl; __le16 duration_id; @@ -1668,7 +1658,7 @@ static inline u8 *ieee80211_get_payload(struct rtl_ieee80211_hdr *hdr) case IEEE80211_2ADDR_LEN: return ((struct ieee80211_hdr_2addr *)hdr)->payload; case IEEE80211_3ADDR_LEN: - return ((struct ieee80211_hdr_3addr *)hdr)->payload; + return (void *)hdr+sizeof(struct ieee80211_hdr_3addr); case IEEE80211_4ADDR_LEN: return ((struct ieee80211_hdr_4addr *)hdr)->payload; } diff --git a/drivers/staging/rtl8192su/ieee80211/ieee80211_r8192s.h b/drivers/staging/rtl8192su/ieee80211/ieee80211_r8192s.h index 123abcf0f497..7d6c3bc143ae 100644 --- a/drivers/staging/rtl8192su/ieee80211/ieee80211_r8192s.h +++ b/drivers/staging/rtl8192su/ieee80211/ieee80211_r8192s.h @@ -201,7 +201,7 @@ typedef union _frameqos { static inline u8 Frame_QoSTID(u8 *buf) { struct ieee80211_hdr_3addr *hdr = (struct ieee80211_hdr_3addr *)buf; - u16 fc = le16_to_cpu(hdr->frame_ctl); + u16 fc = le16_to_cpu(hdr->frame_control); return (u8)((frameqos *)(buf + (((fc & IEEE80211_FCTL_TODS) && diff --git a/drivers/staging/rtl8192su/ieee80211/ieee80211_rx.c b/drivers/staging/rtl8192su/ieee80211/ieee80211_rx.c index 67b99ad88475..cc80faf6598b 100644 --- a/drivers/staging/rtl8192su/ieee80211/ieee80211_rx.c +++ b/drivers/staging/rtl8192su/ieee80211/ieee80211_rx.c @@ -744,7 +744,7 @@ u8 parse_subframe(struct sk_buff *skb, struct ieee80211_rxb *rxb,u8* src,u8* dst) { struct ieee80211_hdr_3addr *hdr = (struct ieee80211_hdr_3addr* )skb->data; - u16 fc = le16_to_cpu(hdr->frame_ctl); + u16 fc = le16_to_cpu(hdr->frame_control); u16 LLCOffset= sizeof(struct ieee80211_hdr_3addr); u16 ChkLength; @@ -756,7 +756,7 @@ u8 parse_subframe(struct sk_buff *skb, struct sk_buff *sub_skb; u8 *data_ptr; /* just for debug purpose */ - SeqNum = WLAN_GET_SEQ_SEQ(le16_to_cpu(hdr->seq_ctl)); + SeqNum = WLAN_GET_SEQ_SEQ(le16_to_cpu(hdr->seq_ctrl)); if((IEEE80211_QOS_HAS_SEQ(fc))&&\ (((frameqos *)(skb->data + IEEE80211_3ADDR_LEN))->field.reserved)) { @@ -2370,7 +2370,7 @@ static inline void ieee80211_process_probe_response( escape_essid(info_element->data, info_element->len), beacon->header.addr3, - WLAN_FC_GET_STYPE(beacon->header.frame_ctl) == + WLAN_FC_GET_STYPE(beacon->header.frame_control) == IEEE80211_STYPE_PROBE_RESP ? "PROBE RESPONSE" : "BEACON"); return; @@ -2387,7 +2387,7 @@ static inline void ieee80211_process_probe_response( return; if(ieee->bGlobalDomain) { - if (WLAN_FC_GET_STYPE(beacon->header.frame_ctl) == IEEE80211_STYPE_PROBE_RESP) + if (WLAN_FC_GET_STYPE(beacon->header.frame_control) == IEEE80211_STYPE_PROBE_RESP) { // Case 1: Country code if(IS_COUNTRY_IE_VALID(ieee) ) @@ -2454,7 +2454,7 @@ static inline void ieee80211_process_probe_response( else ieee->current_network.buseprotection = false; } - if(is_beacon(beacon->header.frame_ctl)) + if(is_beacon(beacon->header.frame_control)) { if(ieee->state == IEEE80211_LINKED) ieee->LinkDetectInfo.NumRecvBcnInPeriod++; @@ -2496,7 +2496,7 @@ static inline void ieee80211_process_probe_response( escape_essid(network.ssid, network.ssid_len), network.bssid, - WLAN_FC_GET_STYPE(beacon->header.frame_ctl) == + WLAN_FC_GET_STYPE(beacon->header.frame_control) == IEEE80211_STYPE_PROBE_RESP ? "PROBE RESPONSE" : "BEACON"); #endif @@ -2509,7 +2509,7 @@ static inline void ieee80211_process_probe_response( escape_essid(target->ssid, target->ssid_len), target->bssid, - WLAN_FC_GET_STYPE(beacon->header.frame_ctl) == + WLAN_FC_GET_STYPE(beacon->header.frame_control) == IEEE80211_STYPE_PROBE_RESP ? "PROBE RESPONSE" : "BEACON"); @@ -2519,7 +2519,7 @@ static inline void ieee80211_process_probe_response( */ renew = !time_after(target->last_scanned + ieee->scan_age, jiffies); //YJ,add,080819,for hidden ap - if(is_beacon(beacon->header.frame_ctl) == 0) + if(is_beacon(beacon->header.frame_control) == 0) network.flags = (~NETWORK_EMPTY_ESSID & network.flags)|(NETWORK_EMPTY_ESSID & target->flags); //if(strncmp(network.ssid, "linksys-c",9) == 0) // printk("====>2 network.ssid=%s FLAG=%d target.ssid=%s FLAG=%d\n", network.ssid, network.flags, target->ssid, target->flags); @@ -2535,7 +2535,7 @@ static inline void ieee80211_process_probe_response( } spin_unlock_irqrestore(&ieee->lock, flags); - if (is_beacon(beacon->header.frame_ctl)&&is_same_network(&ieee->current_network, &network, ieee)&&\ + if (is_beacon(beacon->header.frame_control)&&is_same_network(&ieee->current_network, &network, ieee)&&\ (ieee->state == IEEE80211_LINKED)) { if(ieee->handle_beacon != NULL) { ieee->handle_beacon(ieee->dev,beacon,&ieee->current_network); diff --git a/drivers/staging/rtl8192su/ieee80211/ieee80211_softmac.c b/drivers/staging/rtl8192su/ieee80211/ieee80211_softmac.c index e46f3bc6a21a..e16653e8dcc4 100644 --- a/drivers/staging/rtl8192su/ieee80211/ieee80211_softmac.c +++ b/drivers/staging/rtl8192su/ieee80211/ieee80211_softmac.c @@ -242,7 +242,7 @@ inline void softmac_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *ieee if(ieee->queue_stop){ enqueue_mgmt(ieee,skb); }else{ - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0]<<4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0]<<4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -260,7 +260,7 @@ inline void softmac_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *ieee spin_unlock_irqrestore(&ieee->lock, flags); spin_lock_irqsave(&ieee->mgmt_tx_lock, flags); - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -302,7 +302,7 @@ inline void softmac_ps_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *i //printk("=============>%s()\n", __FUNCTION__); if(single){ - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -315,7 +315,7 @@ inline void softmac_ps_mgmt_xmit(struct sk_buff *skb, struct ieee80211_device *i }else{ - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -347,7 +347,7 @@ inline struct sk_buff *ieee80211_probe_req(struct ieee80211_device *ieee) skb_reserve(skb, ieee->tx_headroom); req = (struct ieee80211_probe_request *) skb_put(skb,sizeof(struct ieee80211_probe_request)); - req->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); + req->header.frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_REQ); req->header.duration_id = 0; //FIXME: is this OK ? memset(req->header.addr1, 0xff, ETH_ALEN); @@ -662,8 +662,8 @@ inline struct sk_buff *ieee80211_authentication_req(struct ieee80211_network *be auth = (struct ieee80211_authentication *) skb_put(skb, sizeof(struct ieee80211_authentication)); - auth->header.frame_ctl = IEEE80211_STYPE_AUTH; - if (challengelen) auth->header.frame_ctl |= IEEE80211_FCTL_WEP; + auth->header.frame_control = IEEE80211_STYPE_AUTH; + if (challengelen) auth->header.frame_control |= IEEE80211_FCTL_WEP; auth->header.duration_id = 0x013a; //FIXME @@ -801,7 +801,7 @@ static struct sk_buff* ieee80211_probe_resp(struct ieee80211_device *ieee, u8 *d beacon_buf->capability |= cpu_to_le16(WLAN_CAPABILITY_PRIVACY); - beacon_buf->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_PROBE_RESP); + beacon_buf->header.frame_control = cpu_to_le16(IEEE80211_STYPE_PROBE_RESP); beacon_buf->info_element[0].id = MFIE_TYPE_SSID; beacon_buf->info_element[0].len = ssid_len; @@ -880,7 +880,7 @@ struct sk_buff* ieee80211_assoc_resp(struct ieee80211_device *ieee, u8 *dest) assoc = (struct ieee80211_assoc_response_frame *) skb_put(skb,sizeof(struct ieee80211_assoc_response_frame)); - assoc->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_ASSOC_RESP); + assoc->header.frame_control = cpu_to_le16(IEEE80211_STYPE_ASSOC_RESP); memcpy(assoc->header.addr1, dest,ETH_ALEN); memcpy(assoc->header.addr3, ieee->dev->dev_addr, ETH_ALEN); memcpy(assoc->header.addr2, ieee->dev->dev_addr, ETH_ALEN); @@ -935,7 +935,7 @@ struct sk_buff* ieee80211_auth_resp(struct ieee80211_device *ieee,int status, u8 memcpy(auth->header.addr3, ieee->dev->dev_addr, ETH_ALEN); memcpy(auth->header.addr2, ieee->dev->dev_addr, ETH_ALEN); memcpy(auth->header.addr1, dest, ETH_ALEN); - auth->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_AUTH); + auth->header.frame_control = cpu_to_le16(IEEE80211_STYPE_AUTH); return skb; @@ -957,7 +957,7 @@ struct sk_buff* ieee80211_null_func(struct ieee80211_device *ieee,short pwr) memcpy(hdr->addr2, ieee->dev->dev_addr, ETH_ALEN); memcpy(hdr->addr3, ieee->current_network.bssid, ETH_ALEN); - hdr->frame_ctl = cpu_to_le16(IEEE80211_FTYPE_DATA | + hdr->frame_control = cpu_to_le16(IEEE80211_FTYPE_DATA | IEEE80211_STYPE_NULLFUNC | IEEE80211_FCTL_TODS | (pwr ? IEEE80211_FCTL_PM:0)); @@ -1083,7 +1083,7 @@ inline struct sk_buff *ieee80211_association_req(struct ieee80211_network *beaco skb_put(skb, sizeof(struct ieee80211_assoc_request_frame)+2); - hdr->header.frame_ctl = IEEE80211_STYPE_ASSOC_REQ; + hdr->header.frame_control = IEEE80211_STYPE_ASSOC_REQ; hdr->header.duration_id= 37; //FIXME memcpy(hdr->header.addr1, beacon->bssid, ETH_ALEN); memcpy(hdr->header.addr2, ieee->dev->dev_addr, ETH_ALEN); @@ -1940,13 +1940,13 @@ ieee80211_rx_frame_softmac(struct ieee80211_device *ieee, struct sk_buff *skb, if(!ieee->proto_started) return 0; - switch (WLAN_FC_GET_STYPE(header->frame_ctl)) { + switch (WLAN_FC_GET_STYPE(header->frame_control)) { case IEEE80211_STYPE_ASSOC_RESP: case IEEE80211_STYPE_REASSOC_RESP: IEEE80211_DEBUG_MGMT("received [RE]ASSOCIATION RESPONSE (%d)\n", - WLAN_FC_GET_STYPE(header->frame_ctl)); + WLAN_FC_GET_STYPE(header->frame_control)); if ((ieee->softmac_features & IEEE_SOFTMAC_ASSOCIATE) && ieee->state == IEEE80211_ASSOCIATING_AUTHENTICATED && ieee->iw_mode == IW_MODE_INFRA){ @@ -2088,7 +2088,7 @@ ieee80211_rx_frame_softmac(struct ieee80211_device *ieee, struct sk_buff *skb, if ((ieee->softmac_features & IEEE_SOFTMAC_ASSOCIATE) && ieee->state == IEEE80211_LINKED && ieee->iw_mode == IW_MODE_INFRA){ - printk("==========>received disassoc/deauth(%x) frame, reason code:%x\n",WLAN_FC_GET_STYPE(header->frame_ctl), ((struct ieee80211_disassoc*)skb->data)->reason); + printk("==========>received disassoc/deauth(%x) frame, reason code:%x\n",WLAN_FC_GET_STYPE(header->frame_control), ((struct ieee80211_disassoc*)skb->data)->reason); ieee->state = IEEE80211_ASSOCIATING; ieee->softmac_stats.reassoc++; ieee->is_roaming = true; @@ -2239,7 +2239,7 @@ void ieee80211_rtl_wake_queue(struct ieee80211_device *ieee) header = (struct ieee80211_hdr_3addr *) skb->data; - header->seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + header->seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -2574,7 +2574,7 @@ struct sk_buff *ieee80211_get_beacon_(struct ieee80211_device *ieee) return NULL; b = (struct ieee80211_probe_response *) skb->data; - b->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_BEACON); + b->header.frame_control = cpu_to_le16(IEEE80211_STYPE_BEACON); return skb; @@ -2590,7 +2590,7 @@ struct sk_buff *ieee80211_get_beacon(struct ieee80211_device *ieee) return NULL; b = (struct ieee80211_probe_response *) skb->data; - b->header.seq_ctl = cpu_to_le16(ieee->seq_ctrl[0] << 4); + b->header.seq_ctrl = cpu_to_le16(ieee->seq_ctrl[0] << 4); if (ieee->seq_ctrl[0] == 0xFFF) ieee->seq_ctrl[0] = 0; @@ -3139,7 +3139,7 @@ inline struct sk_buff *ieee80211_disassociate_skb( return NULL; disass = (struct ieee80211_disassoc *) skb_put(skb,sizeof(struct ieee80211_disassoc)); - disass->header.frame_ctl = cpu_to_le16(IEEE80211_STYPE_DISASSOC); + disass->header.frame_control = cpu_to_le16(IEEE80211_STYPE_DISASSOC); disass->header.duration_id = 0; memcpy(disass->header.addr1, beacon->bssid, ETH_ALEN); diff --git a/drivers/staging/rtl8192su/ieee80211/rtl819x_BAProc.c b/drivers/staging/rtl8192su/ieee80211/rtl819x_BAProc.c index 5a3888450ab3..8c37dd124fc9 100644 --- a/drivers/staging/rtl8192su/ieee80211/rtl819x_BAProc.c +++ b/drivers/staging/rtl8192su/ieee80211/rtl819x_BAProc.c @@ -136,7 +136,7 @@ static struct sk_buff* ieee80211_ADDBA(struct ieee80211_device* ieee, u8* Dst, P memcpy(BAReq->addr3, ieee->current_network.bssid, ETH_ALEN); - BAReq->frame_ctl = cpu_to_le16(IEEE80211_STYPE_MANAGE_ACT); //action frame + BAReq->frame_control = cpu_to_le16(IEEE80211_STYPE_MANAGE_ACT); //action frame //tag += sizeof( struct ieee80211_hdr_3addr); //move to action field tag = (u8*)skb_put(skb, 9); @@ -221,7 +221,7 @@ static struct sk_buff* ieee80211_DELBA( memcpy(Delba->addr1, dst, ETH_ALEN); memcpy(Delba->addr2, ieee->dev->dev_addr, ETH_ALEN); memcpy(Delba->addr3, ieee->current_network.bssid, ETH_ALEN); - Delba->frame_ctl = cpu_to_le16(IEEE80211_STYPE_MANAGE_ACT); //action frame + Delba->frame_control = cpu_to_le16(IEEE80211_STYPE_MANAGE_ACT); //action frame tag = (u8*)skb_put(skb, 6); diff --git a/drivers/staging/rtl8192su/r8192U_core.c b/drivers/staging/rtl8192su/r8192U_core.c index 23d1d35be7ed..41939eee5251 100644 --- a/drivers/staging/rtl8192su/r8192U_core.c +++ b/drivers/staging/rtl8192su/r8192U_core.c @@ -6125,7 +6125,7 @@ void rtl8192_process_phyinfo(struct r8192_priv * priv,u8* buffer, struct ieee802 u16 sc ; unsigned int frag,seq; hdr = (struct ieee80211_hdr_3addr *)buffer; - sc = le16_to_cpu(hdr->seq_ctl); + sc = le16_to_cpu(hdr->seq_ctrl); frag = WLAN_GET_SEQ_FRAG(sc); seq = WLAN_GET_SEQ_SEQ(sc); //cosa add 04292008 to record the sequence number @@ -6784,7 +6784,7 @@ void rtl8192SU_TranslateRxSignalStuff(struct sk_buff *skb, tmp_buf = (u8*)skb->data;// + get_rxpacket_shiftbytes_819xusb(pstats); hdr = (struct ieee80211_hdr_3addr *)tmp_buf; - fc = le16_to_cpu(hdr->frame_ctl); + fc = le16_to_cpu(hdr->frame_control); type = WLAN_FC_GET_TYPE(fc); praddr = hdr->addr1; diff --git a/drivers/staging/slicoss/slicoss.c b/drivers/staging/slicoss/slicoss.c index 87d98e38d3b5..98bc4bd3fd89 100644 --- a/drivers/staging/slicoss/slicoss.c +++ b/drivers/staging/slicoss/slicoss.c @@ -1368,25 +1368,17 @@ static void slic_mcast_set_list(struct net_device *dev) { struct adapter *adapter = netdev_priv(dev); int status = STATUS_SUCCESS; - int i; char *addresses; - struct dev_mc_list *mc_list = dev->mc_list; - int mc_count = dev->mc_count; + struct dev_mc_list *mc_list; ASSERT(adapter); - for (i = 1; i <= mc_count; i++) { + netdev_for_each_mc_addr(mc_list, dev) { addresses = (char *) &mc_list->dmi_addr; - if (mc_list->dmi_addrlen == 6) { - status = slic_mcast_add_list(adapter, addresses); - if (status != STATUS_SUCCESS) - break; - } else { - status = -EINVAL; + status = slic_mcast_add_list(adapter, addresses); + if (status != STATUS_SUCCESS) break; - } slic_mcast_set_bit(adapter, addresses); - mc_list = mc_list->next; } if (adapter->devflags_prev != dev->flags) { diff --git a/drivers/staging/sm7xx/smtcfb.c b/drivers/staging/sm7xx/smtcfb.c index 863df038f0d6..9c82a1a81ccc 100644 --- a/drivers/staging/sm7xx/smtcfb.c +++ b/drivers/staging/sm7xx/smtcfb.c @@ -6,7 +6,7 @@ * Boyod boyod.yang@siliconmotion.com.cn * * Copyright (C) 2009 Lemote, Inc. - * Author: Wu Zhangjin, wuzj@lemote.com + * Author: Wu Zhangjin, wuzhangjin@gmail.com * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of this archive for diff --git a/drivers/staging/sm7xx/smtcfb.h b/drivers/staging/sm7xx/smtcfb.h index 7f2c34138215..7ee565c2c952 100644 --- a/drivers/staging/sm7xx/smtcfb.h +++ b/drivers/staging/sm7xx/smtcfb.h @@ -6,7 +6,7 @@ * Boyod boyod.yang@siliconmotion.com.cn * * Copyright (C) 2009 Lemote, Inc. - * Author: Wu Zhangjin, wuzj@lemote.com + * Author: Wu Zhangjin, wuzhangjin@gmail.com * * This file is subject to the terms and conditions of the GNU General Public * License. See the file COPYING in the main directory of this archive for diff --git a/drivers/staging/tm6000/Kconfig b/drivers/staging/tm6000/Kconfig new file mode 100644 index 000000000000..5fe759cc2ee9 --- /dev/null +++ b/drivers/staging/tm6000/Kconfig @@ -0,0 +1,32 @@ +config VIDEO_TM6000 + tristate "TV Master TM5600/6000/6010 driver" + depends on VIDEO_DEV && I2C && INPUT && USB && EXPERIMENTAL + select VIDEO_TUNER + select TUNER_XC2028 + select VIDEOBUF_VMALLOC + help + Support for TM5600/TM6000/TM6010 USB Device + + Since these cards have no MPEG decoder onboard, they transmit + only compressed MPEG data over the usb bus, so you need + an external software decoder to watch TV on your computer. + + Say Y if you own such a device and want to use it. + +config VIDEO_TM6000_ALSA + tristate "TV Master TM5600/6000/6010 audio support" + depends on VIDEO_TM6000 && SND && EXPERIMENTAL + select SND_PCM + ---help--- + This is a video4linux driver for direct (DMA) audio for + TM5600/TM6000/TM6010 USB Devices. + + To compile this driver as a module, choose M here: the + module will be called tm6000-alsa. + +config VIDEO_TM6000_DVB + bool "DVB Support for tm6000 based TV cards" + depends on VIDEO_TM6000 && DVB_CORE && EXPERIMENTAL + select DVB_ZL10353 + ---help--- + This adds support for DVB cards based on the tm5600/tm6000 chip. diff --git a/drivers/staging/tm6000/Makefile b/drivers/staging/tm6000/Makefile new file mode 100644 index 000000000000..93370fccc073 --- /dev/null +++ b/drivers/staging/tm6000/Makefile @@ -0,0 +1,17 @@ +tm6000-objs := tm6000-cards.o \ + tm6000-core.o \ + tm6000-i2c.o \ + tm6000-video.o \ + tm6000-stds.o + +ifeq ($(CONFIG_VIDEO_TM6000_DVB),y) +tm6000-objs += tm6000-dvb.o +endif + +obj-$(CONFIG_VIDEO_TM6000) += tm6000.o +obj-$(CONFIG_VIDEO_TM6000_ALSA) += tm6000-alsa.o + +EXTRA_CFLAGS = -Idrivers/media/video +EXTRA_CFLAGS += -Idrivers/media/common/tuners +EXTRA_CFLAGS += -Idrivers/media/dvb/dvb-core +EXTRA_CFLAGS += -Idrivers/media/dvb/frontends diff --git a/drivers/staging/tm6000/README b/drivers/staging/tm6000/README new file mode 100644 index 000000000000..cff09b7b477e --- /dev/null +++ b/drivers/staging/tm6000/README @@ -0,0 +1,11 @@ +Todo: + - checkpatch.pl cleanups + - sparse cleanups + - convert to new i2c approach + - better support DVB + - fix reading from i2c, if possible + - fix loosing frames + - fix oops? + +Please send patches to linux-media@vger.kernel.org + diff --git a/drivers/staging/tm6000/tm6000-alsa.c b/drivers/staging/tm6000/tm6000-alsa.c new file mode 100644 index 000000000000..7cc2ac740d8a --- /dev/null +++ b/drivers/staging/tm6000/tm6000-alsa.c @@ -0,0 +1,414 @@ +/* + * + * Support for audio capture for tm5600/6000/6010 + * (c) 2007-2008 Mauro Carvalho Chehab <mchehab@redhat.com> + * + * Based on cx88-alsa.c + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/module.h> +#include <linux/init.h> +#include <linux/device.h> +#include <linux/interrupt.h> +#include <linux/usb.h> + +#include <asm/delay.h> +#include <sound/core.h> +#include <sound/pcm.h> +#include <sound/pcm_params.h> +#include <sound/control.h> +#include <sound/initval.h> + + +#include "tm6000.h" +#include "tm6000-regs.h" + +#undef dprintk + +#define dprintk(level, fmt, arg...) do { \ + if (debug >= level) \ + printk(KERN_INFO "%s/1: " fmt, chip->core->name , ## arg); \ + } while (0) + +/**************************************************************************** + Data type declarations - Can be moded to a header file later + ****************************************************************************/ + +struct snd_tm6000_card { + struct snd_card *card; + + spinlock_t reg_lock; + + atomic_t count; + + unsigned int period_size; + unsigned int num_periods; + + struct tm6000_core *core; + struct tm6000_buffer *buf; + + int bufsize; + + struct snd_pcm_substream *substream; +}; + + +/**************************************************************************** + Module global static vars + ****************************************************************************/ + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; /* Index 0-MAX */ +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; /* ID for this card */ +static int enable[SNDRV_CARDS] = {1, [1 ... (SNDRV_CARDS - 1)] = 1}; + +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "Enable tm6000x soundcard. default enabled."); + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "Index value for tm6000x capture interface(s)."); + + +/**************************************************************************** + Module macros + ****************************************************************************/ + +MODULE_DESCRIPTION("ALSA driver module for tm5600/tm6000/tm6010 based TV cards"); +MODULE_AUTHOR("Mauro Carvalho Chehab <mchehab@redhat.com>"); +MODULE_LICENSE("GPL"); +MODULE_SUPPORTED_DEVICE("{{Trident,tm5600}," + "{{Trident,tm6000}," + "{{Trident,tm6010}"); +static unsigned int debug; +module_param(debug, int, 0644); +MODULE_PARM_DESC(debug, "enable debug messages"); + +/**************************************************************************** + Module specific funtions + ****************************************************************************/ + +/* + * BOARD Specific: Sets audio DMA + */ + +static int _tm6000_start_audio_dma(struct snd_tm6000_card *chip) +{ + struct tm6000_core *core = chip->core; + int val; + + /* Enables audio */ + val = tm6000_get_reg(core, REQ_07_SET_GET_AVREG, 0xcc, 0x0); + val |= 0x20; + tm6000_set_reg(core, REQ_07_SET_GET_AVREG, 0xcc, val); + + tm6000_set_reg(core, REQ_08_SET_GET_AVREG_BIT, 0x01, 0x80); + + return 0; +} + +/* + * BOARD Specific: Resets audio DMA + */ +static int _tm6000_stop_audio_dma(struct snd_tm6000_card *chip) +{ + struct tm6000_core *core = chip->core; + int val; + dprintk(1, "Stopping audio DMA\n"); + + /* Enables audio */ + val = tm6000_get_reg(core, REQ_07_SET_GET_AVREG, 0xcc, 0x0); + val &= ~0x20; + tm6000_set_reg(core, REQ_07_SET_GET_AVREG, 0xcc, val); + + tm6000_set_reg(core, REQ_08_SET_GET_AVREG_BIT, 0x01, 0); + + return 0; +} + +static int dsp_buffer_free(struct snd_tm6000_card *chip) +{ + BUG_ON(!chip->bufsize); + + dprintk(2, "Freeing buffer\n"); + + /* FIXME: Frees buffer */ + + chip->bufsize = 0; + + return 0; +} + +/**************************************************************************** + ALSA PCM Interface + ****************************************************************************/ + +/* + * Digital hardware definition + */ +#define DEFAULT_FIFO_SIZE 4096 + +static struct snd_pcm_hardware snd_tm6000_digital_hw = { + .info = SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP_VALID, + .formats = SNDRV_PCM_FMTBIT_S16_LE, + + .rates = SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000, + .rate_min = 44100, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .period_bytes_min = DEFAULT_FIFO_SIZE/4, + .period_bytes_max = DEFAULT_FIFO_SIZE/4, + .periods_min = 1, + .periods_max = 1024, + .buffer_bytes_max = (1024*1024), +}; + +/* + * audio pcm capture open callback + */ +static int snd_tm6000_pcm_open(struct snd_pcm_substream *substream) +{ + struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + int err; + + err = snd_pcm_hw_constraint_pow2(runtime, 0, + SNDRV_PCM_HW_PARAM_PERIODS); + if (err < 0) + goto _error; + + chip->substream = substream; + + runtime->hw = snd_tm6000_digital_hw; + + return 0; +_error: + dprintk(1, "Error opening PCM!\n"); + return err; +} + +/* + * audio close callback + */ +static int snd_tm6000_close(struct snd_pcm_substream *substream) +{ + return 0; +} + +/* + * hw_params callback + */ +static int snd_tm6000_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + chip->period_size = params_period_bytes(hw_params); + chip->num_periods = params_periods(hw_params); + chip->bufsize = chip->period_size * params_periods(hw_params); + + BUG_ON(!chip->bufsize); + + dprintk(1, "Setting buffer\n"); + + /* FIXME: Allocate buffer for audio */ + + + return 0; +} + +/* + * hw free callback + */ +static int snd_tm6000_hw_free(struct snd_pcm_substream *substream) +{ + + struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + + if (substream->runtime->dma_area) { + dsp_buffer_free(chip); + substream->runtime->dma_area = NULL; + } + + return 0; +} + +/* + * prepare callback + */ +static int snd_tm6000_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + + +/* + * trigger callback + */ +static int snd_tm6000_card_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + int err; + + spin_lock(&chip->reg_lock); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + err = _tm6000_start_audio_dma(chip); + break; + case SNDRV_PCM_TRIGGER_STOP: + err = _tm6000_stop_audio_dma(chip); + break; + default: + err = -EINVAL; + break; + } + + spin_unlock(&chip->reg_lock); + + return err; +} + +/* + * pointer callback + */ +static snd_pcm_uframes_t snd_tm6000_pointer(struct snd_pcm_substream *substream) +{ + struct snd_tm6000_card *chip = snd_pcm_substream_chip(substream); + struct snd_pcm_runtime *runtime = substream->runtime; + u16 count; + + count = atomic_read(&chip->count); + + return runtime->period_size * (count & (runtime->periods-1)); +} + +/* + * operators + */ +static struct snd_pcm_ops snd_tm6000_pcm_ops = { + .open = snd_tm6000_pcm_open, + .close = snd_tm6000_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_tm6000_hw_params, + .hw_free = snd_tm6000_hw_free, + .prepare = snd_tm6000_prepare, + .trigger = snd_tm6000_card_trigger, + .pointer = snd_tm6000_pointer, +}; + +/* + * create a PCM device + */ +static int __devinit snd_tm6000_pcm(struct snd_tm6000_card *chip, + int device, char *name) +{ + int err; + struct snd_pcm *pcm; + + err = snd_pcm_new(chip->card, name, device, 0, 1, &pcm); + if (err < 0) + return err; + pcm->private_data = chip; + strcpy(pcm->name, name); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &snd_tm6000_pcm_ops); + + return 0; +} + +/* FIXME: Control interface - How to control volume/mute? */ + +/**************************************************************************** + Basic Flow for Sound Devices + ****************************************************************************/ + +/* + * Alsa Constructor - Component probe + */ + +int tm6000_audio_init(struct tm6000_core *dev, int idx) +{ + struct snd_card *card; + struct snd_tm6000_card *chip; + int rc, len; + char component[14]; + + if (idx >= SNDRV_CARDS) + return -ENODEV; + + if (!enable[idx]) + return -ENOENT; + + rc = snd_card_create(index[idx], id[idx], THIS_MODULE, 0, &card); + if (rc < 0) { + snd_printk(KERN_ERR "cannot create card instance %d\n", idx); + return rc; + } + + chip = kzalloc(sizeof(*chip), GFP_KERNEL); + if (!chip) { + rc = -ENOMEM; + goto error; + } + + chip->core = dev; + chip->card = card; + + strcpy(card->driver, "tm6000-alsa"); + sprintf(component, "USB%04x:%04x", + le16_to_cpu(dev->udev->descriptor.idVendor), + le16_to_cpu(dev->udev->descriptor.idProduct)); + snd_component_add(card, component); + + if (dev->udev->descriptor.iManufacturer) + len = usb_string(dev->udev, + dev->udev->descriptor.iManufacturer, + card->longname, sizeof(card->longname)); + else + len = 0; + + if (len > 0) + strlcat(card->longname, " ", sizeof(card->longname)); + + strlcat(card->longname, card->shortname, sizeof(card->longname)); + + len = strlcat(card->longname, " at ", sizeof(card->longname)); + + if (len < sizeof(card->longname)) + usb_make_path(dev->udev, card->longname + len, + sizeof(card->longname) - len); + + strlcat(card->longname, + dev->udev->speed == USB_SPEED_LOW ? ", low speed" : + dev->udev->speed == USB_SPEED_FULL ? ", full speed" : + ", high speed", + sizeof(card->longname)); + + rc = snd_tm6000_pcm(chip, 0, "tm6000 Digital"); + if (rc < 0) + goto error; + + rc = snd_card_register(card); + if (rc < 0) + goto error; + + + return 0; + +error: + snd_card_free(card); + return rc; +} +EXPORT_SYMBOL_GPL(tm6000_audio_init); + diff --git a/drivers/staging/tm6000/tm6000-cards.c b/drivers/staging/tm6000/tm6000-cards.c new file mode 100644 index 000000000000..83cb4b95de05 --- /dev/null +++ b/drivers/staging/tm6000/tm6000-cards.c @@ -0,0 +1,799 @@ +/* + tm6000-cards.c - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 2 + + 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/init.h> +#include <linux/module.h> +#include <linux/pci.h> +#include <linux/delay.h> +#include <linux/i2c.h> +#include <linux/usb.h> +#include <linux/version.h> +#include <media/v4l2-common.h> +#include <media/tuner.h> +#include <media/tvaudio.h> +#include <media/i2c-addr.h> + +#include "tm6000.h" +#include "tm6000-regs.h" +#include "tuner-xc2028.h" +#include "xc5000.h" + +#define TM6000_BOARD_UNKNOWN 0 +#define TM5600_BOARD_GENERIC 1 +#define TM6000_BOARD_GENERIC 2 +#define TM6010_BOARD_GENERIC 3 +#define TM5600_BOARD_10MOONS_UT821 4 +#define TM5600_BOARD_10MOONS_UT330 5 +#define TM6000_BOARD_ADSTECH_DUAL_TV 6 +#define TM6000_BOARD_FREECOM_AND_SIMILAR 7 +#define TM6000_BOARD_ADSTECH_MINI_DUAL_TV 8 +#define TM6010_BOARD_HAUPPAUGE_900H 9 +#define TM6010_BOARD_BEHOLD_WANDER 10 +#define TM6010_BOARD_BEHOLD_VOYAGER 11 +#define TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE 12 + +#define TM6000_MAXBOARDS 16 +static unsigned int card[] = {[0 ... (TM6000_MAXBOARDS - 1)] = UNSET }; + +module_param_array(card, int, NULL, 0444); + +static unsigned long tm6000_devused; + + +struct tm6000_board { + char *name; + + struct tm6000_capabilities caps; + + enum tm6000_devtype type; /* variant of the chipset */ + int tuner_type; /* type of the tuner */ + int tuner_addr; /* tuner address */ + int demod_addr; /* demodulator address */ + int gpio_addr_tun_reset; /* GPIO used for tuner reset */ +}; + +struct tm6000_board tm6000_boards[] = { + [TM6000_BOARD_UNKNOWN] = { + .name = "Unknown tm6000 video grabber", + .caps = { + .has_tuner = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_1, + }, + [TM5600_BOARD_GENERIC] = { + .name = "Generic tm5600 board", + .type = TM5600, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0xc2 >> 1, + .caps = { + .has_tuner = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_1, + }, + [TM6000_BOARD_GENERIC] = { + .name = "Generic tm6000 board", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0xc2 >> 1, + .caps = { + .has_tuner = 1, + .has_dvb = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_1, + }, + [TM6010_BOARD_GENERIC] = { + .name = "Generic tm6010 board", + .type = TM6010, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0xc2 >> 1, + .caps = { + .has_tuner = 1, + .has_dvb = 1, + }, + .gpio_addr_tun_reset = TM6010_GPIO_4, + }, + [TM5600_BOARD_10MOONS_UT821] = { + .name = "10Moons UT 821", + .tuner_type = TUNER_XC2028, + .type = TM5600, + .tuner_addr = 0xc2 >> 1, + .caps = { + .has_tuner = 1, + .has_eeprom = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_1, + }, + [TM5600_BOARD_10MOONS_UT330] = { + .name = "10Moons UT 330", + .tuner_type = TUNER_PHILIPS_FQ1216AME_MK4, + .tuner_addr = 0xc8 >> 1, + .caps = { + .has_tuner = 1, + .has_dvb = 0, + .has_zl10353 = 0, + .has_eeprom = 1, + }, + }, + [TM6000_BOARD_ADSTECH_DUAL_TV] = { + .name = "ADSTECH Dual TV USB", + .tuner_type = TUNER_XC2028, + .tuner_addr = 0xc8 >> 1, + .caps = { + .has_tuner = 1, + .has_tda9874 = 1, + .has_dvb = 1, + .has_zl10353 = 1, + .has_eeprom = 1, + }, + }, + [TM6000_BOARD_FREECOM_AND_SIMILAR] = { + .name = "Freecom Hybrid Stick / Moka DVB-T Receiver Dual", + .tuner_type = TUNER_XC2028, /* has a XC3028 */ + .tuner_addr = 0xc2 >> 1, + .demod_addr = 0x1e >> 1, + .caps = { + .has_tuner = 1, + .has_dvb = 1, + .has_zl10353 = 1, + .has_eeprom = 0, + .has_remote = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_4, + }, + [TM6000_BOARD_ADSTECH_MINI_DUAL_TV] = { + .name = "ADSTECH Mini Dual TV USB", + .tuner_type = TUNER_XC2028, /* has a XC3028 */ + .tuner_addr = 0xc8 >> 1, + .demod_addr = 0x1e >> 1, + .caps = { + .has_tuner = 1, + .has_dvb = 1, + .has_zl10353 = 1, + .has_eeprom = 0, + }, + .gpio_addr_tun_reset = TM6000_GPIO_4, + }, + [TM6010_BOARD_HAUPPAUGE_900H] = { + .name = "Hauppauge HVR-900H", + .tuner_type = TUNER_XC2028, /* has a XC3028 */ + .tuner_addr = 0xc2 >> 1, + .demod_addr = 0x1e >> 1, + .type = TM6010, + .caps = { + .has_tuner = 1, + .has_dvb = 1, + .has_zl10353 = 1, + .has_eeprom = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_2, + }, + [TM6010_BOARD_BEHOLD_WANDER] = { + .name = "Beholder Wander DVB-T/TV/FM USB2.0", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0xc2 >> 1, + .demod_addr = 0x1e >> 1, + .type = TM6010, + .caps = { + .has_tuner = 1, + .has_dvb = 1, + .has_zl10353 = 1, + .has_eeprom = 1, + .has_remote = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_2, + }, + [TM6010_BOARD_BEHOLD_VOYAGER] = { + .name = "Beholder Voyager TV/FM USB2.0", + .tuner_type = TUNER_XC5000, + .tuner_addr = 0xc2 >> 1, + .type = TM6010, + .caps = { + .has_tuner = 1, + .has_dvb = 0, + .has_zl10353 = 0, + .has_eeprom = 1, + .has_remote = 1, + }, + .gpio_addr_tun_reset = TM6000_GPIO_2, + }, + [TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE] = { + .name = "Terratec Cinergy Hybrid XE", + .tuner_type = TUNER_XC2028, /* has a XC3028 */ + .tuner_addr = 0xc2 >> 1, + .demod_addr = 0x1e >> 1, + .type = TM6010, + .caps = { + .has_tuner = 1, + .has_dvb = 1, + .has_zl10353 = 1, + .has_eeprom = 1, + .has_remote = 1, + }, + .gpio_addr_tun_reset = TM6010_GPIO_2, + } +}; + +/* table of devices that work with this driver */ +struct usb_device_id tm6000_id_table [] = { + { USB_DEVICE(0x6000, 0x0001), .driver_info = TM5600_BOARD_10MOONS_UT821 }, + { USB_DEVICE(0x6000, 0x0002), .driver_info = TM6010_BOARD_GENERIC }, + { USB_DEVICE(0x06e1, 0xf332), .driver_info = TM6000_BOARD_ADSTECH_DUAL_TV }, + { USB_DEVICE(0x14aa, 0x0620), .driver_info = TM6000_BOARD_FREECOM_AND_SIMILAR }, + { USB_DEVICE(0x06e1, 0xb339), .driver_info = TM6000_BOARD_ADSTECH_MINI_DUAL_TV }, + { USB_DEVICE(0x2040, 0x6600), .driver_info = TM6010_BOARD_HAUPPAUGE_900H }, + { USB_DEVICE(0x6000, 0xdec0), .driver_info = TM6010_BOARD_BEHOLD_WANDER }, + { USB_DEVICE(0x6000, 0xdec1), .driver_info = TM6010_BOARD_BEHOLD_VOYAGER }, + { USB_DEVICE(0x0ccd, 0x0086), .driver_info = TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE }, + { }, +}; + +/* Tuner callback to provide the proper gpio changes needed for xc2028 */ + +int tm6000_tuner_callback(void *ptr, int component, int command, int arg) +{ + int rc=0; + struct tm6000_core *dev = ptr; + + if (dev->tuner_type!=TUNER_XC2028) + return 0; + + switch (command) { + case XC2028_RESET_CLK: + tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT, + 0x02, arg); + msleep(10); + rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, + TM6000_GPIO_CLK, 0); + if (rc<0) + return rc; + msleep(10); + rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, + TM6000_GPIO_CLK, 1); + break; + case XC2028_TUNER_RESET: + /* Reset codes during load firmware */ + switch (arg) { + case 0: + /* newer tuner can faster reset */ + switch (dev->model) { + case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, + dev->tuner_reset_gpio, 0x01); + msleep(60); + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, + dev->tuner_reset_gpio, 0x00); + msleep(75); + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, + dev->tuner_reset_gpio, 0x01); + msleep(60); + break; + default: + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, + dev->tuner_reset_gpio, 0x00); + msleep(130); + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, + dev->tuner_reset_gpio, 0x01); + msleep(130); + break; + } + break; + case 1: + tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT, + 0x02, 0x01); + msleep(10); + break; + + case 2: + rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, + TM6000_GPIO_CLK, 0); + if (rc<0) + return rc; + msleep(100); + rc=tm6000_set_reg (dev, REQ_03_SET_GET_MCU_PIN, + TM6000_GPIO_CLK, 1); + msleep(100); + break; + } + } + return (rc); +} + +int tm6000_cards_setup(struct tm6000_core *dev) +{ + int i, rc; + + /* + * Board-specific initialization sequence. Handles all GPIO + * initialization sequences that are board-specific. + * Up to now, all found devices use GPIO1 and GPIO4 at the same way. + * Probably, they're all based on some reference device. Due to that, + * there's a common routine at the end to handle those GPIO's. Devices + * that use different pinups or init sequences can just return at + * the board-specific session. + */ + switch (dev->model) { + case TM6010_BOARD_HAUPPAUGE_900H: + /* Turn xceive 3028 on */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_3, 0x01); + msleep(11); + break; + case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: + /* Turn zarlink zl10353 on */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_4, 0x00); + msleep(15); + /* Reset zarlink zl10353 */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_1, 0x00); + msleep(50); + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_1, 0x01); + msleep(15); + /* Turn zarlink zl10353 off */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_4, 0x01); + msleep(15); + /* ir ? */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_0, 0x01); + msleep(15); + /* Power led on (blue) */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_7, 0x00); + msleep(15); + /* DVB led off (orange) */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_5, 0x01); + msleep(15); + /* Turn zarlink zl10353 on */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6010_GPIO_4, 0x00); + msleep(15); + break; + default: + break; + } + + /* + * Default initialization. Most of the devices seem to use GPIO1 + * and GPIO4.on the same way, so, this handles the common sequence + * used by most devices. + * If a device uses a different sequence or different GPIO pins for + * reset, just add the code at the board-specific part + */ + for (i = 0; i < 2; i++) { + rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, + dev->tuner_reset_gpio, 0x00); + if (rc < 0) { + printk(KERN_ERR "Error %i doing GPIO1 reset\n", rc); + return rc; + } + + msleep(10); /* Just to be conservative */ + rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, + dev->tuner_reset_gpio, 0x01); + if (rc < 0) { + printk(KERN_ERR "Error %i doing GPIO1 reset\n", rc); + return rc; + } + + msleep(10); + rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_4, 0); + if (rc < 0) { + printk(KERN_ERR "Error %i doing GPIO4 reset\n", rc); + return rc; + } + + msleep(10); + rc = tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_4, 1); + if (rc < 0) { + printk(KERN_ERR "Error %i doing GPIO4 reset\n", rc); + return rc; + } + + if (!i) { + rc = tm6000_get_reg16(dev, 0x40, 0, 0); + if (rc >= 0) + printk(KERN_DEBUG "board=%d\n", rc); + } + } + + msleep(50); + + return 0; +}; + +static void tm6000_config_tuner (struct tm6000_core *dev) +{ + struct tuner_setup tun_setup; + + /* Load tuner module */ + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap, + "tuner", "tuner",dev->tuner_addr, NULL); + + memset(&tun_setup, 0, sizeof(tun_setup)); + tun_setup.type = dev->tuner_type; + tun_setup.addr = dev->tuner_addr; + tun_setup.mode_mask = T_ANALOG_TV | T_RADIO | T_DIGITAL_TV; + tun_setup.tuner_callback = tm6000_tuner_callback; + + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_type_addr, &tun_setup); + + if (dev->tuner_type == TUNER_XC2028) { + struct v4l2_priv_tun_config xc2028_cfg; + struct xc2028_ctrl ctl; + + memset(&xc2028_cfg, 0, sizeof(xc2028_cfg)); + memset (&ctl,0,sizeof(ctl)); + + ctl.input1 = 1; + ctl.read_not_reliable = 0; + ctl.msleep = 10; + ctl.demod = XC3028_FE_ZARLINK456; + ctl.vhfbw7 = 1; + ctl.uhfbw8 = 1; + xc2028_cfg.tuner = TUNER_XC2028; + xc2028_cfg.priv = &ctl; + + switch(dev->model) { + case TM6010_BOARD_HAUPPAUGE_900H: + case TM6010_BOARD_TERRATEC_CINERGY_HYBRID_XE: + ctl.fname = "xc3028L-v36.fw"; + break; + default: + if (dev->dev_type == TM6010) + ctl.fname = "xc3028-v27.fw"; + else + ctl.fname = "tm6000-xc3028.fw"; + } + + printk(KERN_INFO "Setting firmware parameters for xc2028\n"); + + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_config, + &xc2028_cfg); + } +} + +static int tm6000_init_dev(struct tm6000_core *dev) +{ + struct v4l2_frequency f; + int rc = 0; + + mutex_init(&dev->lock); + + mutex_lock(&dev->lock); + + /* Initializa board-specific data */ + dev->dev_type = tm6000_boards[dev->model].type; + dev->tuner_type = tm6000_boards[dev->model].tuner_type; + dev->tuner_addr = tm6000_boards[dev->model].tuner_addr; + dev->tuner_reset_gpio = tm6000_boards[dev->model].gpio_addr_tun_reset; + + dev->demod_addr = tm6000_boards[dev->model].demod_addr; + + dev->caps = tm6000_boards[dev->model].caps; + + /* initialize hardware */ + rc=tm6000_init (dev); + if (rc<0) + goto err; + + rc = v4l2_device_register(&dev->udev->dev, &dev->v4l2_dev); + if (rc < 0) + goto err; + + /* register i2c bus */ + rc=tm6000_i2c_register(dev); + if (rc<0) + goto err; + + /* Default values for STD and resolutions */ + dev->width = 720; + dev->height = 480; + dev->norm = V4L2_STD_PAL_M; + + /* Configure tuner */ + tm6000_config_tuner (dev); + + /* Set video standard */ + v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, dev->norm); + + /* Set tuner frequency - also loads firmware on xc2028/xc3028 */ + f.tuner = 0; + f.type = V4L2_TUNER_ANALOG_TV; + f.frequency = 3092; /* 193.25 MHz */ + dev->freq = f.frequency; + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f); + + if (dev->caps.has_tda9874) + v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_adap, + "tvaudio", "tvaudio", I2C_ADDR_TDA9874, NULL); + + /* register and initialize V4L2 */ + rc=tm6000_v4l2_register(dev); + if (rc<0) + goto err; + + if(dev->caps.has_dvb) { + dev->dvb = kzalloc(sizeof(*(dev->dvb)), GFP_KERNEL); + if(!dev->dvb) { + rc = -ENOMEM; + goto err2; + } + +#ifdef CONFIG_VIDEO_TM6000_DVB + rc = tm6000_dvb_register(dev); + if(rc < 0) { + kfree(dev->dvb); + dev->dvb = NULL; + goto err2; + } +#endif + } + mutex_unlock(&dev->lock); + return 0; + +err2: + v4l2_device_unregister(&dev->v4l2_dev); + +err: + mutex_unlock(&dev->lock); + return rc; +} + +/* high bandwidth multiplier, as encoded in highspeed endpoint descriptors */ +#define hb_mult(wMaxPacketSize) (1 + (((wMaxPacketSize) >> 11) & 0x03)) + +static void get_max_endpoint ( struct usb_device *usbdev, + char *msgtype, + struct usb_host_endpoint *curr_e, + unsigned int *maxsize, + struct usb_host_endpoint **ep ) +{ + u16 tmp = le16_to_cpu(curr_e->desc.wMaxPacketSize); + unsigned int size = tmp & 0x7ff; + + if (usbdev->speed == USB_SPEED_HIGH) + size = size * hb_mult (tmp); + + if (size>*maxsize) { + *ep = curr_e; + *maxsize = size; + printk("tm6000: %s endpoint: 0x%02x (max size=%u bytes)\n", + msgtype, curr_e->desc.bEndpointAddress, + size); + } +} + +/* + * tm6000_usb_probe() + * checks for supported devices + */ +static int tm6000_usb_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *usbdev; + struct tm6000_core *dev = NULL; + int i,rc=0; + int nr=0; + char *speed; + + + usbdev=usb_get_dev(interface_to_usbdev(interface)); + + /* Selects the proper interface */ + rc=usb_set_interface(usbdev,0,1); + if (rc<0) + goto err; + + /* Check to see next free device and mark as used */ + nr=find_first_zero_bit(&tm6000_devused,TM6000_MAXBOARDS); + if (nr >= TM6000_MAXBOARDS) { + printk ("tm6000: Supports only %i tm60xx boards.\n",TM6000_MAXBOARDS); + usb_put_dev(usbdev); + return -ENOMEM; + } + + /* Create and initialize dev struct */ + dev = kzalloc(sizeof(*dev), GFP_KERNEL); + if (dev == NULL) { + printk ("tm6000" ": out of memory!\n"); + usb_put_dev(usbdev); + return -ENOMEM; + } + spin_lock_init(&dev->slock); + + /* Increment usage count */ + tm6000_devused|=1<<nr; + snprintf(dev->name, 29, "tm6000 #%d", nr); + + dev->model=id->driver_info; + if ((card[nr]>=0) && (card[nr]<ARRAY_SIZE(tm6000_boards))) { + dev->model=card[nr]; + } + + dev->udev= usbdev; + dev->devno=nr; + + switch (usbdev->speed) { + case USB_SPEED_LOW: + speed = "1.5"; + break; + case USB_SPEED_UNKNOWN: + case USB_SPEED_FULL: + speed = "12"; + break; + case USB_SPEED_HIGH: + speed = "480"; + break; + default: + speed = "unknown"; + } + + + + /* Get endpoints */ + for (i = 0; i < interface->num_altsetting; i++) { + int ep; + + for (ep = 0; ep < interface->altsetting[i].desc.bNumEndpoints; ep++) { + struct usb_host_endpoint *e; + int dir_out; + + e = &interface->altsetting[i].endpoint[ep]; + + dir_out = ((e->desc.bEndpointAddress & + USB_ENDPOINT_DIR_MASK) == USB_DIR_OUT); + + printk("tm6000: alt %d, interface %i, class %i\n", + i, + interface->altsetting[i].desc.bInterfaceNumber, + interface->altsetting[i].desc.bInterfaceClass); + + switch (e->desc.bmAttributes) { + case USB_ENDPOINT_XFER_BULK: + if (!dir_out) { + get_max_endpoint (usbdev, "Bulk IN", e, + &dev->max_bulk_in, + &dev->bulk_in); + } else { + get_max_endpoint (usbdev, "Bulk OUT", e, + &dev->max_bulk_out, + &dev->bulk_out); + } + break; + case USB_ENDPOINT_XFER_ISOC: + if (!dir_out) { + get_max_endpoint (usbdev, "ISOC IN", e, + &dev->max_isoc_in, + &dev->isoc_in); + } else { + get_max_endpoint (usbdev, "ISOC OUT", e, + &dev->max_isoc_out, + &dev->isoc_out); + } + break; + } + } + } + + + printk("tm6000: New video device @ %s Mbps (%04x:%04x, ifnum %d)\n", + speed, + le16_to_cpu(dev->udev->descriptor.idVendor), + le16_to_cpu(dev->udev->descriptor.idProduct), + interface->altsetting->desc.bInterfaceNumber); + +/* check if the the device has the iso in endpoint at the correct place */ + if (!dev->isoc_in) { + printk("tm6000: probing error: no IN ISOC endpoint!\n"); + rc= -ENODEV; + + goto err; + } + + /* save our data pointer in this interface device */ + usb_set_intfdata(interface, dev); + + printk("tm6000: Found %s\n", tm6000_boards[dev->model].name); + + rc=tm6000_init_dev(dev); + + if (rc<0) + goto err; + + return 0; + +err: + printk("tm6000: Error %d while registering\n", rc); + + tm6000_devused&=~(1<<nr); + usb_put_dev(usbdev); + + kfree(dev); + return rc; +} + +/* + * tm6000_usb_disconnect() + * called when the device gets diconencted + * video device will be unregistered on v4l2_close in case it is still open + */ +static void tm6000_usb_disconnect(struct usb_interface *interface) +{ + struct tm6000_core *dev = usb_get_intfdata(interface); + usb_set_intfdata(interface, NULL); + + if (!dev) + return; + + printk("tm6000: disconnecting %s\n", dev->name); + + mutex_lock(&dev->lock); + +#ifdef CONFIG_VIDEO_TM6000_DVB + if(dev->dvb) { + tm6000_dvb_unregister(dev); + kfree(dev->dvb); + } +#endif + + tm6000_v4l2_unregister(dev); + + tm6000_i2c_unregister(dev); + + v4l2_device_unregister(&dev->v4l2_dev); + +// wake_up_interruptible_all(&dev->open); + + dev->state |= DEV_DISCONNECTED; + + usb_put_dev(dev->udev); + + mutex_unlock(&dev->lock); + kfree(dev); +} + +static struct usb_driver tm6000_usb_driver = { + .name = "tm6000", + .probe = tm6000_usb_probe, + .disconnect = tm6000_usb_disconnect, + .id_table = tm6000_id_table, +}; + +static int __init tm6000_module_init(void) +{ + int result; + + printk(KERN_INFO "tm6000" " v4l2 driver version %d.%d.%d loaded\n", + (TM6000_VERSION >> 16) & 0xff, + (TM6000_VERSION >> 8) & 0xff, TM6000_VERSION & 0xff); + + /* register this driver with the USB subsystem */ + result = usb_register(&tm6000_usb_driver); + if (result) + printk("tm6000" + " usb_register failed. Error number %d.\n", result); + + return result; +} + +static void __exit tm6000_module_exit(void) +{ + /* deregister at USB subsystem */ + usb_deregister(&tm6000_usb_driver); +} + +module_init(tm6000_module_init); +module_exit(tm6000_module_exit); + +MODULE_DESCRIPTION("Trident TVMaster TM5600/TM6000/TM6010 USB2 adapter"); +MODULE_AUTHOR("Mauro Carvalho Chehab"); +MODULE_LICENSE("GPL"); diff --git a/drivers/staging/tm6000/tm6000-core.c b/drivers/staging/tm6000/tm6000-core.c new file mode 100644 index 000000000000..bf40aa833ed0 --- /dev/null +++ b/drivers/staging/tm6000/tm6000-core.c @@ -0,0 +1,494 @@ +/* + tm6000-core.c - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + + Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + - DVB-T support + + 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 + + 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/kernel.h> +#include <linux/usb.h> +#include <linux/i2c.h> +#include "tm6000.h" +#include "tm6000-regs.h" +#include <media/v4l2-common.h> +#include <media/tuner.h> + +#define USB_TIMEOUT 5*HZ /* ms */ + +int tm6000_read_write_usb (struct tm6000_core *dev, u8 req_type, u8 req, + u16 value, u16 index, u8 *buf, u16 len) +{ + int ret, i; + unsigned int pipe; + static int ini=0, last=0, n=0; + u8 *data=NULL; + + if (len) + data = kzalloc(len, GFP_KERNEL); + + + if (req_type & USB_DIR_IN) + pipe=usb_rcvctrlpipe(dev->udev, 0); + else { + pipe=usb_sndctrlpipe(dev->udev, 0); + memcpy(data, buf, len); + } + + if (tm6000_debug & V4L2_DEBUG_I2C) { + if (!ini) + last=ini=jiffies; + + printk("%06i (dev %p, pipe %08x): ", n, dev->udev, pipe); + + printk( "%s: %06u ms %06u ms %02x %02x %02x %02x %02x %02x %02x %02x ", + (req_type & USB_DIR_IN)?" IN":"OUT", + jiffies_to_msecs(jiffies-last), + jiffies_to_msecs(jiffies-ini), + req_type, req,value&0xff,value>>8, index&0xff, index>>8, + len&0xff, len>>8); + last=jiffies; + n++; + + if ( !(req_type & USB_DIR_IN) ) { + printk(">>> "); + for (i=0;i<len;i++) { + printk(" %02x",buf[i]); + } + printk("\n"); + } + } + + ret = usb_control_msg(dev->udev, pipe, req, req_type, value, index, data, + len, USB_TIMEOUT); + + if (req_type & USB_DIR_IN) + memcpy(buf, data, len); + + if (tm6000_debug & V4L2_DEBUG_I2C) { + if (ret<0) { + if (req_type & USB_DIR_IN) + printk("<<< (len=%d)\n",len); + + printk("%s: Error #%d\n", __FUNCTION__, ret); + } else if (req_type & USB_DIR_IN) { + printk("<<< "); + for (i=0;i<len;i++) { + printk(" %02x",buf[i]); + } + printk("\n"); + } + } + + kfree(data); + + msleep(5); + + return ret; +} + +int tm6000_set_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index) +{ + return + tm6000_read_write_usb (dev, USB_DIR_OUT | USB_TYPE_VENDOR, + req, value, index, NULL, 0); +} +EXPORT_SYMBOL_GPL(tm6000_set_reg); + +int tm6000_get_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index) +{ + int rc; + u8 buf[1]; + + rc=tm6000_read_write_usb (dev, USB_DIR_IN | USB_TYPE_VENDOR, req, + value, index, buf, 1); + + if (rc<0) + return rc; + + return *buf; +} +EXPORT_SYMBOL_GPL(tm6000_get_reg); + +int tm6000_get_reg16 (struct tm6000_core *dev, u8 req, u16 value, u16 index) +{ + int rc; + u8 buf[2]; + + rc=tm6000_read_write_usb (dev, USB_DIR_IN | USB_TYPE_VENDOR, req, + value, index, buf, 2); + + if (rc<0) + return rc; + + return buf[1]|buf[0]<<8; +} + +void tm6000_set_fourcc_format(struct tm6000_core *dev) +{ + if (dev->dev_type == TM6010) { + if (dev->fourcc == V4L2_PIX_FMT_UYVY) + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc1, 0xfc); + else + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc1, 0xfd); + } else { + if (dev->fourcc == V4L2_PIX_FMT_UYVY) + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc1, 0xd0); + else + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xc1, 0x90); + } +} + +int tm6000_init_analog_mode (struct tm6000_core *dev) +{ + if (dev->dev_type == TM6010) { + int val; + + /* Enable video */ + val = tm6000_get_reg(dev, REQ_07_SET_GET_AVREG, 0xcc, 0); + val |= 0x60; + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xcc, val); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xfe, 0xcf); + + } else { + /* Enables soft reset */ + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0x3f, 0x01); + + if (dev->scaler) { + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xc0, 0x20); + } else { + /* Enable Hfilter and disable TS Drop err */ + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xc0, 0x80); + } + + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xc3, 0x88); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xda, 0x23); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xd1, 0xc0); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xd2, 0xd8); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xd6, 0x06); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xdf, 0x1f); + + /* AP Software reset */ + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xff, 0x08); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xff, 0x00); + + tm6000_set_fourcc_format(dev); + + /* Disables soft reset */ + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0x3f, 0x00); + + /* E3: Select input 0 - TV tuner */ + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xe3, 0x00); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xeb, 0x60); + + /* This controls input */ + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_2, 0x0); + tm6000_set_reg(dev, REQ_03_SET_GET_MCU_PIN, TM6000_GPIO_3, 0x01); + } + msleep(20); + + /* Tuner firmware can now be loaded */ + + /*FIXME: Hack!!! */ + struct v4l2_frequency f; + mutex_lock(&dev->lock); + f.frequency=dev->freq; + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, &f); + mutex_unlock(&dev->lock); + + msleep(100); + tm6000_set_standard (dev, &dev->norm); + tm6000_set_audio_bitrate (dev,48000); + + return 0; +} + +int tm6000_init_digital_mode (struct tm6000_core *dev) +{ + if (dev->dev_type == TM6010) { + int val; + u8 buf[2]; + + /* digital init */ + val = tm6000_get_reg(dev, REQ_07_SET_GET_AVREG, 0xcc, 0); + val &= ~0x60; + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xcc, val); + val = tm6000_get_reg(dev, REQ_07_SET_GET_AVREG, 0xc0, 0); + val |= 0x40; + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xc0, val); + tm6000_set_reg(dev, REQ_07_SET_GET_AVREG, 0xfe, 0x28); + tm6000_set_reg(dev, REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xfc); + tm6000_set_reg(dev, REQ_08_SET_GET_AVREG_BIT, 0xe6, 0xff); + tm6000_set_reg(dev, REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfe); + tm6000_read_write_usb (dev, 0xc0, 0x0e, 0x00c2, 0x0008, buf, 2); + printk (KERN_INFO "buf %#x %#x \n", buf[0], buf[1]); + + + } else { + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00ff, 0x08); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00ff, 0x00); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x003f, 0x01); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00df, 0x08); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00e2, 0x0c); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00e8, 0xff); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00eb, 0xd8); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00c0, 0x40); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00c1, 0xd0); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00c3, 0x09); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00da, 0x37); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00d1, 0xd8); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00d2, 0xc0); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00d6, 0x60); + + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00e2, 0x0c); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00e8, 0xff); + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x00eb, 0x08); + msleep(50); + + tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x00); + msleep(50); + tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x01); + msleep(50); + tm6000_set_reg (dev, REQ_04_EN_DISABLE_MCU_INT, 0x0020, 0x00); + msleep(100); + } + return 0; +} + +struct reg_init { + u8 req; + u8 reg; + u8 val; +}; + +/* The meaning of those initializations are unknown */ +struct reg_init tm6000_init_tab[] = { + /* REG VALUE */ + { REQ_07_SET_GET_AVREG, 0xdf, 0x1f }, + { REQ_07_SET_GET_AVREG, 0xff, 0x08 }, + { REQ_07_SET_GET_AVREG, 0xff, 0x00 }, + { REQ_07_SET_GET_AVREG, 0xd5, 0x4f }, + { REQ_07_SET_GET_AVREG, 0xda, 0x23 }, + { REQ_07_SET_GET_AVREG, 0xdb, 0x08 }, + { REQ_07_SET_GET_AVREG, 0xe2, 0x00 }, + { REQ_07_SET_GET_AVREG, 0xe3, 0x10 }, + { REQ_07_SET_GET_AVREG, 0xe5, 0x00 }, + { REQ_07_SET_GET_AVREG, 0xe8, 0x00 }, + { REQ_07_SET_GET_AVREG, 0xeb, 0x64 }, /* 48000 bits/sample, external input */ + { REQ_07_SET_GET_AVREG, 0xee, 0xc2 }, + { REQ_07_SET_GET_AVREG, 0x3f, 0x01 }, /* Start of soft reset */ + { REQ_07_SET_GET_AVREG, 0x00, 0x00 }, + { REQ_07_SET_GET_AVREG, 0x01, 0x07 }, + { REQ_07_SET_GET_AVREG, 0x02, 0x5f }, + { REQ_07_SET_GET_AVREG, 0x03, 0x00 }, + { REQ_07_SET_GET_AVREG, 0x05, 0x64 }, + { REQ_07_SET_GET_AVREG, 0x07, 0x01 }, + { REQ_07_SET_GET_AVREG, 0x08, 0x82 }, + { REQ_07_SET_GET_AVREG, 0x09, 0x36 }, + { REQ_07_SET_GET_AVREG, 0x0a, 0x50 }, + { REQ_07_SET_GET_AVREG, 0x0c, 0x6a }, + { REQ_07_SET_GET_AVREG, 0x11, 0xc9 }, + { REQ_07_SET_GET_AVREG, 0x12, 0x07 }, + { REQ_07_SET_GET_AVREG, 0x13, 0x3b }, + { REQ_07_SET_GET_AVREG, 0x14, 0x47 }, + { REQ_07_SET_GET_AVREG, 0x15, 0x6f }, + { REQ_07_SET_GET_AVREG, 0x17, 0xcd }, + { REQ_07_SET_GET_AVREG, 0x18, 0x1e }, + { REQ_07_SET_GET_AVREG, 0x19, 0x8b }, + { REQ_07_SET_GET_AVREG, 0x1a, 0xa2 }, + { REQ_07_SET_GET_AVREG, 0x1b, 0xe9 }, + { REQ_07_SET_GET_AVREG, 0x1c, 0x1c }, + { REQ_07_SET_GET_AVREG, 0x1d, 0xcc }, + { REQ_07_SET_GET_AVREG, 0x1e, 0xcc }, + { REQ_07_SET_GET_AVREG, 0x1f, 0xcd }, + { REQ_07_SET_GET_AVREG, 0x20, 0x3c }, + { REQ_07_SET_GET_AVREG, 0x21, 0x3c }, + { REQ_07_SET_GET_AVREG, 0x2d, 0x48 }, + { REQ_07_SET_GET_AVREG, 0x2e, 0x88 }, + { REQ_07_SET_GET_AVREG, 0x30, 0x22 }, + { REQ_07_SET_GET_AVREG, 0x31, 0x61 }, + { REQ_07_SET_GET_AVREG, 0x32, 0x74 }, + { REQ_07_SET_GET_AVREG, 0x33, 0x1c }, + { REQ_07_SET_GET_AVREG, 0x34, 0x74 }, + { REQ_07_SET_GET_AVREG, 0x35, 0x1c }, + { REQ_07_SET_GET_AVREG, 0x36, 0x7a }, + { REQ_07_SET_GET_AVREG, 0x37, 0x26 }, + { REQ_07_SET_GET_AVREG, 0x38, 0x40 }, + { REQ_07_SET_GET_AVREG, 0x39, 0x0a }, + { REQ_07_SET_GET_AVREG, 0x42, 0x55 }, + { REQ_07_SET_GET_AVREG, 0x51, 0x11 }, + { REQ_07_SET_GET_AVREG, 0x55, 0x01 }, + { REQ_07_SET_GET_AVREG, 0x57, 0x02 }, + { REQ_07_SET_GET_AVREG, 0x58, 0x35 }, + { REQ_07_SET_GET_AVREG, 0x59, 0xa0 }, + { REQ_07_SET_GET_AVREG, 0x80, 0x15 }, + { REQ_07_SET_GET_AVREG, 0x82, 0x42 }, + { REQ_07_SET_GET_AVREG, 0xc1, 0xd0 }, + { REQ_07_SET_GET_AVREG, 0xc3, 0x88 }, + { REQ_07_SET_GET_AVREG, 0x3f, 0x00 }, /* End of the soft reset */ + { REQ_05_SET_GET_USBREG, 0x18, 0x00 }, +}; + +struct reg_init tm6010_init_tab[] = { + { REQ_07_SET_GET_AVREG, 0xc0, 0x00 }, + { REQ_07_SET_GET_AVREG, 0xc4, 0xa0 }, + { REQ_07_SET_GET_AVREG, 0xc6, 0x40 }, + { REQ_07_SET_GET_AVREG, 0xca, 0x31 }, + { REQ_07_SET_GET_AVREG, 0xcc, 0xe1 }, + { REQ_07_SET_GET_AVREG, 0xe0, 0x03 }, + { REQ_07_SET_GET_AVREG, 0xfe, 0x7f }, + + { REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0 }, + { REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf4 }, + { REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf8 }, + { REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x00 }, + { REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf2 }, + { REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xf0 }, + { REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2 }, + { REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x60 }, + { REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc }, + + { REQ_07_SET_GET_AVREG, 0x3f, 0x01 }, + { REQ_07_SET_GET_AVREG, 0x00, 0x00 }, + { REQ_07_SET_GET_AVREG, 0x01, 0x07 }, + { REQ_07_SET_GET_AVREG, 0x02, 0x5f }, + { REQ_07_SET_GET_AVREG, 0x03, 0x00 }, + { REQ_07_SET_GET_AVREG, 0x05, 0x64 }, + { REQ_07_SET_GET_AVREG, 0x07, 0x01 }, + { REQ_07_SET_GET_AVREG, 0x08, 0x82 }, + { REQ_07_SET_GET_AVREG, 0x09, 0x36 }, + { REQ_07_SET_GET_AVREG, 0x0a, 0x50 }, + { REQ_07_SET_GET_AVREG, 0x0c, 0x6a }, + { REQ_07_SET_GET_AVREG, 0x11, 0xc9 }, + { REQ_07_SET_GET_AVREG, 0x12, 0x07 }, + { REQ_07_SET_GET_AVREG, 0x13, 0x3b }, + { REQ_07_SET_GET_AVREG, 0x14, 0x47 }, + { REQ_07_SET_GET_AVREG, 0x15, 0x6f }, + { REQ_07_SET_GET_AVREG, 0x17, 0xcd }, + { REQ_07_SET_GET_AVREG, 0x18, 0x1e }, + { REQ_07_SET_GET_AVREG, 0x19, 0x8b }, + { REQ_07_SET_GET_AVREG, 0x1a, 0xa2 }, + { REQ_07_SET_GET_AVREG, 0x1b, 0xe9 }, + { REQ_07_SET_GET_AVREG, 0x1c, 0x1c }, + { REQ_07_SET_GET_AVREG, 0x1d, 0xcc }, + { REQ_07_SET_GET_AVREG, 0x1e, 0xcc }, + { REQ_07_SET_GET_AVREG, 0x1f, 0xcd }, + { REQ_07_SET_GET_AVREG, 0x20, 0x3c }, + { REQ_07_SET_GET_AVREG, 0x21, 0x3c }, + { REQ_07_SET_GET_AVREG, 0x2d, 0x48 }, + { REQ_07_SET_GET_AVREG, 0x2e, 0x88 }, + { REQ_07_SET_GET_AVREG, 0x30, 0x22 }, + { REQ_07_SET_GET_AVREG, 0x31, 0x61 }, + { REQ_07_SET_GET_AVREG, 0x32, 0x74 }, + { REQ_07_SET_GET_AVREG, 0x33, 0x1c }, + { REQ_07_SET_GET_AVREG, 0x34, 0x74 }, + { REQ_07_SET_GET_AVREG, 0x35, 0x1c }, + { REQ_07_SET_GET_AVREG, 0x36, 0x7a }, + { REQ_07_SET_GET_AVREG, 0x37, 0x26 }, + { REQ_07_SET_GET_AVREG, 0x38, 0x40 }, + { REQ_07_SET_GET_AVREG, 0x39, 0x0a }, + { REQ_07_SET_GET_AVREG, 0x42, 0x55 }, + { REQ_07_SET_GET_AVREG, 0x51, 0x11 }, + { REQ_07_SET_GET_AVREG, 0x55, 0x01 }, + { REQ_07_SET_GET_AVREG, 0x57, 0x02 }, + { REQ_07_SET_GET_AVREG, 0x58, 0x35 }, + { REQ_07_SET_GET_AVREG, 0x59, 0xa0 }, + { REQ_07_SET_GET_AVREG, 0x80, 0x15 }, + { REQ_07_SET_GET_AVREG, 0x82, 0x42 }, + { REQ_07_SET_GET_AVREG, 0xc1, 0xd0 }, + { REQ_07_SET_GET_AVREG, 0xc3, 0x88 }, + { REQ_07_SET_GET_AVREG, 0x3f, 0x00 }, + + { REQ_05_SET_GET_USBREG, 0x18, 0x00 }, + + { REQ_07_SET_GET_AVREG, 0xdc, 0xaa }, + { REQ_07_SET_GET_AVREG, 0xdd, 0x30 }, + { REQ_07_SET_GET_AVREG, 0xde, 0x20 }, + { REQ_07_SET_GET_AVREG, 0xdf, 0xd0 }, + { REQ_04_EN_DISABLE_MCU_INT, 0x02, 0x00 }, + { REQ_07_SET_GET_AVREG, 0xd8, 0x2f }, + + /* set remote wakeup key:any key wakeup */ + { REQ_07_SET_GET_AVREG, 0xe5, 0xfe }, + { REQ_07_SET_GET_AVREG, 0xda, 0xff }, +}; + +int tm6000_init (struct tm6000_core *dev) +{ + int board, rc=0, i, size; + struct reg_init *tab; + + if (dev->dev_type == TM6010) { + tab = tm6010_init_tab; + size = ARRAY_SIZE(tm6010_init_tab); + } else { + tab = tm6000_init_tab; + size = ARRAY_SIZE(tm6000_init_tab); + } + + /* Load board's initialization table */ + for (i=0; i< size; i++) { + rc= tm6000_set_reg (dev, tab[i].req, tab[i].reg, tab[i].val); + if (rc<0) { + printk (KERN_ERR "Error %i while setting req %d, " + "reg %d to value %d\n", rc, + tab[i].req,tab[i].reg, tab[i].val); + return rc; + } + } + + msleep(5); /* Just to be conservative */ + + /* Check board version - maybe 10Moons specific */ + board=tm6000_get_reg16 (dev, 0x40, 0, 0); + if (board >=0) { + printk (KERN_INFO "Board version = 0x%04x\n",board); + } else { + printk (KERN_ERR "Error %i while retrieving board version\n",board); + } + + rc = tm6000_cards_setup(dev); + + return rc; +} + +int tm6000_set_audio_bitrate(struct tm6000_core *dev, int bitrate) +{ + int val; + + val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, 0x0); +printk("Original value=%d\n",val); + if (val<0) + return val; + + val &= 0x0f; /* Preserve the audio input control bits */ + switch (bitrate) { + case 44100: + val|=0xd0; + dev->audio_bitrate=bitrate; + break; + case 48000: + val|=0x60; + dev->audio_bitrate=bitrate; + break; + } + val=tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0xeb, val); + + return val; +} +EXPORT_SYMBOL_GPL(tm6000_set_audio_bitrate); diff --git a/drivers/staging/tm6000/tm6000-dvb.c b/drivers/staging/tm6000/tm6000-dvb.c new file mode 100644 index 000000000000..1072206f2a71 --- /dev/null +++ b/drivers/staging/tm6000/tm6000-dvb.c @@ -0,0 +1,346 @@ +/* + tm6000-dvb.c - dvb-t support for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2007 Michel Ludwig <michel.ludwig@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 version 2 + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/usb.h> + +#include "tm6000.h" +#include "tm6000-regs.h" + +#include "zl10353.h" + +#include <media/tuner.h> + +#include "tuner-xc2028.h" + +static void inline print_err_status (struct tm6000_core *dev, + int packet, int status) +{ + char *errmsg = "Unknown"; + + switch(status) { + case -ENOENT: + errmsg = "unlinked synchronuously"; + break; + case -ECONNRESET: + errmsg = "unlinked asynchronuously"; + break; + case -ENOSR: + errmsg = "Buffer error (overrun)"; + break; + case -EPIPE: + errmsg = "Stalled (device not responding)"; + break; + case -EOVERFLOW: + errmsg = "Babble (bad cable?)"; + break; + case -EPROTO: + errmsg = "Bit-stuff error (bad cable?)"; + break; + case -EILSEQ: + errmsg = "CRC/Timeout (could be anything)"; + break; + case -ETIME: + errmsg = "Device does not respond"; + break; + } + if (packet<0) { + dprintk(dev, 1, "URB status %d [%s].\n", + status, errmsg); + } else { + dprintk(dev, 1, "URB packet %d, status %d [%s].\n", + packet, status, errmsg); + } +} + +static void tm6000_urb_received(struct urb *urb) +{ + int ret; + struct tm6000_core* dev = urb->context; + + if(urb->status != 0) { + print_err_status (dev,0,urb->status); + } + else if(urb->actual_length>0){ + dvb_dmx_swfilter(&dev->dvb->demux, urb->transfer_buffer, + urb->actual_length); + } + + if(dev->dvb->streams > 0) { + ret = usb_submit_urb(urb, GFP_ATOMIC); + if(ret < 0) { + printk(KERN_ERR "tm6000: error %s\n", __FUNCTION__); + kfree(urb->transfer_buffer); + usb_free_urb(urb); + } + } +} + +int tm6000_start_stream(struct tm6000_core *dev) +{ + int ret; + unsigned int pipe, size; + struct tm6000_dvb *dvb = dev->dvb; + + printk(KERN_INFO "tm6000: got start stream request %s\n",__FUNCTION__); + + tm6000_init_digital_mode(dev); + + dvb->bulk_urb = usb_alloc_urb(0, GFP_KERNEL); + if(dvb->bulk_urb == NULL) { + printk(KERN_ERR "tm6000: couldn't allocate urb\n"); + return -ENOMEM; + } + + pipe = usb_rcvbulkpipe(dev->udev, dev->bulk_in->desc.bEndpointAddress + & USB_ENDPOINT_NUMBER_MASK); + + size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); + size = size * 15; /* 512 x 8 or 12 or 15 */ + + dvb->bulk_urb->transfer_buffer = kzalloc(size, GFP_KERNEL); + if(dvb->bulk_urb->transfer_buffer == NULL) { + usb_free_urb(dvb->bulk_urb); + printk(KERN_ERR "tm6000: couldn't allocate transfer buffer!\n"); + return -ENOMEM; + } + + usb_fill_bulk_urb(dvb->bulk_urb, dev->udev, pipe, + dvb->bulk_urb->transfer_buffer, + size, + tm6000_urb_received, dev); + + ret = usb_clear_halt(dev->udev, pipe); + if(ret < 0) { + printk(KERN_ERR "tm6000: error %i in %s during pipe reset\n",ret,__FUNCTION__); + return ret; + } + else { + printk(KERN_ERR "tm6000: pipe resetted\n"); + } + +/* mutex_lock(&tm6000_driver.open_close_mutex); */ + ret = usb_submit_urb(dvb->bulk_urb, GFP_KERNEL); + +/* mutex_unlock(&tm6000_driver.open_close_mutex); */ + if (ret) { + printk(KERN_ERR "tm6000: submit of urb failed (error=%i)\n",ret); + + kfree(dvb->bulk_urb->transfer_buffer); + usb_free_urb(dvb->bulk_urb); + return ret; + } + + return 0; +} + +void tm6000_stop_stream(struct tm6000_core *dev) +{ + struct tm6000_dvb *dvb = dev->dvb; + + if(dvb->bulk_urb) { + printk (KERN_INFO "urb killing\n"); + usb_kill_urb(dvb->bulk_urb); + printk (KERN_INFO "urb buffer free\n"); + kfree(dvb->bulk_urb->transfer_buffer); + usb_free_urb(dvb->bulk_urb); + dvb->bulk_urb = NULL; + } +} + +int tm6000_start_feed(struct dvb_demux_feed *feed) +{ + struct dvb_demux *demux = feed->demux; + struct tm6000_core *dev = demux->priv; + struct tm6000_dvb *dvb = dev->dvb; + printk(KERN_INFO "tm6000: got start feed request %s\n",__FUNCTION__); + + mutex_lock(&dvb->mutex); + if(dvb->streams == 0) { + dvb->streams = 1; +/* mutex_init(&tm6000_dev->streming_mutex); */ + tm6000_start_stream(dev); + } + else { + ++(dvb->streams); + } + mutex_unlock(&dvb->mutex); + + return 0; +} + +int tm6000_stop_feed(struct dvb_demux_feed *feed) { + struct dvb_demux *demux = feed->demux; + struct tm6000_core *dev = demux->priv; + struct tm6000_dvb *dvb = dev->dvb; + + printk(KERN_INFO "tm6000: got stop feed request %s\n",__FUNCTION__); + + mutex_lock(&dvb->mutex); + + printk (KERN_INFO "stream %#x\n", dvb->streams); + --(dvb->streams); + if(dvb->streams == 0) { + printk (KERN_INFO "stop stream\n"); + tm6000_stop_stream(dev); +/* mutex_destroy(&tm6000_dev->streaming_mutex); */ + } + mutex_unlock(&dvb->mutex); +/* mutex_destroy(&tm6000_dev->streaming_mutex); */ + + return 0; +} + +int tm6000_dvb_attach_frontend(struct tm6000_core *dev) +{ + struct tm6000_dvb *dvb = dev->dvb; + + if(dev->caps.has_zl10353) { + struct zl10353_config config = + {.demod_address = dev->demod_addr, + .no_tuner = 1, + .parallel_ts = 1, + .if2 = 45700, + .disable_i2c_gate_ctrl = 1, + }; + + dvb->frontend = dvb_attach(zl10353_attach, &config, + &dev->i2c_adap); + } + else { + printk(KERN_ERR "tm6000: no frontend defined for the device!\n"); + return -1; + } + + return (!dvb->frontend) ? -1 : 0; +} + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); + +int tm6000_dvb_register(struct tm6000_core *dev) +{ + int ret = -1; + struct tm6000_dvb *dvb = dev->dvb; + + mutex_init(&dvb->mutex); + + dvb->streams = 0; + + /* attach the frontend */ + ret = tm6000_dvb_attach_frontend(dev); + if(ret < 0) { + printk(KERN_ERR "tm6000: couldn't attach the frontend!\n"); + goto err; + } + + ret = dvb_register_adapter(&dvb->adapter, "Trident TVMaster 6000 DVB-T", + THIS_MODULE, &dev->udev->dev, adapter_nr); + dvb->adapter.priv = dev; + + if (dvb->frontend) { + struct xc2028_config cfg = { + .i2c_adap = &dev->i2c_adap, + .i2c_addr = dev->tuner_addr, + }; + + dvb->frontend->callback = tm6000_tuner_callback; + ret = dvb_register_frontend(&dvb->adapter, dvb->frontend); + if (ret < 0) { + printk(KERN_ERR + "tm6000: couldn't register frontend\n"); + goto adapter_err; + } + + if (!dvb_attach(xc2028_attach, dvb->frontend, &cfg)) { + printk(KERN_ERR "tm6000: couldn't register " + "frontend (xc3028)\n"); + ret = -EINVAL; + goto frontend_err; + } + printk(KERN_INFO "tm6000: XC2028/3028 asked to be " + "attached to frontend!\n"); + } else { + printk(KERN_ERR "tm6000: no frontend found\n"); + } + + dvb->demux.dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING + | DMX_MEMORY_BASED_FILTERING; + dvb->demux.priv = dev; + dvb->demux.filternum = 8; + dvb->demux.feednum = 8; + dvb->demux.start_feed = tm6000_start_feed; + dvb->demux.stop_feed = tm6000_stop_feed; + dvb->demux.write_to_decoder = NULL; + ret = dvb_dmx_init(&dvb->demux); + if(ret < 0) { + printk("tm6000: dvb_dmx_init failed (errno = %d)\n", ret); + goto frontend_err; + } + + dvb->dmxdev.filternum = dev->dvb->demux.filternum; + dvb->dmxdev.demux = &dev->dvb->demux.dmx; + dvb->dmxdev.capabilities = 0; + + ret = dvb_dmxdev_init(&dvb->dmxdev, &dvb->adapter); + if(ret < 0) { + printk("tm6000: dvb_dmxdev_init failed (errno = %d)\n", ret); + goto dvb_dmx_err; + } + + return 0; + +dvb_dmx_err: + dvb_dmx_release(&dvb->demux); +frontend_err: + if(dvb->frontend) { + dvb_frontend_detach(dvb->frontend); + dvb_unregister_frontend(dvb->frontend); + } +adapter_err: + dvb_unregister_adapter(&dvb->adapter); +err: + return ret; +} + +void tm6000_dvb_unregister(struct tm6000_core *dev) +{ + struct tm6000_dvb *dvb = dev->dvb; + + if(dvb->bulk_urb != NULL) { + struct urb *bulk_urb = dvb->bulk_urb; + + kfree(bulk_urb->transfer_buffer); + bulk_urb->transfer_buffer = NULL; + usb_unlink_urb(bulk_urb); + usb_free_urb(bulk_urb); + } + +/* mutex_lock(&tm6000_driver.open_close_mutex); */ + if(dvb->frontend) { + dvb_frontend_detach(dvb->frontend); + dvb_unregister_frontend(dvb->frontend); + } + + dvb_dmxdev_release(&dvb->dmxdev); + dvb_dmx_release(&dvb->demux); + dvb_unregister_adapter(&dvb->adapter); + mutex_destroy(&dvb->mutex); +/* mutex_unlock(&tm6000_driver.open_close_mutex); */ + +} diff --git a/drivers/staging/tm6000/tm6000-i2c.c b/drivers/staging/tm6000/tm6000-i2c.c new file mode 100644 index 000000000000..ec4c9381c14a --- /dev/null +++ b/drivers/staging/tm6000/tm6000-i2c.c @@ -0,0 +1,297 @@ +/* + tm6000-i2c.c - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + + Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + - Fix SMBus Read Byte command + + 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 + + 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/kernel.h> +#include <linux/usb.h> +#include <linux/i2c.h> + +#include "tm6000.h" +#include "tm6000-regs.h" +#include <media/v4l2-common.h> +#include <media/tuner.h> +#include "tuner-xc2028.h" + + +/*FIXME: Hack to avoid needing to patch i2c-id.h */ +#define I2C_HW_B_TM6000 I2C_HW_B_EM28XX +/* ----------------------------------------------------------- */ + +static unsigned int i2c_debug = 0; +module_param(i2c_debug, int, 0644); +MODULE_PARM_DESC(i2c_debug, "enable debug messages [i2c]"); + +#define i2c_dprintk(lvl,fmt, args...) if (i2c_debug>=lvl) do{ \ + printk(KERN_DEBUG "%s at %s: " fmt, \ + dev->name, __FUNCTION__ , ##args); } while (0) + +static int tm6000_i2c_send_regs(struct tm6000_core *dev, unsigned char addr, + __u8 reg, char *buf, int len) +{ + return tm6000_read_write_usb(dev, USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, buf, len); +} + +/* Generic read - doesn't work fine with 16bit registers */ +static int tm6000_i2c_recv_regs(struct tm6000_core *dev, unsigned char addr, + __u8 reg, char *buf, int len) +{ + int rc; + u8 b[2]; + + if ((dev->caps.has_zl10353) && (dev->demod_addr << 1 == addr) && (reg % 2 == 0)) { + /* + * Workaround an I2C bug when reading from zl10353 + */ + reg -= 1; + len += 1; + + rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, b, len); + + *buf = b[1]; + } else { + rc = tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + REQ_16_SET_GET_I2C_WR1_RDN, addr | reg << 8, 0, buf, len); + } + + return rc; +} + +/* + * read from a 16bit register + * for example xc2028, xc3028 or xc3028L + */ +static int tm6000_i2c_recv_regs16(struct tm6000_core *dev, unsigned char addr, + __u16 reg, char *buf, int len) +{ + return tm6000_read_write_usb(dev, USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + REQ_14_SET_GET_I2C_WR2_RDN, addr, reg, buf, len); +} + +static int tm6000_i2c_xfer(struct i2c_adapter *i2c_adap, + struct i2c_msg msgs[], int num) +{ + struct tm6000_core *dev = i2c_adap->algo_data; + int addr, rc, i, byte; + + if (num <= 0) + return 0; + for (i = 0; i < num; i++) { + addr = (msgs[i].addr << 1) & 0xff; + i2c_dprintk(2,"%s %s addr=0x%x len=%d:", + (msgs[i].flags & I2C_M_RD) ? "read" : "write", + i == num - 1 ? "stop" : "nonstop", addr, msgs[i].len); + if (msgs[i].flags & I2C_M_RD) { + /* read request without preceding register selection */ + /* + * The TM6000 only supports a read transaction + * immediately after a 1 or 2 byte write to select + * a register. We cannot fulfil this request. + */ + i2c_dprintk(2, " read without preceding write not" + " supported"); + rc = -EOPNOTSUPP; + goto err; + } else if (i + 1 < num && msgs[i].len <= 2 && + (msgs[i + 1].flags & I2C_M_RD) && + msgs[i].addr == msgs[i + 1].addr) { + /* 1 or 2 byte write followed by a read */ + if (i2c_debug >= 2) + for (byte = 0; byte < msgs[i].len; byte++) + printk(" %02x", msgs[i].buf[byte]); + i2c_dprintk(2, "; joined to read %s len=%d:", + i == num - 2 ? "stop" : "nonstop", + msgs[i + 1].len); + + if (msgs[i].len == 2) { + rc = tm6000_i2c_recv_regs16(dev, addr, + msgs[i].buf[0] << 8 | msgs[i].buf[1], + msgs[i + 1].buf, msgs[i + 1].len); + } else { + rc = tm6000_i2c_recv_regs(dev, addr, msgs[i].buf[0], + msgs[i + 1].buf, msgs[i + 1].len); + } + + i++; + + if (addr == dev->tuner_addr << 1) { + tm6000_set_reg(dev, 0x32, 0,0); + tm6000_set_reg(dev, 0x33, 0,0); + } + if (i2c_debug >= 2) + for (byte = 0; byte < msgs[i].len; byte++) + printk(" %02x", msgs[i].buf[byte]); + } else { + /* write bytes */ + if (i2c_debug >= 2) + for (byte = 0; byte < msgs[i].len; byte++) + printk(" %02x", msgs[i].buf[byte]); + rc = tm6000_i2c_send_regs(dev, addr, msgs[i].buf[0], + msgs[i].buf + 1, msgs[i].len - 1); + + if (addr == dev->tuner_addr << 1) { + tm6000_set_reg(dev, 0x32, 0,0); + tm6000_set_reg(dev, 0x33, 0,0); + } + } + if (i2c_debug >= 2) + printk("\n"); + if (rc < 0) + goto err; + } + + return num; +err: + i2c_dprintk(2," ERROR: %i\n", rc); + return rc; +} + +static int tm6000_i2c_eeprom(struct tm6000_core *dev, + unsigned char *eedata, int len) +{ + int i, rc; + unsigned char *p = eedata; + unsigned char bytes[17]; + + dev->i2c_client.addr = 0xa0 >> 1; + + bytes[16] = '\0'; + for (i = 0; i < len; ) { + *p = i; + rc = tm6000_i2c_recv_regs(dev, 0xa0, i, p, 1); + if (rc < 1) { + if (p == eedata) + goto noeeprom; + else { + printk(KERN_WARNING + "%s: i2c eeprom read error (err=%d)\n", + dev->name, rc); + } + return -1; + } + p++; + if (0 == (i % 16)) + printk(KERN_INFO "%s: i2c eeprom %02x:", dev->name, i); + printk(" %02x", eedata[i]); + if ((eedata[i] >= ' ') && (eedata[i] <= 'z')) { + bytes[i%16] = eedata[i]; + } else { + bytes[i%16]='.'; + } + + i++; + + if (0 == (i % 16)) { + bytes[16] = '\0'; + printk(" %s\n", bytes); + } + } + if (0 != (i%16)) { + bytes[i%16] = '\0'; + for (i %= 16; i < 16; i++) + printk(" "); + } + printk(" %s\n", bytes); + + return 0; + +noeeprom: + printk(KERN_INFO "%s: Huh, no eeprom present (err=%d)?\n", + dev->name, rc); + return rc; +} + +/* ----------------------------------------------------------- */ + +/* + * functionality() + */ +static u32 functionality(struct i2c_adapter *adap) +{ + return I2C_FUNC_SMBUS_EMUL; +} + +#define mass_write(addr, reg, data...) \ + { const static u8 _val[] = data; \ + rc=tm6000_read_write_usb(dev,USB_DIR_OUT | USB_TYPE_VENDOR, \ + REQ_16_SET_GET_I2C_WR1_RDN,(reg<<8)+addr, 0x00, (u8 *) _val, \ + ARRAY_SIZE(_val)); \ + if (rc<0) { \ + printk(KERN_ERR "Error on line %d: %d\n",__LINE__,rc); \ + return rc; \ + } \ + msleep (10); \ + } + +static struct i2c_algorithm tm6000_algo = { + .master_xfer = tm6000_i2c_xfer, + .functionality = functionality, +}; + +static struct i2c_adapter tm6000_adap_template = { + .owner = THIS_MODULE, + .class = I2C_CLASS_TV_ANALOG | I2C_CLASS_TV_DIGITAL, + .name = "tm6000", + .id = I2C_HW_B_TM6000, + .algo = &tm6000_algo, +}; + +static struct i2c_client tm6000_client_template = { + .name = "tm6000 internal", +}; + +/* ----------------------------------------------------------- */ + +/* + * tm6000_i2c_register() + * register i2c bus + */ +int tm6000_i2c_register(struct tm6000_core *dev) +{ + unsigned char eedata[256]; + + dev->i2c_adap = tm6000_adap_template; + dev->i2c_adap.dev.parent = &dev->udev->dev; + strcpy(dev->i2c_adap.name, dev->name); + dev->i2c_adap.algo_data = dev; + i2c_add_adapter(&dev->i2c_adap); + + dev->i2c_client = tm6000_client_template; + dev->i2c_client.adapter = &dev->i2c_adap; + + i2c_set_adapdata(&dev->i2c_adap, &dev->v4l2_dev); + + tm6000_i2c_eeprom(dev, eedata, sizeof(eedata)); + + return 0; +} + +/* + * tm6000_i2c_unregister() + * unregister i2c_bus + */ +int tm6000_i2c_unregister(struct tm6000_core *dev) +{ + i2c_del_adapter(&dev->i2c_adap); + return 0; +} diff --git a/drivers/staging/tm6000/tm6000-regs.h b/drivers/staging/tm6000/tm6000-regs.h new file mode 100644 index 000000000000..6c9368878f5b --- /dev/null +++ b/drivers/staging/tm6000/tm6000-regs.h @@ -0,0 +1,86 @@ +/* + tm6000-regs.h - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 2 + + 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. + */ + +/* + * Define TV Master TM5600/TM6000/TM6010 Request codes + */ +#define REQ_00_SET_IR_VALUE 0 +#define REQ_01_SET_WAKEUP_IRCODE 1 +#define REQ_02_GET_IR_CODE 2 +#define REQ_03_SET_GET_MCU_PIN 3 +#define REQ_04_EN_DISABLE_MCU_INT 4 +#define REQ_05_SET_GET_USBREG 5 + /* Write: RegNum, Value, 0 */ + /* Read : RegNum, Value, 1, RegStatus */ +#define REQ_06_SET_GET_USBREG_BIT 6 +#define REQ_07_SET_GET_AVREG 7 + /* Write: RegNum, Value, 0 */ + /* Read : RegNum, Value, 1, RegStatus */ +#define REQ_08_SET_GET_AVREG_BIT 8 +#define REQ_09_SET_GET_TUNER_FQ 9 +#define REQ_10_SET_TUNER_SYSTEM 10 +#define REQ_11_SET_EEPROM_ADDR 11 +#define REQ_12_SET_GET_EEPROMBYTE 12 +#define REQ_13_GET_EEPROM_SEQREAD 13 +#define REQ_14_SET_GET_I2C_WR2_RDN 14 +#define REQ_15_SET_GET_I2CBYTE 15 + /* Write: Subaddr, Slave Addr, value, 0 */ + /* Read : Subaddr, Slave Addr, value, 1 */ +#define REQ_16_SET_GET_I2C_WR1_RDN 16 + /* Subaddr, Slave Addr, 0, length */ +#define REQ_17_SET_GET_I2CFP 17 + /* Write: Slave Addr, register, value */ + /* Read : Slave Addr, register, 2, data */ + +/* + * Define TV Master TM5600/TM6000/TM6010 GPIO lines + */ + +#define TM6000_GPIO_CLK 0x101 +#define TM6000_GPIO_DATA 0x100 + +#define TM6000_GPIO_1 0x102 +#define TM6000_GPIO_2 0x103 +#define TM6000_GPIO_3 0x104 +#define TM6000_GPIO_4 0x300 +#define TM6000_GPIO_5 0x301 +#define TM6000_GPIO_6 0x304 +#define TM6000_GPIO_7 0x305 + +/* tm6010 defines GPIO with different values */ +#define TM6010_GPIO_0 0x0102 +#define TM6010_GPIO_1 0x0103 +#define TM6010_GPIO_2 0x0104 +#define TM6010_GPIO_3 0x0105 +#define TM6010_GPIO_4 0x0106 +#define TM6010_GPIO_5 0x0107 +#define TM6010_GPIO_6 0x0300 +#define TM6010_GPIO_7 0x0301 +#define TM6010_GPIO_9 0x0305 +/* + * Define TV Master TM5600/TM6000/TM6010 URB message codes and length + */ + +enum { + TM6000_URB_MSG_VIDEO=1, + TM6000_URB_MSG_AUDIO, + TM6000_URB_MSG_VBI, + TM6000_URB_MSG_PTS, + TM6000_URB_MSG_ERR, +}; diff --git a/drivers/staging/tm6000/tm6000-stds.c b/drivers/staging/tm6000/tm6000-stds.c new file mode 100644 index 000000000000..1e142e5d59c5 --- /dev/null +++ b/drivers/staging/tm6000/tm6000-stds.c @@ -0,0 +1,873 @@ +/* + tm6000-stds.c - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2007 Mauro Carvalho Chehab <mchehab@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 + the Free Software Foundation version 2 + + 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/kernel.h> +#include "tm6000.h" +#include "tm6000-regs.h" + +struct tm6000_reg_settings { + unsigned char req; + unsigned char reg; + unsigned char value; +}; + +struct tm6000_std_tv_settings { + v4l2_std_id id; + struct tm6000_reg_settings sif[12]; + struct tm6000_reg_settings nosif[12]; + struct tm6000_reg_settings common[25]; +}; + +struct tm6000_std_settings { + v4l2_std_id id; + struct tm6000_reg_settings common[37]; +}; + +static struct tm6000_std_tv_settings tv_stds[] = { + { + .id = V4L2_STD_PAL_M, + .sif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x08}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x62}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfe}, + {REQ_07_SET_GET_AVREG, 0xfe, 0xcb}, + {0, 0, 0}, + }, + .nosif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x60}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + {0, 0, 0}, + }, + .common = { + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x04}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x00}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x83}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x0a}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xe0}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x88}, + {REQ_07_SET_GET_AVREG, 0x30, 0x20}, + {REQ_07_SET_GET_AVREG, 0x31, 0x61}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_PAL_Nc, + .sif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x08}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x62}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfe}, + {REQ_07_SET_GET_AVREG, 0xfe, 0xcb}, + {0, 0, 0}, + }, + .nosif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x60}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + {0, 0, 0}, + }, + .common = { + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x36}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x02}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x91}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x1f}, + {REQ_07_SET_GET_AVREG, 0x1b, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_PAL, + .sif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x08}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x62}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfe}, + {REQ_07_SET_GET_AVREG, 0xfe, 0xcb}, + {0, 0, 0} + }, + .nosif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x60}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + {0, 0, 0}, + }, + .common = { + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x32}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x02}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x25}, + {REQ_07_SET_GET_AVREG, 0x19, 0xd5}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x63}, + {REQ_07_SET_GET_AVREG, 0x1b, 0x50}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_SECAM, + .sif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x08}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x62}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfe}, + {REQ_07_SET_GET_AVREG, 0xfe, 0xcb}, + {0, 0, 0}, + }, + .nosif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x60}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + {0, 0, 0}, + }, + .common = { + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x38}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x02}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x24}, + {REQ_07_SET_GET_AVREG, 0x19, 0x92}, + {REQ_07_SET_GET_AVREG, 0x1a, 0xe8}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xed}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x18}, + {REQ_07_SET_GET_AVREG, 0x82, 0x42}, + {REQ_07_SET_GET_AVREG, 0x83, 0xFF}, + + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_NTSC, + .sif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x08}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x62}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfe}, + {REQ_07_SET_GET_AVREG, 0xfe, 0xcb}, + {0, 0, 0}, + }, + .nosif = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x60}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + {0, 0, 0}, + }, + .common = { + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x00}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0f}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x00}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x8b}, + {REQ_07_SET_GET_AVREG, 0x1a, 0xa2}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xe9}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x88}, + {REQ_07_SET_GET_AVREG, 0x30, 0x22}, + {REQ_07_SET_GET_AVREG, 0x31, 0x61}, + {REQ_07_SET_GET_AVREG, 0x33, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x42}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdd}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, +}; + +static struct tm6000_std_settings composite_stds[] = { + { + .id = V4L2_STD_PAL_M, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf4}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x04}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x00}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x83}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x0a}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xe0}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x88}, + {REQ_07_SET_GET_AVREG, 0x30, 0x20}, + {REQ_07_SET_GET_AVREG, 0x31, 0x61}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_PAL_Nc, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf4}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x36}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x02}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x91}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x1f}, + {REQ_07_SET_GET_AVREG, 0x1b, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_PAL, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf4}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x32}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x02}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x25}, + {REQ_07_SET_GET_AVREG, 0x19, 0xd5}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x63}, + {REQ_07_SET_GET_AVREG, 0x1b, 0x50}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_SECAM, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf4}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x38}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x02}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x24}, + {REQ_07_SET_GET_AVREG, 0x19, 0x92}, + {REQ_07_SET_GET_AVREG, 0x1a, 0xe8}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xed}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x18}, + {REQ_07_SET_GET_AVREG, 0x82, 0x42}, + {REQ_07_SET_GET_AVREG, 0x83, 0xFF}, + + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_NTSC, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xf4}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf3}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x0f}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf1}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe8}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8b}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x00}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0f}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x00}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x8b}, + {REQ_07_SET_GET_AVREG, 0x1a, 0xa2}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xe9}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x88}, + {REQ_07_SET_GET_AVREG, 0x30, 0x22}, + {REQ_07_SET_GET_AVREG, 0x31, 0x61}, + {REQ_07_SET_GET_AVREG, 0x33, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x42}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdd}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, +}; + +static struct tm6000_std_settings svideo_stds[] = { + { + .id = V4L2_STD_PAL_M, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xfc}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x00}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8a}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x05}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x04}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x83}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x0a}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xe0}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x88}, + {REQ_07_SET_GET_AVREG, 0x30, 0x22}, + {REQ_07_SET_GET_AVREG, 0x31, 0x61}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_PAL_Nc, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xfc}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x00}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8a}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x37}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x04}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x91}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x1f}, + {REQ_07_SET_GET_AVREG, 0x1b, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x88}, + {REQ_07_SET_GET_AVREG, 0x30, 0x22}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_PAL, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xfc}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x00}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8a}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x33}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x04}, + {REQ_07_SET_GET_AVREG, 0x07, 0x00}, + {REQ_07_SET_GET_AVREG, 0x18, 0x25}, + {REQ_07_SET_GET_AVREG, 0x19, 0xd5}, + {REQ_07_SET_GET_AVREG, 0x1a, 0x63}, + {REQ_07_SET_GET_AVREG, 0x1b, 0x50}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2a}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x0c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x52}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdc}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_SECAM, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xfc}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x00}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8a}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x39}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0e}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x03}, + {REQ_07_SET_GET_AVREG, 0x07, 0x01}, + {REQ_07_SET_GET_AVREG, 0x18, 0x24}, + {REQ_07_SET_GET_AVREG, 0x19, 0x92}, + {REQ_07_SET_GET_AVREG, 0x1a, 0xe8}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xed}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x8c}, + {REQ_07_SET_GET_AVREG, 0x30, 0x2a}, + {REQ_07_SET_GET_AVREG, 0x31, 0xc1}, + {REQ_07_SET_GET_AVREG, 0x33, 0x2c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x18}, + {REQ_07_SET_GET_AVREG, 0x82, 0x42}, + {REQ_07_SET_GET_AVREG, 0x83, 0xFF}, + + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, { + .id = V4L2_STD_NTSC, + .common = { + {REQ_08_SET_GET_AVREG_BIT, 0xe2, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xe3, 0xfc}, + {REQ_08_SET_GET_AVREG_BIT, 0xe4, 0xf8}, + {REQ_08_SET_GET_AVREG_BIT, 0xe6, 0x00}, + {REQ_08_SET_GET_AVREG_BIT, 0xea, 0xf2}, + {REQ_08_SET_GET_AVREG_BIT, 0xeb, 0xf0}, + {REQ_08_SET_GET_AVREG_BIT, 0xec, 0xc2}, + {REQ_08_SET_GET_AVREG_BIT, 0xed, 0xe0}, + {REQ_08_SET_GET_AVREG_BIT, 0xf0, 0x68}, + {REQ_08_SET_GET_AVREG_BIT, 0xf1, 0xfc}, + {REQ_07_SET_GET_AVREG, 0xfe, 0x8a}, + + {REQ_07_SET_GET_AVREG, 0x3f, 0x01}, + {REQ_07_SET_GET_AVREG, 0x00, 0x01}, + {REQ_07_SET_GET_AVREG, 0x01, 0x0f}, + {REQ_07_SET_GET_AVREG, 0x02, 0x5f}, + {REQ_07_SET_GET_AVREG, 0x03, 0x03}, + {REQ_07_SET_GET_AVREG, 0x07, 0x00}, + {REQ_07_SET_GET_AVREG, 0x17, 0x8b}, + {REQ_07_SET_GET_AVREG, 0x18, 0x1e}, + {REQ_07_SET_GET_AVREG, 0x19, 0x8b}, + {REQ_07_SET_GET_AVREG, 0x1a, 0xa2}, + {REQ_07_SET_GET_AVREG, 0x1b, 0xe9}, + {REQ_07_SET_GET_AVREG, 0x1c, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x1d, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1e, 0xcc}, + {REQ_07_SET_GET_AVREG, 0x1f, 0xcd}, + {REQ_07_SET_GET_AVREG, 0x2e, 0x88}, + {REQ_07_SET_GET_AVREG, 0x30, 0x22}, + {REQ_07_SET_GET_AVREG, 0x31, 0x61}, + {REQ_07_SET_GET_AVREG, 0x33, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x35, 0x1c}, + {REQ_07_SET_GET_AVREG, 0x82, 0x42}, + {REQ_07_SET_GET_AVREG, 0x83, 0x6F}, + + {REQ_07_SET_GET_AVREG, 0x04, 0xdd}, + {REQ_07_SET_GET_AVREG, 0x0d, 0x07}, + {REQ_07_SET_GET_AVREG, 0x3f, 0x00}, + {0, 0, 0}, + }, + }, +}; + +void tm6000_get_std_res(struct tm6000_core *dev) +{ + /* Currently, those are the only supported resoltions */ + if (dev->norm & V4L2_STD_525_60) { + dev->height = 480; + } else { + dev->height = 576; + } + dev->width = 720; +} + +static int tm6000_load_std(struct tm6000_core *dev, + struct tm6000_reg_settings *set, int max_size) +{ + int i, rc; + + /* Load board's initialization table */ + for (i = 0; max_size; i++) { + if (!set[i].req) + return 0; + + if ((dev->dev_type != TM6010) && + (set[i].req == REQ_08_SET_GET_AVREG_BIT)) + continue; + + rc = tm6000_set_reg(dev, set[i].req, set[i].reg, set[i].value); + if (rc < 0) { + printk(KERN_ERR "Error %i while setting " + "req %d, reg %d to value %d\n", + rc, set[i].req, set[i].reg, set[i].value); + return rc; + } + } + + return 0; +} + +static int tm6000_set_tv(struct tm6000_core *dev, int pos) +{ + int rc; + + /* FIXME: This code is for tm6010 - not tested yet - doesn't work with + tm5600 + */ + + /* FIXME: This is tuner-dependent */ + int nosif = 0; + + if (nosif) { + rc = tm6000_load_std(dev, tv_stds[pos].nosif, + sizeof(tv_stds[pos].nosif)); + } else { + rc = tm6000_load_std(dev, tv_stds[pos].sif, + sizeof(tv_stds[pos].sif)); + } + if (rc < 0) + return rc; + rc = tm6000_load_std(dev, tv_stds[pos].common, + sizeof(tv_stds[pos].common)); + + return rc; +} + +int tm6000_set_standard(struct tm6000_core *dev, v4l2_std_id * norm) +{ + int i, rc = 0; + + dev->norm = *norm; + tm6000_get_std_res(dev); + + switch (dev->input) { + case TM6000_INPUT_TV: + for (i = 0; i < ARRAY_SIZE(tv_stds); i++) { + if (*norm & tv_stds[i].id) { + rc = tm6000_set_tv(dev, i); + goto ret; + } + } + return -EINVAL; + case TM6000_INPUT_SVIDEO: + for (i = 0; i < ARRAY_SIZE(svideo_stds); i++) { + if (*norm & svideo_stds[i].id) { + rc = tm6000_load_std(dev, svideo_stds[i].common, + sizeof(svideo_stds[i]. + common)); + goto ret; + } + } + return -EINVAL; + case TM6000_INPUT_COMPOSITE: + for (i = 0; i < ARRAY_SIZE(composite_stds); i++) { + if (*norm & composite_stds[i].id) { + rc = tm6000_load_std(dev, + composite_stds[i].common, + sizeof(composite_stds[i]. + common)); + goto ret; + } + } + return -EINVAL; + } + +ret: + if (rc < 0) + return rc; + + msleep(40); + + + return 0; +} diff --git a/drivers/staging/tm6000/tm6000-usb-isoc.h b/drivers/staging/tm6000/tm6000-usb-isoc.h new file mode 100644 index 000000000000..5a5049acd4ec --- /dev/null +++ b/drivers/staging/tm6000/tm6000-usb-isoc.h @@ -0,0 +1,53 @@ +/* + tm6000-buf.c - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation version 2 + + 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/videodev2.h> + +#define TM6000_URB_MSG_LEN 180 + +struct usb_isoc_ctl { + /* max packet size of isoc transaction */ + int max_pkt_size; + + /* number of allocated urbs */ + int num_bufs; + + /* urb for isoc transfers */ + struct urb **urb; + + /* transfer buffers for isoc transfer */ + char **transfer_buffer; + + /* Last buffer command and region */ + u8 cmd; + int pos, size, pktsize; + + /* Last field: ODD or EVEN? */ + int field; + + /* Stores incomplete commands */ + u32 tmp_buf; + int tmp_buf_len; + + /* Stores already requested buffers */ + struct tm6000_buffer *buf; + + /* Stores the number of received fields */ + int nfields; +}; diff --git a/drivers/staging/tm6000/tm6000-video.c b/drivers/staging/tm6000/tm6000-video.c new file mode 100644 index 000000000000..66f922d01e1a --- /dev/null +++ b/drivers/staging/tm6000/tm6000-video.c @@ -0,0 +1,1550 @@ +/* + tm6000-video.c - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + + Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + - Fixed module load/unload + + 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 + + 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/delay.h> +#include <linux/errno.h> +#include <linux/fs.h> +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/mm.h> +#include <linux/ioport.h> +#include <linux/init.h> +#include <linux/sched.h> +#include <linux/random.h> +#include <linux/version.h> +#include <linux/usb.h> +#include <linux/videodev2.h> +#include <media/v4l2-ioctl.h> +#include <linux/interrupt.h> +#include <linux/kthread.h> +#include <linux/highmem.h> +#include <linux/freezer.h> + +#include "tm6000-regs.h" +#include "tm6000.h" + +#define BUFFER_TIMEOUT msecs_to_jiffies(2000) /* 2 seconds */ + +/* Limits minimum and default number of buffers */ +#define TM6000_MIN_BUF 4 +#define TM6000_DEF_BUF 8 + +#define TM6000_MAX_ISO_PACKETS 40 /* Max number of ISO packets */ + +/* Declare static vars that will be used as parameters */ +static unsigned int vid_limit = 16; /* Video memory limit, in Mb */ +static int video_nr = -1; /* /dev/videoN, -1 for autodetect */ + +/* Debug level */ +int tm6000_debug; + +/* supported controls */ +static struct v4l2_queryctrl tm6000_qctrl[] = { + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 255, + .step = 1, + .default_value = 54, + .flags = 0, + }, { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = 119, + .flags = 0, + }, { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 255, + .step = 0x1, + .default_value = 112, + .flags = 0, + }, { + .id = V4L2_CID_HUE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Hue", + .minimum = -128, + .maximum = 127, + .step = 0x1, + .default_value = 0, + .flags = 0, + } +}; + +static int qctl_regs[ARRAY_SIZE(tm6000_qctrl)]; + +static struct tm6000_fmt format[] = { + { + .name = "4:2:2, packed, YVY2", + .fourcc = V4L2_PIX_FMT_YUYV, + .depth = 16, + }, { + .name = "4:2:2, packed, UYVY", + .fourcc = V4L2_PIX_FMT_UYVY, + .depth = 16, + }, { + .name = "A/V + VBI mux packet", + .fourcc = V4L2_PIX_FMT_TM6000, + .depth = 16, + } +}; + +/* ------------------------------------------------------------------ + DMA and thread functions + ------------------------------------------------------------------*/ + +#define norm_maxw(a) 720 +#define norm_maxh(a) 576 + +#define norm_minw(a) norm_maxw(a) +#define norm_minh(a) norm_maxh(a) + +/* + * video-buf generic routine to get the next available buffer + */ +static inline void get_next_buf(struct tm6000_dmaqueue *dma_q, + struct tm6000_buffer **buf) +{ + struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); + char *outp; + + if (list_empty(&dma_q->active)) { + dprintk(dev, V4L2_DEBUG_QUEUE, "No active queue to serve\n"); + *buf = NULL; + return; + } + + *buf = list_entry(dma_q->active.next, + struct tm6000_buffer, vb.queue); + + if (!buf) + return; + + /* Cleans up buffer - Usefull for testing for frame/URB loss */ + outp = videobuf_to_vmalloc(&(*buf)->vb); + memset(outp, 0, (*buf)->vb.size); + + return; +} + +/* + * Announces that a buffer were filled and request the next + */ +static inline void buffer_filled(struct tm6000_core *dev, + struct tm6000_dmaqueue *dma_q, + struct tm6000_buffer *buf) +{ + /* Advice that buffer was filled */ + dprintk(dev, V4L2_DEBUG_ISOC, "[%p/%d] wakeup\n", buf, buf->vb.i); + buf->vb.state = VIDEOBUF_DONE; + buf->vb.field_count++; + do_gettimeofday(&buf->vb.ts); + + list_del(&buf->vb.queue); + wake_up(&buf->vb.done); +} + +const char *tm6000_msg_type[] = { + "unknown(0)", /* 0 */ + "video", /* 1 */ + "audio", /* 2 */ + "vbi", /* 3 */ + "pts", /* 4 */ + "err", /* 5 */ + "unknown(6)", /* 6 */ + "unknown(7)", /* 7 */ +}; + +/* + * Identify the tm5600/6000 buffer header type and properly handles + */ +static int copy_packet(struct urb *urb, u32 header, u8 **ptr, u8 *endp, + u8 *out_p, struct tm6000_buffer **buf) +{ + struct tm6000_dmaqueue *dma_q = urb->context; + struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); + u8 c; + unsigned int cmd, cpysize, pktsize, size, field, block, line, pos = 0; + int rc = 0; + /* FIXME: move to tm6000-isoc */ + static int last_line = -2, start_line = -2, last_field = -2; + + /* FIXME: this is the hardcoded window size + */ + unsigned int linewidth = (*buf)->vb.width << 1; + + if (!dev->isoc_ctl.cmd) { + c = (header >> 24) & 0xff; + + /* split the header fields */ + size = (((header & 0x7e) << 1) -1) *4; + block = (header >> 7) & 0xf; + field = (header >> 11) & 0x1; + line = (header >> 12) & 0x1ff; + cmd = (header >> 21) & 0x7; + + /* Validates header fields */ + if(size > TM6000_URB_MSG_LEN) + size = TM6000_URB_MSG_LEN; + + if (cmd == TM6000_URB_MSG_VIDEO) { + if ((block+1)*TM6000_URB_MSG_LEN>linewidth) + cmd = TM6000_URB_MSG_ERR; + + /* FIXME: Mounts the image as field0+field1 + * It should, instead, check if the user selected + * entrelaced or non-entrelaced mode + */ + pos= ((line<<1)+field)*linewidth + + block*TM6000_URB_MSG_LEN; + + /* Don't allow to write out of the buffer */ + if (pos+TM6000_URB_MSG_LEN > (*buf)->vb.size) { + dprintk(dev, V4L2_DEBUG_ISOC, + "ERR: size=%d, num=%d, line=%d, " + "field=%d\n", + size, block, line, field); + + cmd = TM6000_URB_MSG_ERR; + } + } else { + pos=0; + } + + /* Prints debug info */ + dprintk(dev, V4L2_DEBUG_ISOC, "size=%d, num=%d, " + " line=%d, field=%d\n", + size, block, line, field); + + if ((last_line!=line)&&(last_line+1!=line) && + (cmd != TM6000_URB_MSG_ERR) ) { + if (cmd != TM6000_URB_MSG_VIDEO) { + dprintk(dev, V4L2_DEBUG_ISOC, "cmd=%d, " + "size=%d, num=%d, line=%d, field=%d\n", + cmd, size, block, line, field); + } + if (start_line<0) + start_line=last_line; + /* Prints debug info */ + dprintk(dev, V4L2_DEBUG_ISOC, "lines= %d-%d, " + "field=%d\n", + start_line, last_line, field); + + if ((start_line<6 && last_line>200) && + (last_field != field) ) { + + dev->isoc_ctl.nfields++; + if (dev->isoc_ctl.nfields>=2) { + dev->isoc_ctl.nfields=0; + + /* Announces that a new buffer were filled */ + buffer_filled (dev, dma_q, *buf); + dprintk(dev, V4L2_DEBUG_ISOC, + "new buffer filled\n"); + get_next_buf (dma_q, buf); + if (!*buf) + return rc; + out_p = videobuf_to_vmalloc(&((*buf)->vb)); + if (!out_p) + return rc; + + pos = dev->isoc_ctl.pos = 0; + } + } + + start_line=line; + last_field=field; + } + last_line=line; + + pktsize = TM6000_URB_MSG_LEN; + } else { + /* Continue the last copy */ + cmd = dev->isoc_ctl.cmd; + size= dev->isoc_ctl.size; + pos = dev->isoc_ctl.pos; + pktsize = dev->isoc_ctl.pktsize; + } + + cpysize = (endp-(*ptr) > size) ? size : endp - *ptr; + + if (cpysize) { + /* handles each different URB message */ + switch(cmd) { + case TM6000_URB_MSG_VIDEO: + /* Fills video buffer */ + memcpy(&out_p[pos], *ptr, cpysize); + break; + case TM6000_URB_MSG_PTS: + break; + case TM6000_URB_MSG_AUDIO: +/* Need some code to process audio */ +printk ("%ld: cmd=%s, size=%d\n", jiffies, + tm6000_msg_type[cmd],size); + break; + default: + dprintk (dev, V4L2_DEBUG_ISOC, "cmd=%s, size=%d\n", + tm6000_msg_type[cmd],size); + } + } + if (cpysize<size) { + /* End of URB packet, but cmd processing is not + * complete. Preserve the state for a next packet + */ + dev->isoc_ctl.pos = pos+cpysize; + dev->isoc_ctl.size= size-cpysize; + dev->isoc_ctl.cmd = cmd; + dev->isoc_ctl.pktsize = pktsize-cpysize; + (*ptr)+=cpysize; + } else { + dev->isoc_ctl.cmd = 0; + (*ptr)+=pktsize; + } + + return rc; +} + +static int copy_streams(u8 *data, u8 *out_p, unsigned long len, + struct urb *urb, struct tm6000_buffer **buf) +{ + struct tm6000_dmaqueue *dma_q = urb->context; + struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq); + u8 *ptr=data, *endp=data+len; + unsigned long header=0; + int rc=0; + + for (ptr=data; ptr<endp;) { + if (!dev->isoc_ctl.cmd) { + u8 *p=(u8 *)&dev->isoc_ctl.tmp_buf; + /* FIXME: This seems very complex + * It just recovers up to 3 bytes of the header that + * might be at the previous packet + */ + if (dev->isoc_ctl.tmp_buf_len) { + while (dev->isoc_ctl.tmp_buf_len) { + if ( *(ptr+3-dev->isoc_ctl.tmp_buf_len) == 0x47) { + break; + } + p++; + dev->isoc_ctl.tmp_buf_len--; + } + if (dev->isoc_ctl.tmp_buf_len) { + memcpy (&header,p, + dev->isoc_ctl.tmp_buf_len); + memcpy (((u8 *)header)+ + dev->isoc_ctl.tmp_buf, + ptr, + 4-dev->isoc_ctl.tmp_buf_len); + ptr+=4-dev->isoc_ctl.tmp_buf_len; + goto HEADER; + } + } + /* Seek for sync */ + for (;ptr<endp-3;ptr++) { + if (*(ptr+3)==0x47) + break; + } + + if (ptr+3>=endp) { + dev->isoc_ctl.tmp_buf_len=endp-ptr; + memcpy (&dev->isoc_ctl.tmp_buf,ptr, + dev->isoc_ctl.tmp_buf_len); + dev->isoc_ctl.cmd=0; + return rc; + } + + /* Get message header */ + header=*(unsigned long *)ptr; + ptr+=4; + } +HEADER: + /* Copy or continue last copy */ + rc=copy_packet(urb,header,&ptr,endp,out_p,buf); + if (rc<0) { + buf=NULL; + printk(KERN_ERR "tm6000: buffer underrun at %ld\n", + jiffies); + return rc; + } + } + + return 0; +} +/* + * Identify the tm5600/6000 buffer header type and properly handles + */ +static int copy_multiplexed(u8 *ptr, u8 *out_p, unsigned long len, + struct urb *urb, struct tm6000_buffer **buf) +{ + struct tm6000_dmaqueue *dma_q = urb->context; + struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq); + unsigned int pos=dev->isoc_ctl.pos,cpysize; + int rc=1; + + while (len>0) { + cpysize=min(len,(*buf)->vb.size-pos); +//printk("Copying %d bytes (max=%lu) from %p to %p[%u]\n",cpysize,(*buf)->vb.size,ptr,out_p,pos); + memcpy(&out_p[pos], ptr, cpysize); + pos+=cpysize; + ptr+=cpysize; + len-=cpysize; + if (pos >= (*buf)->vb.size) { + pos=0; + /* Announces that a new buffer were filled */ + buffer_filled (dev, dma_q, *buf); + dprintk(dev, V4L2_DEBUG_ISOC, "new buffer filled\n"); + get_next_buf (dma_q, buf); + if (!*buf) + break; + out_p = videobuf_to_vmalloc(&((*buf)->vb)); + if (!out_p) + return rc; + pos = 0; + } + } + + dev->isoc_ctl.pos=pos; + return rc; +} + +static void inline print_err_status (struct tm6000_core *dev, + int packet, int status) +{ + char *errmsg = "Unknown"; + + switch(status) { + case -ENOENT: + errmsg = "unlinked synchronuously"; + break; + case -ECONNRESET: + errmsg = "unlinked asynchronuously"; + break; + case -ENOSR: + errmsg = "Buffer error (overrun)"; + break; + case -EPIPE: + errmsg = "Stalled (device not responding)"; + break; + case -EOVERFLOW: + errmsg = "Babble (bad cable?)"; + break; + case -EPROTO: + errmsg = "Bit-stuff error (bad cable?)"; + break; + case -EILSEQ: + errmsg = "CRC/Timeout (could be anything)"; + break; + case -ETIME: + errmsg = "Device does not respond"; + break; + } + if (packet<0) { + dprintk(dev, V4L2_DEBUG_QUEUE, "URB status %d [%s].\n", + status, errmsg); + } else { + dprintk(dev, V4L2_DEBUG_QUEUE, "URB packet %d, status %d [%s].\n", + packet, status, errmsg); + } +} + + +/* + * Controls the isoc copy of each urb packet + */ +static inline int tm6000_isoc_copy(struct urb *urb) +{ + struct tm6000_dmaqueue *dma_q = urb->context; + struct tm6000_core *dev= container_of(dma_q,struct tm6000_core,vidq); + struct tm6000_buffer *buf; + int i, len=0, rc=1; + int size; + char *outp = NULL, *p; + unsigned long copied; + + get_next_buf(dma_q, &buf); + if (!buf) + outp = videobuf_to_vmalloc(&buf->vb); + + if (!outp) + return 0; + + size = buf->vb.size; + + copied=0; + + if (urb->status<0) { + print_err_status (dev,-1,urb->status); + return 0; + } + + for (i = 0; i < urb->number_of_packets; i++) { + int status = urb->iso_frame_desc[i].status; + + if (status<0) { + print_err_status (dev,i,status); + continue; + } + + len=urb->iso_frame_desc[i].actual_length; + +// if (len>=TM6000_URB_MSG_LEN) { + p=urb->transfer_buffer + urb->iso_frame_desc[i].offset; + if (!urb->iso_frame_desc[i].status) { + if ((buf->fmt->fourcc)==V4L2_PIX_FMT_TM6000) { + rc=copy_multiplexed(p, outp, len, urb, &buf); + if (rc<=0) + return rc; + } else { + copy_streams(p, outp, len, urb, &buf); + } + } + copied += len; + if (copied>=size) + break; +// } + } + return rc; +} + +/* ------------------------------------------------------------------ + URB control + ------------------------------------------------------------------*/ + +/* + * IRQ callback, called by URB callback + */ +static void tm6000_irq_callback(struct urb *urb) +{ + struct tm6000_dmaqueue *dma_q = urb->context; + struct tm6000_core *dev = container_of(dma_q, struct tm6000_core, vidq); + int i; + + if (!dev) + return; + + spin_lock(&dev->slock); + tm6000_isoc_copy(urb); + spin_unlock(&dev->slock); + + /* Reset urb buffers */ + for (i = 0; i < urb->number_of_packets; i++) { + urb->iso_frame_desc[i].status = 0; + urb->iso_frame_desc[i].actual_length = 0; + } + + urb->status = usb_submit_urb(urb, GFP_ATOMIC); + if (urb->status) + tm6000_err("urb resubmit failed (error=%i)\n", + urb->status); +} + +/* + * Stop and Deallocate URBs + */ +static void tm6000_uninit_isoc(struct tm6000_core *dev) +{ + struct urb *urb; + int i; + + dev->isoc_ctl.nfields = -1; + dev->isoc_ctl.buf = NULL; + for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { + urb=dev->isoc_ctl.urb[i]; + if (urb) { + usb_kill_urb(urb); + usb_unlink_urb(urb); + if (dev->isoc_ctl.transfer_buffer[i]) { + usb_buffer_free(dev->udev, + urb->transfer_buffer_length, + dev->isoc_ctl.transfer_buffer[i], + urb->transfer_dma); + } + usb_free_urb(urb); + dev->isoc_ctl.urb[i] = NULL; + } + dev->isoc_ctl.transfer_buffer[i] = NULL; + } + + kfree (dev->isoc_ctl.urb); + kfree (dev->isoc_ctl.transfer_buffer); + + dev->isoc_ctl.urb=NULL; + dev->isoc_ctl.transfer_buffer=NULL; + dev->isoc_ctl.num_bufs = 0; + + dev->isoc_ctl.num_bufs=0; +} + +/* + * Allocate URBs and start IRQ + */ +static int tm6000_prepare_isoc(struct tm6000_core *dev, unsigned int framesize) +{ + struct tm6000_dmaqueue *dma_q = &dev->vidq; + int i, j, sb_size, pipe, size, max_packets, num_bufs = 5; + struct urb *urb; + + /* De-allocates all pending stuff */ + tm6000_uninit_isoc(dev); + + pipe = usb_rcvisocpipe(dev->udev, + dev->isoc_in->desc.bEndpointAddress & + USB_ENDPOINT_NUMBER_MASK); + + size = usb_maxpacket(dev->udev, pipe, usb_pipeout(pipe)); + + if (size > dev->max_isoc_in) + size = dev->max_isoc_in; + + dev->isoc_ctl.max_pkt_size = size; + + max_packets = ( framesize + size - 1) / size; + + if (max_packets > TM6000_MAX_ISO_PACKETS) + max_packets = TM6000_MAX_ISO_PACKETS; + + sb_size = max_packets * size; + + dev->isoc_ctl.num_bufs = num_bufs; + + dev->isoc_ctl.urb = kmalloc(sizeof(void *)*num_bufs, GFP_KERNEL); + if (!dev->isoc_ctl.urb) { + tm6000_err("cannot alloc memory for usb buffers\n"); + return -ENOMEM; + } + + dev->isoc_ctl.transfer_buffer = kmalloc(sizeof(void *)*num_bufs, + GFP_KERNEL); + if (!dev->isoc_ctl.transfer_buffer) { + tm6000_err("cannot allocate memory for usbtransfer\n"); + kfree(dev->isoc_ctl.urb); + return -ENOMEM; + } + + dprintk(dev, V4L2_DEBUG_QUEUE, "Allocating %d x %d packets" + " (%d bytes) of %d bytes each to handle %u size\n", + max_packets, num_bufs, sb_size, + dev->max_isoc_in, size); + + + /* allocate urbs and transfer buffers */ + for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { + urb = usb_alloc_urb(max_packets, GFP_KERNEL); + if (!urb) { + tm6000_err("cannot alloc isoc_ctl.urb %i\n", i); + tm6000_uninit_isoc(dev); + usb_free_urb(urb); + return -ENOMEM; + } + dev->isoc_ctl.urb[i] = urb; + + dev->isoc_ctl.transfer_buffer[i] = usb_buffer_alloc(dev->udev, + sb_size, GFP_KERNEL, &urb->transfer_dma); + if (!dev->isoc_ctl.transfer_buffer[i]) { + tm6000_err ("unable to allocate %i bytes for transfer" + " buffer %i%s\n", + sb_size, i, + in_interrupt()?" while in int":""); + tm6000_uninit_isoc(dev); + return -ENOMEM; + } + memset(dev->isoc_ctl.transfer_buffer[i], 0, sb_size); + + usb_fill_bulk_urb(urb, dev->udev, pipe, + dev->isoc_ctl.transfer_buffer[i], sb_size, + tm6000_irq_callback, dma_q); + urb->interval = dev->isoc_in->desc.bInterval; + urb->number_of_packets = max_packets; + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + + for (j = 0; j < max_packets; j++) { + urb->iso_frame_desc[j].offset = size * j; + urb->iso_frame_desc[j].length = size; + } + } + + return 0; +} + +static int tm6000_start_thread( struct tm6000_core *dev) +{ + struct tm6000_dmaqueue *dma_q = &dev->vidq; + int i; + + dma_q->frame=0; + dma_q->ini_jiffies=jiffies; + + init_waitqueue_head(&dma_q->wq); + + /* submit urbs and enables IRQ */ + for (i = 0; i < dev->isoc_ctl.num_bufs; i++) { + int rc = usb_submit_urb(dev->isoc_ctl.urb[i], GFP_ATOMIC); + if (rc) { + tm6000_err("submit of urb %i failed (error=%i)\n", i, + rc); + tm6000_uninit_isoc(dev); + return rc; + } + } + + return 0; +} + +/* ------------------------------------------------------------------ + Videobuf operations + ------------------------------------------------------------------*/ + +static int +buffer_setup(struct videobuf_queue *vq, unsigned int *count, unsigned int *size) +{ + struct tm6000_fh *fh = vq->priv_data; + + *size = fh->fmt->depth * fh->width * fh->height >> 3; + if (0 == *count) + *count = TM6000_DEF_BUF; + + if (*count < TM6000_MIN_BUF) { + *count=TM6000_MIN_BUF; + } + + while (*size * *count > vid_limit * 1024 * 1024) + (*count)--; + + return 0; +} + +static void free_buffer(struct videobuf_queue *vq, struct tm6000_buffer *buf) +{ + struct tm6000_fh *fh = vq->priv_data; + struct tm6000_core *dev = fh->dev; + unsigned long flags; + + if (in_interrupt()) + BUG(); + + /* We used to wait for the buffer to finish here, but this didn't work + because, as we were keeping the state as VIDEOBUF_QUEUED, + videobuf_queue_cancel marked it as finished for us. + (Also, it could wedge forever if the hardware was misconfigured.) + + This should be safe; by the time we get here, the buffer isn't + queued anymore. If we ever start marking the buffers as + VIDEOBUF_ACTIVE, it won't be, though. + */ + spin_lock_irqsave(&dev->slock, flags); + if (dev->isoc_ctl.buf == buf) + dev->isoc_ctl.buf = NULL; + spin_unlock_irqrestore(&dev->slock, flags); + + videobuf_vmalloc_free(&buf->vb); + buf->vb.state = VIDEOBUF_NEEDS_INIT; +} + +static int +buffer_prepare(struct videobuf_queue *vq, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct tm6000_fh *fh = vq->priv_data; + struct tm6000_buffer *buf = container_of(vb,struct tm6000_buffer,vb); + struct tm6000_core *dev = fh->dev; + int rc = 0, urb_init = 0; + + BUG_ON(NULL == fh->fmt); + + + /* FIXME: It assumes depth=2 */ + /* The only currently supported format is 16 bits/pixel */ + buf->vb.size = fh->fmt->depth*fh->width*fh->height >> 3; + if (0 != buf->vb.baddr && buf->vb.bsize < buf->vb.size) + return -EINVAL; + + if (buf->fmt != fh->fmt || + buf->vb.width != fh->width || + buf->vb.height != fh->height || + buf->vb.field != field) { + buf->fmt = fh->fmt; + buf->vb.width = fh->width; + buf->vb.height = fh->height; + buf->vb.field = field; + buf->vb.state = VIDEOBUF_NEEDS_INIT; + } + + if (VIDEOBUF_NEEDS_INIT == buf->vb.state) { + if (0 != (rc = videobuf_iolock(vq, &buf->vb, NULL))) + goto fail; + urb_init = 1; + } + + if (!dev->isoc_ctl.num_bufs) + urb_init = 1; + + if (urb_init) { + rc = tm6000_prepare_isoc(dev, buf->vb.size); + if (rc < 0) + goto fail; + + rc = tm6000_start_thread(dev); + if (rc < 0) + goto fail; + + } + + buf->vb.state = VIDEOBUF_PREPARED; + return 0; + +fail: + free_buffer(vq, buf); + return rc; +} + +static void +buffer_queue(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct tm6000_buffer *buf = container_of(vb,struct tm6000_buffer,vb); + struct tm6000_fh *fh = vq->priv_data; + struct tm6000_core *dev = fh->dev; + struct tm6000_dmaqueue *vidq = &dev->vidq; + + buf->vb.state = VIDEOBUF_QUEUED; + list_add_tail(&buf->vb.queue, &vidq->active); +} + +static void buffer_release(struct videobuf_queue *vq, struct videobuf_buffer *vb) +{ + struct tm6000_buffer *buf = container_of(vb,struct tm6000_buffer,vb); + + free_buffer(vq,buf); +} + +static struct videobuf_queue_ops tm6000_video_qops = { + .buf_setup = buffer_setup, + .buf_prepare = buffer_prepare, + .buf_queue = buffer_queue, + .buf_release = buffer_release, +}; + +/* ------------------------------------------------------------------ + IOCTL handling + ------------------------------------------------------------------*/ + +static int res_get(struct tm6000_core *dev, struct tm6000_fh *fh) +{ + /* is it free? */ + mutex_lock(&dev->lock); + if (dev->resources) { + /* no, someone else uses it */ + mutex_unlock(&dev->lock); + return 0; + } + /* it's free, grab it */ + dev->resources =1; + dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: get\n"); + mutex_unlock(&dev->lock); + return 1; +} + +static int res_locked(struct tm6000_core *dev) +{ + return (dev->resources); +} + +static void res_free(struct tm6000_core *dev, struct tm6000_fh *fh) +{ + mutex_lock(&dev->lock); + dev->resources = 0; + dprintk(dev, V4L2_DEBUG_RES_LOCK, "res: put\n"); + mutex_unlock(&dev->lock); +} + +/* ------------------------------------------------------------------ + IOCTL vidioc handling + ------------------------------------------------------------------*/ +static int vidioc_querycap (struct file *file, void *priv, + struct v4l2_capability *cap) +{ + // struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev; + + strlcpy(cap->driver, "tm6000", sizeof(cap->driver)); + strlcpy(cap->card,"Trident TVMaster TM5600/6000/6010", sizeof(cap->card)); + // strlcpy(cap->bus_info, dev->udev->dev.bus_id, sizeof(cap->bus_info)); + cap->version = TM6000_VERSION; + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | + V4L2_CAP_STREAMING | + V4L2_CAP_TUNER | + V4L2_CAP_READWRITE; + return 0; +} + +static int vidioc_enum_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_fmtdesc *f) +{ + if (unlikely(f->index >= ARRAY_SIZE(format))) + return -EINVAL; + + strlcpy(f->description,format[f->index].name,sizeof(f->description)); + f->pixelformat = format[f->index].fourcc; + return 0; +} + +static int vidioc_g_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tm6000_fh *fh=priv; + + f->fmt.pix.width = fh->width; + f->fmt.pix.height = fh->height; + f->fmt.pix.field = fh->vb_vidq.field; + f->fmt.pix.pixelformat = fh->fmt->fourcc; + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fh->fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return (0); +} + +static struct tm6000_fmt* format_by_fourcc(unsigned int fourcc) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(format); i++) + if (format[i].fourcc == fourcc) + return format+i; + return NULL; +} + +static int vidioc_try_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tm6000_core *dev = ((struct tm6000_fh *)priv)->dev; + struct tm6000_fmt *fmt; + enum v4l2_field field; + + fmt = format_by_fourcc(f->fmt.pix.pixelformat); + if (NULL == fmt) { + dprintk(dev, V4L2_DEBUG_IOCTL_ARG, "Fourcc format (0x%08x)" + " invalid.\n", f->fmt.pix.pixelformat); + return -EINVAL; + } + + field = f->fmt.pix.field; + + if (field == V4L2_FIELD_ANY) { +// field=V4L2_FIELD_INTERLACED; + field=V4L2_FIELD_SEQ_TB; + } else if (V4L2_FIELD_INTERLACED != field) { + dprintk(dev, V4L2_DEBUG_IOCTL_ARG, "Field type invalid.\n"); + return -EINVAL; + } + + tm6000_get_std_res (dev); + + f->fmt.pix.width = dev->width; + f->fmt.pix.height = dev->height; + + f->fmt.pix.width &= ~0x01; + + f->fmt.pix.field = field; + + f->fmt.pix.bytesperline = + (f->fmt.pix.width * fmt->depth) >> 3; + f->fmt.pix.sizeimage = + f->fmt.pix.height * f->fmt.pix.bytesperline; + + return 0; +} + +/*FIXME: This seems to be generic enough to be at videodev2 */ +static int vidioc_s_fmt_vid_cap (struct file *file, void *priv, + struct v4l2_format *f) +{ + struct tm6000_fh *fh=priv; + struct tm6000_core *dev = fh->dev; + int ret = vidioc_try_fmt_vid_cap(file,fh,f); + if (ret < 0) + return (ret); + + fh->fmt = format_by_fourcc(f->fmt.pix.pixelformat); + fh->width = f->fmt.pix.width; + fh->height = f->fmt.pix.height; + fh->vb_vidq.field = f->fmt.pix.field; + fh->type = f->type; + + dev->fourcc = f->fmt.pix.pixelformat; + + tm6000_set_fourcc_format(dev); + + return (0); +} + +static int vidioc_reqbufs (struct file *file, void *priv, + struct v4l2_requestbuffers *p) +{ + struct tm6000_fh *fh=priv; + + return (videobuf_reqbufs(&fh->vb_vidq, p)); +} + +static int vidioc_querybuf (struct file *file, void *priv, + struct v4l2_buffer *p) +{ + struct tm6000_fh *fh=priv; + + return (videobuf_querybuf(&fh->vb_vidq, p)); +} + +static int vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct tm6000_fh *fh=priv; + + return (videobuf_qbuf(&fh->vb_vidq, p)); +} + +static int vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *p) +{ + struct tm6000_fh *fh=priv; + + return (videobuf_dqbuf(&fh->vb_vidq, p, + file->f_flags & O_NONBLOCK)); +} + +#ifdef CONFIG_VIDEO_V4L1_COMPAT +static int vidiocgmbuf (struct file *file, void *priv, struct video_mbuf *mbuf) +{ + struct tm6000_fh *fh=priv; + + return videobuf_cgmbuf (&fh->vb_vidq, mbuf, 8); +} +#endif + +static int vidioc_streamon(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct tm6000_fh *fh=priv; + struct tm6000_core *dev = fh->dev; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (i != fh->type) + return -EINVAL; + + if (!res_get(dev,fh)) + return -EBUSY; + return (videobuf_streamon(&fh->vb_vidq)); +} + +static int vidioc_streamoff(struct file *file, void *priv, enum v4l2_buf_type i) +{ + struct tm6000_fh *fh=priv; + struct tm6000_core *dev = fh->dev; + + if (fh->type != V4L2_BUF_TYPE_VIDEO_CAPTURE) + return -EINVAL; + if (i != fh->type) + return -EINVAL; + + videobuf_streamoff(&fh->vb_vidq); + res_free(dev,fh); + + return (0); +} + +static int vidioc_s_std (struct file *file, void *priv, v4l2_std_id *norm) +{ + int rc=0; + struct tm6000_fh *fh=priv; + struct tm6000_core *dev = fh->dev; + + rc=tm6000_set_standard (dev, norm); + + fh->width = dev->width; + fh->height = dev->height; + + if (rc<0) + return rc; + + v4l2_device_call_all(&dev->v4l2_dev, 0, core, s_std, dev->norm); + + return 0; +} + +static int vidioc_enum_input (struct file *file, void *priv, + struct v4l2_input *inp) +{ + switch (inp->index) { + case TM6000_INPUT_TV: + inp->type = V4L2_INPUT_TYPE_TUNER; + strcpy(inp->name,"Television"); + break; + case TM6000_INPUT_COMPOSITE: + inp->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(inp->name,"Composite"); + break; + case TM6000_INPUT_SVIDEO: + inp->type = V4L2_INPUT_TYPE_CAMERA; + strcpy(inp->name,"S-Video"); + break; + default: + return -EINVAL; + } + inp->std = TM6000_STD; + + return 0; +} + +static int vidioc_g_input (struct file *file, void *priv, unsigned int *i) +{ + struct tm6000_fh *fh=priv; + struct tm6000_core *dev = fh->dev; + + *i=dev->input; + + return 0; +} +static int vidioc_s_input (struct file *file, void *priv, unsigned int i) +{ + struct tm6000_fh *fh=priv; + struct tm6000_core *dev = fh->dev; + int rc=0; + char buf[1]; + + switch (i) { + case TM6000_INPUT_TV: + dev->input=i; + *buf=0; + break; + case TM6000_INPUT_COMPOSITE: + case TM6000_INPUT_SVIDEO: + dev->input=i; + *buf=1; + break; + default: + return -EINVAL; + } + rc=tm6000_read_write_usb (dev, USB_DIR_OUT | USB_TYPE_VENDOR, + REQ_03_SET_GET_MCU_PIN, 0x03, 1, buf, 1); + + if (!rc) { + dev->input=i; + rc=vidioc_s_std (file, priv, &dev->vfd->current_norm); + } + + return (rc); +} + + /* --- controls ---------------------------------------------- */ +static int vidioc_queryctrl (struct file *file, void *priv, + struct v4l2_queryctrl *qc) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(tm6000_qctrl); i++) + if (qc->id && qc->id == tm6000_qctrl[i].id) { + memcpy(qc, &(tm6000_qctrl[i]), + sizeof(*qc)); + return (0); + } + + return -EINVAL; +} + +static int vidioc_g_ctrl (struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct tm6000_fh *fh=priv; + struct tm6000_core *dev = fh->dev; + int val; + + /* FIXME: Probably, those won't work! Maybe we need shadow regs */ + switch (ctrl->id) { + case V4L2_CID_CONTRAST: + val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x08, 0); + break; + case V4L2_CID_BRIGHTNESS: + val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x09, 0); + return 0; + case V4L2_CID_SATURATION: + val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x0a, 0); + return 0; + case V4L2_CID_HUE: + val=tm6000_get_reg (dev, REQ_07_SET_GET_AVREG, 0x0b, 0); + return 0; + default: + return -EINVAL; + } + + if (val<0) + return val; + + ctrl->value=val; + + return 0; +} +static int vidioc_s_ctrl (struct file *file, void *priv, + struct v4l2_control *ctrl) +{ + struct tm6000_fh *fh =priv; + struct tm6000_core *dev = fh->dev; + u8 val=ctrl->value; + + switch (ctrl->id) { + case V4L2_CID_CONTRAST: + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x08, val); + return 0; + case V4L2_CID_BRIGHTNESS: + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x09, val); + return 0; + case V4L2_CID_SATURATION: + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0a, val); + return 0; + case V4L2_CID_HUE: + tm6000_set_reg (dev, REQ_07_SET_GET_AVREG, 0x0b, val); + return 0; + } + return -EINVAL; +} + +static int vidioc_g_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct tm6000_fh *fh =priv; + struct tm6000_core *dev = fh->dev; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + strcpy(t->name, "Television"); + t->type = V4L2_TUNER_ANALOG_TV; + t->capability = V4L2_TUNER_CAP_NORM; + t->rangehigh = 0xffffffffUL; + t->rxsubchans = V4L2_TUNER_SUB_MONO; + + return 0; +} + +static int vidioc_s_tuner (struct file *file, void *priv, + struct v4l2_tuner *t) +{ + struct tm6000_fh *fh =priv; + struct tm6000_core *dev = fh->dev; + + if (UNSET == dev->tuner_type) + return -EINVAL; + if (0 != t->index) + return -EINVAL; + + return 0; +} + +static int vidioc_g_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct tm6000_fh *fh =priv; + struct tm6000_core *dev = fh->dev; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + + f->type = V4L2_TUNER_ANALOG_TV; + f->frequency = dev->freq; + + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, g_frequency, f); + + return 0; +} + +static int vidioc_s_frequency (struct file *file, void *priv, + struct v4l2_frequency *f) +{ + struct tm6000_fh *fh =priv; + struct tm6000_core *dev = fh->dev; + + if (unlikely(f->type != V4L2_TUNER_ANALOG_TV)) + return -EINVAL; + + if (unlikely(UNSET == dev->tuner_type)) + return -EINVAL; + if (unlikely(f->tuner != 0)) + return -EINVAL; + +// mutex_lock(&dev->lock); + dev->freq = f->frequency; + v4l2_device_call_all(&dev->v4l2_dev, 0, tuner, s_frequency, f); +// mutex_unlock(&dev->lock); + + return 0; +} + +/* ------------------------------------------------------------------ + File operations for the device + ------------------------------------------------------------------*/ + +static int tm6000_open(struct file *file) +{ + struct video_device *vdev = video_devdata(file); + struct tm6000_core *dev = video_drvdata(file); + struct tm6000_fh *fh; + enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + int i,rc; + + printk(KERN_INFO "tm6000: open called (dev=%s)\n", + video_device_node_name(vdev)); + + dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: open called (dev=%s)\n", + video_device_node_name(vdev)); + + + /* If more than one user, mutex should be added */ + dev->users++; + + dprintk(dev, V4L2_DEBUG_OPEN, "open dev=%s type=%s users=%d\n", + video_device_node_name(vdev), v4l2_type_names[type], + dev->users); + + /* allocate + initialize per filehandle data */ + fh = kzalloc(sizeof(*fh),GFP_KERNEL); + if (NULL == fh) { + dev->users--; + return -ENOMEM; + } + + file->private_data = fh; + fh->dev = dev; + + fh->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + dev->fourcc = format[0].fourcc; + + fh->fmt = format_by_fourcc(dev->fourcc); + + tm6000_get_std_res (dev); + + fh->width = dev->width; + fh->height = dev->height; + + dprintk(dev, V4L2_DEBUG_OPEN, "Open: fh=0x%08lx, dev=0x%08lx, " + "dev->vidq=0x%08lx\n", + (unsigned long)fh,(unsigned long)dev,(unsigned long)&dev->vidq); + dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty " + "queued=%d\n",list_empty(&dev->vidq.queued)); + dprintk(dev, V4L2_DEBUG_OPEN, "Open: list_empty " + "active=%d\n",list_empty(&dev->vidq.active)); + + /* initialize hardware on analog mode */ + if (dev->mode!=TM6000_MODE_ANALOG) { + rc=tm6000_init_analog_mode (dev); + if (rc<0) + return rc; + + /* Put all controls at a sane state */ + for (i = 0; i < ARRAY_SIZE(tm6000_qctrl); i++) + qctl_regs[i] =tm6000_qctrl[i].default_value; + + dev->mode=TM6000_MODE_ANALOG; + } + + videobuf_queue_vmalloc_init(&fh->vb_vidq, &tm6000_video_qops, + NULL, &dev->slock, + fh->type, + V4L2_FIELD_INTERLACED, + sizeof(struct tm6000_buffer),fh); + + return 0; +} + +static ssize_t +tm6000_read(struct file *file, char __user *data, size_t count, loff_t *pos) +{ + struct tm6000_fh *fh = file->private_data; + + if (fh->type==V4L2_BUF_TYPE_VIDEO_CAPTURE) { + if (res_locked(fh->dev)) + return -EBUSY; + + return videobuf_read_stream(&fh->vb_vidq, data, count, pos, 0, + file->f_flags & O_NONBLOCK); + } + return 0; +} + +static unsigned int +tm6000_poll(struct file *file, struct poll_table_struct *wait) +{ + struct tm6000_fh *fh = file->private_data; + struct tm6000_buffer *buf; + + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != fh->type) + return POLLERR; + + if (res_get(fh->dev,fh)) { + /* streaming capture */ + if (list_empty(&fh->vb_vidq.stream)) + return POLLERR; + buf = list_entry(fh->vb_vidq.stream.next,struct tm6000_buffer,vb.stream); + } else { + /* read() capture */ + return videobuf_poll_stream(file, &fh->vb_vidq, + wait); + } + poll_wait(file, &buf->vb.done, wait); + if (buf->vb.state == VIDEOBUF_DONE || + buf->vb.state == VIDEOBUF_ERROR) + return POLLIN|POLLRDNORM; + return 0; +} + +static int tm6000_release(struct file *file) +{ + struct tm6000_fh *fh = file->private_data; + struct tm6000_core *dev = fh->dev; + struct video_device *vdev = video_devdata(file); + + dprintk(dev, V4L2_DEBUG_OPEN, "tm6000: close called (dev=%s, users=%d)\n", + video_device_node_name(vdev), dev->users); + + dev->users--; + + if (!dev->users) { + tm6000_uninit_isoc(dev); + videobuf_mmap_free(&fh->vb_vidq); + } + + kfree (fh); + + return 0; +} + +static int tm6000_mmap(struct file *file, struct vm_area_struct * vma) +{ + struct tm6000_fh *fh = file->private_data; + int ret; + + ret=videobuf_mmap_mapper(&fh->vb_vidq, vma); + + return ret; +} + +static struct v4l2_file_operations tm6000_fops = { + .owner = THIS_MODULE, + .open = tm6000_open, + .release = tm6000_release, + .ioctl = video_ioctl2, /* V4L2 ioctl handler */ + .read = tm6000_read, + .poll = tm6000_poll, + .mmap = tm6000_mmap, +}; + +static const struct v4l2_ioctl_ops video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt_vid_cap, + .vidioc_g_fmt_vid_cap = vidioc_g_fmt_vid_cap, + .vidioc_try_fmt_vid_cap = vidioc_try_fmt_vid_cap, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt_vid_cap, + .vidioc_s_std = vidioc_s_std, + .vidioc_enum_input = vidioc_enum_input, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, +#ifdef CONFIG_VIDEO_V4L1_COMPAT + .vidiocgmbuf = vidiocgmbuf, +#endif +}; + +static struct video_device tm6000_template = { + .name = "tm6000", + .fops = &tm6000_fops, + .ioctl_ops = &video_ioctl_ops, + .release = video_device_release, + .tvnorms = TM6000_STD, + .current_norm = V4L2_STD_NTSC_M, +}; + +/* ----------------------------------------------------------------- + Initialization and module stuff + ------------------------------------------------------------------*/ + +int tm6000_v4l2_register(struct tm6000_core *dev) +{ + int ret = -1; + struct video_device *vfd; + + vfd = video_device_alloc(); + if(!vfd) { + return -ENOMEM; + } + dev->vfd = vfd; + + /* init video dma queues */ + INIT_LIST_HEAD(&dev->vidq.active); + INIT_LIST_HEAD(&dev->vidq.queued); + + memcpy (dev->vfd, &tm6000_template, sizeof(*(dev->vfd))); + dev->vfd->debug=tm6000_debug; + vfd->v4l2_dev = &dev->v4l2_dev; + video_set_drvdata(vfd, dev); + + ret = video_register_device(dev->vfd, VFL_TYPE_GRABBER, video_nr); + printk(KERN_INFO "Trident TVMaster TM5600/TM6000/TM6010 USB2 board (Load status: %d)\n", ret); + return ret; +} + +int tm6000_v4l2_unregister(struct tm6000_core *dev) +{ + video_unregister_device(dev->vfd); + + return 0; +} + +int tm6000_v4l2_exit(void) +{ + return 0; +} + +module_param(video_nr, int, 0); +MODULE_PARM_DESC(video_nr,"Allow changing video device number"); + +module_param_named (debug, tm6000_debug, int, 0444); +MODULE_PARM_DESC(debug,"activates debug info"); + +module_param(vid_limit,int,0644); +MODULE_PARM_DESC(vid_limit,"capture memory limit in megabytes"); + diff --git a/drivers/staging/tm6000/tm6000.h b/drivers/staging/tm6000/tm6000.h new file mode 100644 index 000000000000..bd727f1e00c1 --- /dev/null +++ b/drivers/staging/tm6000/tm6000.h @@ -0,0 +1,283 @@ +/* + tm6000.h - driver for TM5600/TM6000/TM6010 USB video capture devices + + Copyright (C) 2006-2007 Mauro Carvalho Chehab <mchehab@infradead.org> + + Copyright (C) 2007 Michel Ludwig <michel.ludwig@gmail.com> + - DVB-T support + + 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 + + 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. + */ + +// Use the tm6000-hack, instead of the proper initialization code +//#define HACK 1 + +#include <linux/videodev2.h> +#include <media/v4l2-common.h> +#include <media/videobuf-vmalloc.h> +#include "tm6000-usb-isoc.h" +#include <linux/i2c.h> +#include <linux/mutex.h> +#include <media/v4l2-device.h> + + +#include <linux/dvb/frontend.h> +#include "dvb_demux.h" +#include "dvb_frontend.h" +#include "dmxdev.h" + +#define TM6000_VERSION KERNEL_VERSION(0, 0, 2) + +/* Inputs */ + +enum tm6000_itype { + TM6000_INPUT_TV = 0, + TM6000_INPUT_COMPOSITE, + TM6000_INPUT_SVIDEO, +}; + +enum tm6000_devtype { + TM6000 = 0, + TM5600, + TM6010, +}; + +/* ------------------------------------------------------------------ + Basic structures + ------------------------------------------------------------------*/ + +struct tm6000_fmt { + char *name; + u32 fourcc; /* v4l2 format id */ + int depth; +}; + +/* buffer for one video frame */ +struct tm6000_buffer { + /* common v4l buffer stuff -- must be first */ + struct videobuf_buffer vb; + + struct tm6000_fmt *fmt; +}; + +struct tm6000_dmaqueue { + struct list_head active; + struct list_head queued; + + /* thread for generating video stream*/ + struct task_struct *kthread; + wait_queue_head_t wq; + /* Counters to control fps rate */ + int frame; + int ini_jiffies; +}; + +/* device states */ +enum tm6000_core_state { + DEV_INITIALIZED = 0x01, + DEV_DISCONNECTED = 0x02, + DEV_MISCONFIGURED = 0x04, +}; + +/* io methods */ +enum tm6000_io_method { + IO_NONE, + IO_READ, + IO_MMAP, +}; + +enum tm6000_mode { + TM6000_MODE_UNKNOWN=0, + TM6000_MODE_ANALOG, + TM6000_MODE_DIGITAL, +}; + +struct tm6000_capabilities { + unsigned int has_tuner:1; + unsigned int has_tda9874:1; + unsigned int has_dvb:1; + unsigned int has_zl10353:1; + unsigned int has_eeprom:1; + unsigned int has_remote:1; +}; + +struct tm6000_dvb { + struct dvb_adapter adapter; + struct dvb_demux demux; + struct dvb_frontend *frontend; + struct dmxdev dmxdev; + unsigned int streams; + struct urb *bulk_urb; + struct mutex mutex; +}; + +struct tm6000_core { + /* generic device properties */ + char name[30]; /* name (including minor) of the device */ + int model; /* index in the device_data struct */ + int devno; /* marks the number of this device */ + enum tm6000_devtype dev_type; /* type of device */ + + v4l2_std_id norm; /* Current norm */ + int width,height; /* Selected resolution */ + + enum tm6000_core_state state; + + /* Device Capabilities*/ + struct tm6000_capabilities caps; + + /* Tuner configuration */ + int tuner_type; /* type of the tuner */ + int tuner_addr; /* tuner address */ + int tuner_reset_gpio; /* GPIO used for tuner reset */ + + /* Demodulator configuration */ + int demod_addr; /* demodulator address */ + + int audio_bitrate; + /* i2c i/o */ + struct i2c_adapter i2c_adap; + struct i2c_client i2c_client; + + /* video for linux */ + int users; + + /* various device info */ + unsigned int resources; + struct video_device *vfd; + struct tm6000_dmaqueue vidq; + struct v4l2_device v4l2_dev; + + int input; + int freq; + unsigned int fourcc; + + enum tm6000_mode mode; + + /* DVB-T support */ + struct tm6000_dvb *dvb; + + /* locks */ + struct mutex lock; + + /* usb transfer */ + struct usb_device *udev; /* the usb device */ + + struct usb_host_endpoint *bulk_in, *bulk_out, *isoc_in, *isoc_out; + unsigned int max_bulk_in, max_bulk_out; + unsigned int max_isoc_in, max_isoc_out; + + /* scaler!=0 if scaler is active*/ + int scaler; + + /* Isoc control struct */ + struct usb_isoc_ctl isoc_ctl; + + spinlock_t slock; +}; + +struct tm6000_fh { + struct tm6000_core *dev; + + /* video capture */ + struct tm6000_fmt *fmt; + unsigned int width,height; + struct videobuf_queue vb_vidq; + + enum v4l2_buf_type type; +}; + +#define TM6000_STD V4L2_STD_PAL|V4L2_STD_PAL_N|V4L2_STD_PAL_Nc| \ + V4L2_STD_PAL_M|V4L2_STD_PAL_60|V4L2_STD_NTSC_M| \ + V4L2_STD_NTSC_M_JP|V4L2_STD_SECAM + +/* In tm6000-cards.c */ + +int tm6000_tuner_callback (void *ptr, int component, int command, int arg); +int tm6000_cards_setup(struct tm6000_core *dev); + +/* In tm6000-core.c */ + +int tm6000_read_write_usb (struct tm6000_core *dev, u8 reqtype, u8 req, + u16 value, u16 index, u8 *buf, u16 len); +int tm6000_get_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index); +int tm6000_get_reg16(struct tm6000_core *dev, u8 req, u16 value, u16 index); +int tm6000_set_reg (struct tm6000_core *dev, u8 req, u16 value, u16 index); +int tm6000_init (struct tm6000_core *dev); + +int tm6000_init_analog_mode (struct tm6000_core *dev); +int tm6000_init_digital_mode (struct tm6000_core *dev); +int tm6000_set_audio_bitrate (struct tm6000_core *dev, int bitrate); + +int tm6000_dvb_register(struct tm6000_core *dev); +void tm6000_dvb_unregister(struct tm6000_core *dev); + +int tm6000_v4l2_register(struct tm6000_core *dev); +int tm6000_v4l2_unregister(struct tm6000_core *dev); +int tm6000_v4l2_exit(void); +void tm6000_set_fourcc_format(struct tm6000_core *dev); + +/* In tm6000-stds.c */ +void tm6000_get_std_res(struct tm6000_core *dev); +int tm6000_set_standard (struct tm6000_core *dev, v4l2_std_id *norm); + +/* In tm6000-i2c.c */ +int tm6000_i2c_register(struct tm6000_core *dev); +int tm6000_i2c_unregister(struct tm6000_core *dev); + +/* In tm6000-queue.c */ + +int tm6000_v4l2_mmap(struct file *filp, struct vm_area_struct *vma); + +int tm6000_vidioc_streamon(struct file *file, void *priv, + enum v4l2_buf_type i); +int tm6000_vidioc_streamoff(struct file *file, void *priv, + enum v4l2_buf_type i); +int tm6000_vidioc_reqbufs (struct file *file, void *priv, + struct v4l2_requestbuffers *rb); +int tm6000_vidioc_querybuf (struct file *file, void *priv, + struct v4l2_buffer *b); +int tm6000_vidioc_qbuf (struct file *file, void *priv, struct v4l2_buffer *b); +int tm6000_vidioc_dqbuf (struct file *file, void *priv, struct v4l2_buffer *b); +ssize_t tm6000_v4l2_read(struct file *filp, char __user * buf, size_t count, + loff_t * f_pos); +unsigned int tm6000_v4l2_poll(struct file *file, + struct poll_table_struct *wait); +int tm6000_queue_init(struct tm6000_core *dev); + +/* In tm6000-alsa.c */ +int tm6000_audio_init(struct tm6000_core *dev, int idx); + + +/* Debug stuff */ + +extern int tm6000_debug; + +#define dprintk(dev, level, fmt, arg...) do {\ + if (tm6000_debug & level) \ + printk(KERN_INFO "(%lu) %s %s :"fmt, jiffies, \ + dev->name, __FUNCTION__ , ##arg); } while (0) + +#define V4L2_DEBUG_REG 0x0004 +#define V4L2_DEBUG_I2C 0x0008 +#define V4L2_DEBUG_QUEUE 0x0010 +#define V4L2_DEBUG_ISOC 0x0020 +#define V4L2_DEBUG_RES_LOCK 0x0040 /* Resource locking */ +#define V4L2_DEBUG_OPEN 0x0080 /* video open/close debug */ + +#define tm6000_err(fmt, arg...) do {\ + printk(KERN_ERR "tm6000 %s :"fmt, \ + __FUNCTION__ , ##arg); } while (0) + + diff --git a/drivers/staging/vt6655/device_main.c b/drivers/staging/vt6655/device_main.c index ddf3aa04db09..1d643653a7ed 100644 --- a/drivers/staging/vt6655/device_main.c +++ b/drivers/staging/vt6655/device_main.c @@ -3079,8 +3079,7 @@ static void device_set_multi(struct net_device *dev) { PSMgmtObject pMgmt = pDevice->pMgmt; u32 mc_filter[2]; - int i; - struct dev_mc_list *mclist; + struct dev_mc_list *mclist; VNSvInPortB(pDevice->PortOffset + MAC_REG_RCR, &(pDevice->byRxMode)); @@ -3090,7 +3089,7 @@ static void device_set_multi(struct net_device *dev) { /* Unconditionally log net taps. */ pDevice->byRxMode |= (RCR_MULTICAST|RCR_BROADCAST|RCR_UNICAST); } - else if ((dev->mc_count > pDevice->multicast_limit) + else if ((netdev_mc_count(dev) > pDevice->multicast_limit) || (dev->flags & IFF_ALLMULTI)) { MACvSelectPage1(pDevice->PortOffset); VNSvOutPortD(pDevice->PortOffset + MAC_REG_MAR0, 0xffffffff); @@ -3100,8 +3099,7 @@ static void device_set_multi(struct net_device *dev) { } else { memset(mc_filter, 0, sizeof(mc_filter)); - for (i = 0, mclist = dev->mc_list; mclist && i < dev->mc_count; - i++, mclist = mclist->next) { + netdev_for_each_mc_addr(mclist, dev) { int bit_nr = ether_crc(ETH_ALEN, mclist->dmi_addr) >> 26; mc_filter[bit_nr >> 5] |= cpu_to_le32(1 << (bit_nr & 31)); } diff --git a/drivers/staging/vt6656/main_usb.c b/drivers/staging/vt6656/main_usb.c index ef17c4958c67..a8e1adbc9592 100644 --- a/drivers/staging/vt6656/main_usb.c +++ b/drivers/staging/vt6656/main_usb.c @@ -1596,7 +1596,7 @@ static void device_set_multi(struct net_device *dev) { PSMgmtObject pMgmt = &(pDevice->sMgmtObj); u32 mc_filter[2]; int ii; - struct dev_mc_list *mclist; + struct dev_mc_list *mclist; BYTE pbyData[8] = {0xff,0xff,0xff,0xff,0xff,0xff,0xff,0xff}; BYTE byTmpMode = 0; int rc; @@ -1619,7 +1619,8 @@ static void device_set_multi(struct net_device *dev) { // Unconditionally log net taps. pDevice->byRxMode |= (RCR_MULTICAST|RCR_BROADCAST|RCR_UNICAST); } - else if ((dev->mc_count > pDevice->multicast_limit) || (dev->flags & IFF_ALLMULTI)) { + else if ((netdev_mc_count(dev) > pDevice->multicast_limit) || + (dev->flags & IFF_ALLMULTI)) { CONTROLnsRequestOut(pDevice, MESSAGE_TYPE_WRITE, MAC_REG_MAR0, @@ -1631,8 +1632,7 @@ static void device_set_multi(struct net_device *dev) { } else { memset(mc_filter, 0, sizeof(mc_filter)); - for (ii = 0, mclist = dev->mc_list; mclist && ii < dev->mc_count; - ii++, mclist = mclist->next) { + netdev_for_each_mc_addr(mclist, dev) { int bit_nr = ether_crc(ETH_ALEN, mclist->dmi_addr) >> 26; mc_filter[bit_nr >> 5] |= cpu_to_le32(1 << (bit_nr & 31)); } diff --git a/drivers/staging/wavelan/wavelan.c b/drivers/staging/wavelan/wavelan.c index d634b2da3b84..54ca63196fdd 100644 --- a/drivers/staging/wavelan/wavelan.c +++ b/drivers/staging/wavelan/wavelan.c @@ -1367,7 +1367,7 @@ static void wavelan_set_multicast_list(struct net_device * dev) #ifdef DEBUG_IOCTL_INFO printk(KERN_DEBUG "%s: wavelan_set_multicast_list(): setting Rx mode %02X to %d addresses.\n", - dev->name, dev->flags, dev->mc_count); + dev->name, dev->flags, netdev_mc_count(dev)); #endif /* Are we asking for promiscuous mode, @@ -1375,7 +1375,7 @@ static void wavelan_set_multicast_list(struct net_device * dev) * or too many multicast addresses for the hardware filter? */ if ((dev->flags & IFF_PROMISC) || (dev->flags & IFF_ALLMULTI) || - (dev->mc_count > I82586_MAX_MULTICAST_ADDRESSES)) { + (netdev_mc_count(dev) > I82586_MAX_MULTICAST_ADDRESSES)) { /* * Enable promiscuous mode: receive all packets. */ @@ -1387,17 +1387,17 @@ static void wavelan_set_multicast_list(struct net_device * dev) } } else /* Are there multicast addresses to send? */ - if (dev->mc_list != (struct dev_mc_list *) NULL) { + if (!netdev_mc_empty(dev)) { /* * Disable promiscuous mode, but receive all packets * in multicast list */ #ifdef MULTICAST_AVOID - if (lp->promiscuous || (dev->mc_count != lp->mc_count)) + if (lp->promiscuous || (netdev_mc_count(dev) != lp->mc_count)) #endif { lp->promiscuous = 0; - lp->mc_count = dev->mc_count; + lp->mc_count = netdev_mc_count(dev); wv_82586_reconfig(dev); } @@ -3531,7 +3531,7 @@ static void wv_82586_config(struct net_device * dev) /* Any address to set? */ if (lp->mc_count) { - for (dmi = dev->mc_list; dmi; dmi = dmi->next) + netdev_for_each_mc_addr(dmi, dev) outsw(PIOP1(ioaddr), (u16 *) dmi->dmi_addr, WAVELAN_ADDR_SIZE >> 1); @@ -3539,7 +3539,7 @@ static void wv_82586_config(struct net_device * dev) printk(KERN_DEBUG "%s: wv_82586_config(): set %d multicast addresses:\n", dev->name, lp->mc_count); - for (dmi = dev->mc_list; dmi; dmi = dmi->next) + netdev_for_each_mc_addr(dmi, dev) printk(KERN_DEBUG " %pM\n", dmi->dmi_addr); #endif } diff --git a/drivers/staging/wavelan/wavelan_cs.c b/drivers/staging/wavelan/wavelan_cs.c index 10c702b5be4a..04f691d127b4 100644 --- a/drivers/staging/wavelan/wavelan_cs.c +++ b/drivers/staging/wavelan/wavelan_cs.c @@ -1373,7 +1373,7 @@ wavelan_set_multicast_list(struct net_device * dev) #ifdef DEBUG_IOCTL_INFO printk(KERN_DEBUG "%s: wavelan_set_multicast_list(): setting Rx mode %02X to %d addresses.\n", - dev->name, dev->flags, dev->mc_count); + dev->name, dev->flags, netdev_mc_count(dev)); #endif if(dev->flags & IFF_PROMISC) @@ -1394,7 +1394,7 @@ wavelan_set_multicast_list(struct net_device * dev) /* If all multicast addresses * or too much multicast addresses for the hardware filter */ if((dev->flags & IFF_ALLMULTI) || - (dev->mc_count > I82593_MAX_MULTICAST_ADDRESSES)) + (netdev_mc_count(dev) > I82593_MAX_MULTICAST_ADDRESSES)) { /* * Disable promiscuous mode, but active the all multicast mode @@ -1410,20 +1410,19 @@ wavelan_set_multicast_list(struct net_device * dev) } else /* If there is some multicast addresses to send */ - if(dev->mc_list != (struct dev_mc_list *) NULL) - { + if (!netdev_mc_empty(dev)) { /* * Disable promiscuous mode, but receive all packets * in multicast list */ #ifdef MULTICAST_AVOID if(lp->promiscuous || lp->allmulticast || - (dev->mc_count != lp->mc_count)) + (netdev_mc_count(dev) != lp->mc_count)) #endif { lp->promiscuous = 0; lp->allmulticast = 0; - lp->mc_count = dev->mc_count; + lp->mc_count = netdev_mc_count(dev); wv_82593_reconfig(dev); } @@ -3598,13 +3597,13 @@ wv_82593_config(struct net_device * dev) /* If any multicast address to set */ if(lp->mc_count) { - struct dev_mc_list * dmi; + struct dev_mc_list *dmi; int addrs_len = WAVELAN_ADDR_SIZE * lp->mc_count; #ifdef DEBUG_CONFIG_INFO printk(KERN_DEBUG "%s: wv_hw_config(): set %d multicast addresses:\n", dev->name, lp->mc_count); - for(dmi=dev->mc_list; dmi; dmi=dmi->next) + netdev_for_each_mc_addr(dmi, dev) printk(KERN_DEBUG " %pM\n", dmi->dmi_addr); #endif @@ -3613,7 +3612,7 @@ wv_82593_config(struct net_device * dev) outb(((TX_BASE >> 8) & PIORH_MASK) | PIORH_SEL_TX, PIORH(base)); outb(addrs_len & 0xff, PIOP(base)); /* byte count lsb */ outb((addrs_len >> 8), PIOP(base)); /* byte count msb */ - for(dmi=dev->mc_list; dmi; dmi=dmi->next) + netdev_for_each_mc_addr(dmi, dev) outsb(PIOP(base), dmi->dmi_addr, dmi->dmi_addrlen); /* reset transmit DMA pointer */ @@ -3622,7 +3621,8 @@ wv_82593_config(struct net_device * dev) if(!wv_82593_cmd(dev, "wv_82593_config(): mc-setup", OP0_MC_SETUP, SR0_MC_SETUP_DONE)) ret = FALSE; - lp->mc_count = dev->mc_count; /* remember to avoid repeated reset */ + /* remember to avoid repeated reset */ + lp->mc_count = netdev_mc_count(dev); } /* Job done, clear the flag */ diff --git a/drivers/staging/wlags49_h2/wl_netdev.c b/drivers/staging/wlags49_h2/wl_netdev.c index 6d4019160ebe..fa082d90fcad 100644 --- a/drivers/staging/wlags49_h2/wl_netdev.c +++ b/drivers/staging/wlags49_h2/wl_netdev.c @@ -1049,7 +1049,7 @@ void wl_multicast( struct net_device *dev ) //;?seems reasonable that even an AP-only driver could afford this small additional footprint int x; - struct dev_mc_list *mclist; + struct dev_mc_list *mclist; struct wl_private *lp = wl_priv(dev); unsigned long flags; /*------------------------------------------------------------------------*/ @@ -1070,13 +1070,11 @@ void wl_multicast( struct net_device *dev ) ( dev->flags & IFF_MULTICAST ) ? "Multicast " : "", ( dev->flags & IFF_ALLMULTI ) ? "All-Multicast" : "" ); - DBG_PRINT( " mc_count: %d\n", dev->mc_count ); + DBG_PRINT( " mc_count: %d\n", netdev_mc_count(dev)); - for( x = 0, mclist = dev->mc_list; mclist && x < dev->mc_count; - x++, mclist = mclist->next ) { + netdev_for_each_mc_addr(mclist, dev) DBG_PRINT( " %s (%d)\n", DbgHwAddr(mclist->dmi_addr), mclist->dmi_addrlen ); - } } #endif /* DBG */ @@ -1103,7 +1101,7 @@ void wl_multicast( struct net_device *dev ) DBG_PRINT( "Enabling Promiscuous mode (IFF_PROMISC)\n" ); hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord )); } - else if(( dev->mc_count > HCF_MAX_MULTICAST ) || + else if ((netdev_mc_count(dev) > HCF_MAX_MULTICAST) || ( dev->flags & IFF_ALLMULTI )) { /* Shutting off this filter will enable all multicast frames to be sent up from the device; however, this is a static RID, so @@ -1115,17 +1113,15 @@ void wl_multicast( struct net_device *dev ) hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord )); wl_apply( lp ); } - else if( dev->mc_count != 0 ) { + else if (!netdev_mc_empty(dev)) { /* Set the multicast addresses */ - lp->ltvRecord.len = ( dev->mc_count * 3 ) + 1; + lp->ltvRecord.len = ( netdev_mc_count(dev) * 3 ) + 1; lp->ltvRecord.typ = CFG_GROUP_ADDR; - for( x = 0, mclist = dev->mc_list; - ( x < dev->mc_count ) && ( mclist != NULL ); - x++, mclist = mclist->next ) { - memcpy( &( lp->ltvRecord.u.u8[x * ETH_ALEN] ), - mclist->dmi_addr, ETH_ALEN ); - } + x = 0; + netdev_for_each_mc_addr(mclist, dev) + memcpy(&(lp->ltvRecord.u.u8[x++ * ETH_ALEN]), + mclist->dmi_addr, ETH_ALEN); DBG_PRINT( "Setting multicast list\n" ); hcf_put_info( &( lp->hcfCtx ), (LTVP)&( lp->ltvRecord )); } else { @@ -1194,9 +1190,7 @@ static const struct net_device_ops wl_netdev_ops = .ndo_stop = &wl_adapter_close, .ndo_do_ioctl = &wl_ioctl, -#ifdef HAVE_TX_TIMEOUT .ndo_tx_timeout = &wl_tx_timeout, -#endif #ifdef CONFIG_NET_POLL_CONTROLLER .ndo_poll_controller = wl_poll, @@ -1270,9 +1264,7 @@ struct net_device * wl_device_alloc( void ) dev->stop = &wl_adapter_close; dev->do_ioctl = &wl_ioctl; -#ifdef HAVE_TX_TIMEOUT dev->tx_timeout = &wl_tx_timeout; -#endif #ifdef CONFIG_NET_POLL_CONTROLLER dev->poll_controller = wl_poll; @@ -1280,9 +1272,7 @@ struct net_device * wl_device_alloc( void ) #endif // (LINUX_VERSION_CODE > KERNEL_VERSION(2,6,30)) -#ifdef HAVE_TX_TIMEOUT dev->watchdog_timeo = TX_TIMEOUT; -#endif dev->ethtool_ops = &wl_ethtool_ops; |