From ef6ada3de49074a913fb72f163657158be2c0e98 Mon Sep 17 00:00:00 2001 From: Joern Engel Date: Fri, 20 Nov 2009 22:17:12 +0100 Subject: [LogFS] Add MAINTAINERS entry --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c824b4d62754..132310560765 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3274,6 +3274,13 @@ S: Maintained F: Documentation/ldm.txt F: fs/partitions/ldm.* +LogFS +M: Joern Engel +L: logfs@logfs.org +W: logfs.org +S: Maintained +F: fs/logfs/ + LSILOGIC MPT FUSION DRIVERS (FC/SAS/SPI) M: Eric Moore M: support@lsi.com -- cgit v1.2.3 From f9094d8e5587cf21091a9516628147c0b55e4264 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Mon, 4 Jan 2010 16:04:00 -0800 Subject: rcu: Make MAINTAINERS file match new RCU reality Both rcutorture and RCU are supported. Also, update the files to match the new layout. Signed-off-by: Paul E. McKenney Cc: laijs@cn.fujitsu.com Cc: dipankar@in.ibm.com Cc: mathieu.desnoyers@polymtl.ca Cc: josh@joshtriplett.org Cc: dvhltc@us.ibm.com Cc: niv@us.ibm.com Cc: peterz@infradead.org Cc: rostedt@goodmis.org Cc: Valdis.Kletnieks@vt.edu Cc: dhowells@redhat.com LKML-Reference: <12626498422334-git-send-email-> Signed-off-by: Ingo Molnar --- MAINTAINERS | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c8f47bf154f4..175da7c0803f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4509,7 +4509,7 @@ F: drivers/net/wireless/ray* RCUTORTURE MODULE M: Josh Triplett M: "Paul E. McKenney" -S: Maintained +S: Supported F: Documentation/RCU/torture.txt F: kernel/rcutorture.c @@ -4534,11 +4534,12 @@ M: Dipankar Sarma M: "Paul E. McKenney" W: http://www.rdrop.com/users/paulmck/rclock/ S: Supported -F: Documentation/RCU/rcu.txt -F: Documentation/RCU/rcuref.txt -F: include/linux/rcupdate.h -F: include/linux/srcu.h -F: kernel/rcupdate.c +F: Documentation/RCU/ +F: include/linux/rcu* +F: include/linux/srcu* +F: kernel/rcu* +F: kernel/srcu* +X: kernel/rcutorture.c REAL TIME CLOCK DRIVER M: Paul Gortmaker -- cgit v1.2.3 From 3a4d5c94e959359ece6d6b55045c3f046677f55c Mon Sep 17 00:00:00 2001 From: "Michael S. Tsirkin" Date: Thu, 14 Jan 2010 06:17:27 +0000 Subject: vhost_net: a kernel-level virtio server What it is: vhost net is a character device that can be used to reduce the number of system calls involved in virtio networking. Existing virtio net code is used in the guest without modification. There's similarity with vringfd, with some differences and reduced scope - uses eventfd for signalling - structures can be moved around in memory at any time (good for migration, bug work-arounds in userspace) - write logging is supported (good for migration) - support memory table and not just an offset (needed for kvm) common virtio related code has been put in a separate file vhost.c and can be made into a separate module if/when more backends appear. I used Rusty's lguest.c as the source for developing this part : this supplied me with witty comments I wouldn't be able to write myself. What it is not: vhost net is not a bus, and not a generic new system call. No assumptions are made on how guest performs hypercalls. Userspace hypervisors are supported as well as kvm. How it works: Basically, we connect virtio frontend (configured by userspace) to a backend. The backend could be a network device, or a tap device. Backend is also configured by userspace, including vlan/mac etc. Status: This works for me, and I haven't see any crashes. Compared to userspace, people reported improved latency (as I save up to 4 system calls per packet), as well as better bandwidth and CPU utilization. Features that I plan to look at in the future: - mergeable buffers - zero copy - scalability tuning: figure out the best threading model to use Note on RCU usage (this is also documented in vhost.h, near private_pointer which is the value protected by this variant of RCU): what is happening is that the rcu_dereference() is being used in a workqueue item. The role of rcu_read_lock() is taken on by the start of execution of the workqueue item, of rcu_read_unlock() by the end of execution of the workqueue item, and of synchronize_rcu() by flush_workqueue()/flush_work(). In the future we might need to apply some gcc attribute or sparse annotation to the function passed to INIT_WORK(). Paul's ack below is for this RCU usage. (Includes fixes by Alan Cox , David L Stevens , Chris Wright ) Acked-by: Rusty Russell Acked-by: Arnd Bergmann Acked-by: "Paul E. McKenney" Signed-off-by: Michael S. Tsirkin Signed-off-by: David S. Miller --- MAINTAINERS | 9 + arch/ia64/kvm/Kconfig | 1 + arch/powerpc/kvm/Kconfig | 1 + arch/s390/kvm/Kconfig | 1 + arch/x86/kvm/Kconfig | 1 + drivers/Makefile | 1 + drivers/vhost/Kconfig | 11 + drivers/vhost/Makefile | 2 + drivers/vhost/net.c | 661 ++++++++++++++++++++++++++ drivers/vhost/vhost.c | 1098 ++++++++++++++++++++++++++++++++++++++++++++ drivers/vhost/vhost.h | 161 +++++++ include/linux/Kbuild | 1 + include/linux/miscdevice.h | 1 + include/linux/vhost.h | 130 ++++++ 14 files changed, 2079 insertions(+) create mode 100644 drivers/vhost/Kconfig create mode 100644 drivers/vhost/Makefile create mode 100644 drivers/vhost/net.c create mode 100644 drivers/vhost/vhost.c create mode 100644 drivers/vhost/vhost.h create mode 100644 include/linux/vhost.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 745643b8c344..337dffbe9a47 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5803,6 +5803,15 @@ S: Maintained F: Documentation/filesystems/vfat.txt F: fs/fat/ +VIRTIO HOST (VHOST) +M: "Michael S. Tsirkin" +L: kvm@vger.kernel.org +L: virtualization@lists.osdl.org +L: netdev@vger.kernel.org +S: Maintained +F: drivers/vhost/ +F: include/linux/vhost.h + VIA RHINE NETWORK DRIVER M: Roger Luethi S: Maintained diff --git a/arch/ia64/kvm/Kconfig b/arch/ia64/kvm/Kconfig index ef3e7be29caf..01c75797119c 100644 --- a/arch/ia64/kvm/Kconfig +++ b/arch/ia64/kvm/Kconfig @@ -47,6 +47,7 @@ config KVM_INTEL Provides support for KVM on Itanium 2 processors equipped with the VT extensions. +source drivers/vhost/Kconfig source drivers/virtio/Kconfig endif # VIRTUALIZATION diff --git a/arch/powerpc/kvm/Kconfig b/arch/powerpc/kvm/Kconfig index 07703f72330e..e28841fbfb8d 100644 --- a/arch/powerpc/kvm/Kconfig +++ b/arch/powerpc/kvm/Kconfig @@ -75,6 +75,7 @@ config KVM_E500 If unsure, say N. +source drivers/vhost/Kconfig source drivers/virtio/Kconfig endif # VIRTUALIZATION diff --git a/arch/s390/kvm/Kconfig b/arch/s390/kvm/Kconfig index 6ee55ae84ce2..a7251580891c 100644 --- a/arch/s390/kvm/Kconfig +++ b/arch/s390/kvm/Kconfig @@ -35,6 +35,7 @@ config KVM # OK, it's a little counter-intuitive to do this, but it puts it neatly under # the virtualization menu. +source drivers/vhost/Kconfig source drivers/virtio/Kconfig endif # VIRTUALIZATION diff --git a/arch/x86/kvm/Kconfig b/arch/x86/kvm/Kconfig index 4cd498332466..3c4d0109ad20 100644 --- a/arch/x86/kvm/Kconfig +++ b/arch/x86/kvm/Kconfig @@ -65,6 +65,7 @@ config KVM_AMD # OK, it's a little counter-intuitive to do this, but it puts it neatly under # the virtualization menu. +source drivers/vhost/Kconfig source drivers/lguest/Kconfig source drivers/virtio/Kconfig diff --git a/drivers/Makefile b/drivers/Makefile index 6ee53c7a57a1..81e36596b1e9 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -106,6 +106,7 @@ obj-$(CONFIG_HID) += hid/ obj-$(CONFIG_PPC_PS3) += ps3/ obj-$(CONFIG_OF) += of/ obj-$(CONFIG_SSB) += ssb/ +obj-$(CONFIG_VHOST_NET) += vhost/ obj-$(CONFIG_VIRTIO) += virtio/ obj-$(CONFIG_VLYNQ) += vlynq/ obj-$(CONFIG_STAGING) += staging/ diff --git a/drivers/vhost/Kconfig b/drivers/vhost/Kconfig new file mode 100644 index 000000000000..9f409f447aea --- /dev/null +++ b/drivers/vhost/Kconfig @@ -0,0 +1,11 @@ +config VHOST_NET + tristate "Host kernel accelerator for virtio net (EXPERIMENTAL)" + depends on NET && EVENTFD && EXPERIMENTAL + ---help--- + This kernel module can be loaded in host kernel to accelerate + guest networking with virtio_net. Not to be confused with virtio_net + module itself which needs to be loaded in guest kernel. + + To compile this driver as a module, choose M here: the module will + be called vhost_net. + diff --git a/drivers/vhost/Makefile b/drivers/vhost/Makefile new file mode 100644 index 000000000000..72dd02050bb9 --- /dev/null +++ b/drivers/vhost/Makefile @@ -0,0 +1,2 @@ +obj-$(CONFIG_VHOST_NET) += vhost_net.o +vhost_net-y := vhost.o net.o diff --git a/drivers/vhost/net.c b/drivers/vhost/net.c new file mode 100644 index 000000000000..4c8928319e1d --- /dev/null +++ b/drivers/vhost/net.c @@ -0,0 +1,661 @@ +/* Copyright (C) 2009 Red Hat, Inc. + * Author: Michael S. Tsirkin + * + * This work is licensed under the terms of the GNU GPL, version 2. + * + * virtio-net server in host kernel. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +#include "vhost.h" + +/* Max number of bytes transferred before requeueing the job. + * Using this limit prevents one virtqueue from starving others. */ +#define VHOST_NET_WEIGHT 0x80000 + +enum { + VHOST_NET_VQ_RX = 0, + VHOST_NET_VQ_TX = 1, + VHOST_NET_VQ_MAX = 2, +}; + +enum vhost_net_poll_state { + VHOST_NET_POLL_DISABLED = 0, + VHOST_NET_POLL_STARTED = 1, + VHOST_NET_POLL_STOPPED = 2, +}; + +struct vhost_net { + struct vhost_dev dev; + struct vhost_virtqueue vqs[VHOST_NET_VQ_MAX]; + struct vhost_poll poll[VHOST_NET_VQ_MAX]; + /* Tells us whether we are polling a socket for TX. + * We only do this when socket buffer fills up. + * Protected by tx vq lock. */ + enum vhost_net_poll_state tx_poll_state; +}; + +/* Pop first len bytes from iovec. Return number of segments used. */ +static int move_iovec_hdr(struct iovec *from, struct iovec *to, + size_t len, int iov_count) +{ + int seg = 0; + size_t size; + while (len && seg < iov_count) { + size = min(from->iov_len, len); + to->iov_base = from->iov_base; + to->iov_len = size; + from->iov_len -= size; + from->iov_base += size; + len -= size; + ++from; + ++to; + ++seg; + } + return seg; +} + +/* Caller must have TX VQ lock */ +static void tx_poll_stop(struct vhost_net *net) +{ + if (likely(net->tx_poll_state != VHOST_NET_POLL_STARTED)) + return; + vhost_poll_stop(net->poll + VHOST_NET_VQ_TX); + net->tx_poll_state = VHOST_NET_POLL_STOPPED; +} + +/* Caller must have TX VQ lock */ +static void tx_poll_start(struct vhost_net *net, struct socket *sock) +{ + if (unlikely(net->tx_poll_state != VHOST_NET_POLL_STOPPED)) + return; + vhost_poll_start(net->poll + VHOST_NET_VQ_TX, sock->file); + net->tx_poll_state = VHOST_NET_POLL_STARTED; +} + +/* Expects to be always run from workqueue - which acts as + * read-size critical section for our kind of RCU. */ +static void handle_tx(struct vhost_net *net) +{ + struct vhost_virtqueue *vq = &net->dev.vqs[VHOST_NET_VQ_TX]; + unsigned head, out, in, s; + struct msghdr msg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_control = NULL, + .msg_controllen = 0, + .msg_iov = vq->iov, + .msg_flags = MSG_DONTWAIT, + }; + size_t len, total_len = 0; + int err, wmem; + size_t hdr_size; + struct socket *sock = rcu_dereference(vq->private_data); + if (!sock) + return; + + wmem = atomic_read(&sock->sk->sk_wmem_alloc); + if (wmem >= sock->sk->sk_sndbuf) + return; + + use_mm(net->dev.mm); + mutex_lock(&vq->mutex); + vhost_disable_notify(vq); + + if (wmem < sock->sk->sk_sndbuf * 2) + tx_poll_stop(net); + hdr_size = vq->hdr_size; + + for (;;) { + head = vhost_get_vq_desc(&net->dev, vq, vq->iov, + ARRAY_SIZE(vq->iov), + &out, &in, + NULL, NULL); + /* Nothing new? Wait for eventfd to tell us they refilled. */ + if (head == vq->num) { + wmem = atomic_read(&sock->sk->sk_wmem_alloc); + if (wmem >= sock->sk->sk_sndbuf * 3 / 4) { + tx_poll_start(net, sock); + set_bit(SOCK_ASYNC_NOSPACE, &sock->flags); + break; + } + if (unlikely(vhost_enable_notify(vq))) { + vhost_disable_notify(vq); + continue; + } + break; + } + if (in) { + vq_err(vq, "Unexpected descriptor format for TX: " + "out %d, int %d\n", out, in); + break; + } + /* Skip header. TODO: support TSO. */ + s = move_iovec_hdr(vq->iov, vq->hdr, hdr_size, out); + msg.msg_iovlen = out; + len = iov_length(vq->iov, out); + /* Sanity check */ + if (!len) { + vq_err(vq, "Unexpected header len for TX: " + "%zd expected %zd\n", + iov_length(vq->hdr, s), hdr_size); + break; + } + /* TODO: Check specific error and bomb out unless ENOBUFS? */ + err = sock->ops->sendmsg(NULL, sock, &msg, len); + if (unlikely(err < 0)) { + vhost_discard_vq_desc(vq); + tx_poll_start(net, sock); + break; + } + if (err != len) + pr_err("Truncated TX packet: " + " len %d != %zd\n", err, len); + vhost_add_used_and_signal(&net->dev, vq, head, 0); + total_len += len; + if (unlikely(total_len >= VHOST_NET_WEIGHT)) { + vhost_poll_queue(&vq->poll); + break; + } + } + + mutex_unlock(&vq->mutex); + unuse_mm(net->dev.mm); +} + +/* Expects to be always run from workqueue - which acts as + * read-size critical section for our kind of RCU. */ +static void handle_rx(struct vhost_net *net) +{ + struct vhost_virtqueue *vq = &net->dev.vqs[VHOST_NET_VQ_RX]; + unsigned head, out, in, log, s; + struct vhost_log *vq_log; + struct msghdr msg = { + .msg_name = NULL, + .msg_namelen = 0, + .msg_control = NULL, /* FIXME: get and handle RX aux data. */ + .msg_controllen = 0, + .msg_iov = vq->iov, + .msg_flags = MSG_DONTWAIT, + }; + + struct virtio_net_hdr hdr = { + .flags = 0, + .gso_type = VIRTIO_NET_HDR_GSO_NONE + }; + + size_t len, total_len = 0; + int err; + size_t hdr_size; + struct socket *sock = rcu_dereference(vq->private_data); + if (!sock || skb_queue_empty(&sock->sk->sk_receive_queue)) + return; + + use_mm(net->dev.mm); + mutex_lock(&vq->mutex); + vhost_disable_notify(vq); + hdr_size = vq->hdr_size; + + vq_log = unlikely(vhost_has_feature(&net->dev, VHOST_F_LOG_ALL)) ? + vq->log : NULL; + + for (;;) { + head = vhost_get_vq_desc(&net->dev, vq, vq->iov, + ARRAY_SIZE(vq->iov), + &out, &in, + vq_log, &log); + /* OK, now we need to know about added descriptors. */ + if (head == vq->num) { + if (unlikely(vhost_enable_notify(vq))) { + /* They have slipped one in as we were + * doing that: check again. */ + vhost_disable_notify(vq); + continue; + } + /* Nothing new? Wait for eventfd to tell us + * they refilled. */ + break; + } + /* We don't need to be notified again. */ + if (out) { + vq_err(vq, "Unexpected descriptor format for RX: " + "out %d, int %d\n", + out, in); + break; + } + /* Skip header. TODO: support TSO/mergeable rx buffers. */ + s = move_iovec_hdr(vq->iov, vq->hdr, hdr_size, in); + msg.msg_iovlen = in; + len = iov_length(vq->iov, in); + /* Sanity check */ + if (!len) { + vq_err(vq, "Unexpected header len for RX: " + "%zd expected %zd\n", + iov_length(vq->hdr, s), hdr_size); + break; + } + err = sock->ops->recvmsg(NULL, sock, &msg, + len, MSG_DONTWAIT | MSG_TRUNC); + /* TODO: Check specific error and bomb out unless EAGAIN? */ + if (err < 0) { + vhost_discard_vq_desc(vq); + break; + } + /* TODO: Should check and handle checksum. */ + if (err > len) { + pr_err("Discarded truncated rx packet: " + " len %d > %zd\n", err, len); + vhost_discard_vq_desc(vq); + continue; + } + len = err; + err = memcpy_toiovec(vq->hdr, (unsigned char *)&hdr, hdr_size); + if (err) { + vq_err(vq, "Unable to write vnet_hdr at addr %p: %d\n", + vq->iov->iov_base, err); + break; + } + len += hdr_size; + vhost_add_used_and_signal(&net->dev, vq, head, len); + if (unlikely(vq_log)) + vhost_log_write(vq, vq_log, log, len); + total_len += len; + if (unlikely(total_len >= VHOST_NET_WEIGHT)) { + vhost_poll_queue(&vq->poll); + break; + } + } + + mutex_unlock(&vq->mutex); + unuse_mm(net->dev.mm); +} + +static void handle_tx_kick(struct work_struct *work) +{ + struct vhost_virtqueue *vq; + struct vhost_net *net; + vq = container_of(work, struct vhost_virtqueue, poll.work); + net = container_of(vq->dev, struct vhost_net, dev); + handle_tx(net); +} + +static void handle_rx_kick(struct work_struct *work) +{ + struct vhost_virtqueue *vq; + struct vhost_net *net; + vq = container_of(work, struct vhost_virtqueue, poll.work); + net = container_of(vq->dev, struct vhost_net, dev); + handle_rx(net); +} + +static void handle_tx_net(struct work_struct *work) +{ + struct vhost_net *net; + net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_TX].work); + handle_tx(net); +} + +static void handle_rx_net(struct work_struct *work) +{ + struct vhost_net *net; + net = container_of(work, struct vhost_net, poll[VHOST_NET_VQ_RX].work); + handle_rx(net); +} + +static int vhost_net_open(struct inode *inode, struct file *f) +{ + struct vhost_net *n = kmalloc(sizeof *n, GFP_KERNEL); + int r; + if (!n) + return -ENOMEM; + n->vqs[VHOST_NET_VQ_TX].handle_kick = handle_tx_kick; + n->vqs[VHOST_NET_VQ_RX].handle_kick = handle_rx_kick; + r = vhost_dev_init(&n->dev, n->vqs, VHOST_NET_VQ_MAX); + if (r < 0) { + kfree(n); + return r; + } + + vhost_poll_init(n->poll + VHOST_NET_VQ_TX, handle_tx_net, POLLOUT); + vhost_poll_init(n->poll + VHOST_NET_VQ_RX, handle_rx_net, POLLIN); + n->tx_poll_state = VHOST_NET_POLL_DISABLED; + + f->private_data = n; + + return 0; +} + +static void vhost_net_disable_vq(struct vhost_net *n, + struct vhost_virtqueue *vq) +{ + if (!vq->private_data) + return; + if (vq == n->vqs + VHOST_NET_VQ_TX) { + tx_poll_stop(n); + n->tx_poll_state = VHOST_NET_POLL_DISABLED; + } else + vhost_poll_stop(n->poll + VHOST_NET_VQ_RX); +} + +static void vhost_net_enable_vq(struct vhost_net *n, + struct vhost_virtqueue *vq) +{ + struct socket *sock = vq->private_data; + if (!sock) + return; + if (vq == n->vqs + VHOST_NET_VQ_TX) { + n->tx_poll_state = VHOST_NET_POLL_STOPPED; + tx_poll_start(n, sock); + } else + vhost_poll_start(n->poll + VHOST_NET_VQ_RX, sock->file); +} + +static struct socket *vhost_net_stop_vq(struct vhost_net *n, + struct vhost_virtqueue *vq) +{ + struct socket *sock; + + mutex_lock(&vq->mutex); + sock = vq->private_data; + vhost_net_disable_vq(n, vq); + rcu_assign_pointer(vq->private_data, NULL); + mutex_unlock(&vq->mutex); + return sock; +} + +static void vhost_net_stop(struct vhost_net *n, struct socket **tx_sock, + struct socket **rx_sock) +{ + *tx_sock = vhost_net_stop_vq(n, n->vqs + VHOST_NET_VQ_TX); + *rx_sock = vhost_net_stop_vq(n, n->vqs + VHOST_NET_VQ_RX); +} + +static void vhost_net_flush_vq(struct vhost_net *n, int index) +{ + vhost_poll_flush(n->poll + index); + vhost_poll_flush(&n->dev.vqs[index].poll); +} + +static void vhost_net_flush(struct vhost_net *n) +{ + vhost_net_flush_vq(n, VHOST_NET_VQ_TX); + vhost_net_flush_vq(n, VHOST_NET_VQ_RX); +} + +static int vhost_net_release(struct inode *inode, struct file *f) +{ + struct vhost_net *n = f->private_data; + struct socket *tx_sock; + struct socket *rx_sock; + + vhost_net_stop(n, &tx_sock, &rx_sock); + vhost_net_flush(n); + vhost_dev_cleanup(&n->dev); + if (tx_sock) + fput(tx_sock->file); + if (rx_sock) + fput(rx_sock->file); + /* We do an extra flush before freeing memory, + * since jobs can re-queue themselves. */ + vhost_net_flush(n); + kfree(n); + return 0; +} + +static struct socket *get_raw_socket(int fd) +{ + struct { + struct sockaddr_ll sa; + char buf[MAX_ADDR_LEN]; + } uaddr; + int uaddr_len = sizeof uaddr, r; + struct socket *sock = sockfd_lookup(fd, &r); + if (!sock) + return ERR_PTR(-ENOTSOCK); + + /* Parameter checking */ + if (sock->sk->sk_type != SOCK_RAW) { + r = -ESOCKTNOSUPPORT; + goto err; + } + + r = sock->ops->getname(sock, (struct sockaddr *)&uaddr.sa, + &uaddr_len, 0); + if (r) + goto err; + + if (uaddr.sa.sll_family != AF_PACKET) { + r = -EPFNOSUPPORT; + goto err; + } + return sock; +err: + fput(sock->file); + return ERR_PTR(r); +} + +static struct socket *get_tun_socket(int fd) +{ + struct file *file = fget(fd); + struct socket *sock; + if (!file) + return ERR_PTR(-EBADF); + sock = tun_get_socket(file); + if (IS_ERR(sock)) + fput(file); + return sock; +} + +static struct socket *get_socket(int fd) +{ + struct socket *sock; + /* special case to disable backend */ + if (fd == -1) + return NULL; + sock = get_raw_socket(fd); + if (!IS_ERR(sock)) + return sock; + sock = get_tun_socket(fd); + if (!IS_ERR(sock)) + return sock; + return ERR_PTR(-ENOTSOCK); +} + +static long vhost_net_set_backend(struct vhost_net *n, unsigned index, int fd) +{ + struct socket *sock, *oldsock; + struct vhost_virtqueue *vq; + int r; + + mutex_lock(&n->dev.mutex); + r = vhost_dev_check_owner(&n->dev); + if (r) + goto err; + + if (index >= VHOST_NET_VQ_MAX) { + r = -ENOBUFS; + goto err; + } + vq = n->vqs + index; + mutex_lock(&vq->mutex); + + /* Verify that ring has been setup correctly. */ + if (!vhost_vq_access_ok(vq)) { + r = -EFAULT; + goto err; + } + sock = get_socket(fd); + if (IS_ERR(sock)) { + r = PTR_ERR(sock); + goto err; + } + + /* start polling new socket */ + oldsock = vq->private_data; + if (sock == oldsock) + goto done; + + vhost_net_disable_vq(n, vq); + rcu_assign_pointer(vq->private_data, sock); + vhost_net_enable_vq(n, vq); + mutex_unlock(&vq->mutex); +done: + if (oldsock) { + vhost_net_flush_vq(n, index); + fput(oldsock->file); + } +err: + mutex_unlock(&n->dev.mutex); + return r; +} + +static long vhost_net_reset_owner(struct vhost_net *n) +{ + struct socket *tx_sock = NULL; + struct socket *rx_sock = NULL; + long err; + mutex_lock(&n->dev.mutex); + err = vhost_dev_check_owner(&n->dev); + if (err) + goto done; + vhost_net_stop(n, &tx_sock, &rx_sock); + vhost_net_flush(n); + err = vhost_dev_reset_owner(&n->dev); +done: + mutex_unlock(&n->dev.mutex); + if (tx_sock) + fput(tx_sock->file); + if (rx_sock) + fput(rx_sock->file); + return err; +} + +static int vhost_net_set_features(struct vhost_net *n, u64 features) +{ + size_t hdr_size = features & (1 << VHOST_NET_F_VIRTIO_NET_HDR) ? + sizeof(struct virtio_net_hdr) : 0; + int i; + mutex_lock(&n->dev.mutex); + if ((features & (1 << VHOST_F_LOG_ALL)) && + !vhost_log_access_ok(&n->dev)) { + mutex_unlock(&n->dev.mutex); + return -EFAULT; + } + n->dev.acked_features = features; + smp_wmb(); + for (i = 0; i < VHOST_NET_VQ_MAX; ++i) { + mutex_lock(&n->vqs[i].mutex); + n->vqs[i].hdr_size = hdr_size; + mutex_unlock(&n->vqs[i].mutex); + } + vhost_net_flush(n); + mutex_unlock(&n->dev.mutex); + return 0; +} + +static long vhost_net_ioctl(struct file *f, unsigned int ioctl, + unsigned long arg) +{ + struct vhost_net *n = f->private_data; + void __user *argp = (void __user *)arg; + u64 __user *featurep = argp; + struct vhost_vring_file backend; + u64 features; + int r; + switch (ioctl) { + case VHOST_NET_SET_BACKEND: + r = copy_from_user(&backend, argp, sizeof backend); + if (r < 0) + return r; + return vhost_net_set_backend(n, backend.index, backend.fd); + case VHOST_GET_FEATURES: + features = VHOST_FEATURES; + return copy_to_user(featurep, &features, sizeof features); + case VHOST_SET_FEATURES: + r = copy_from_user(&features, featurep, sizeof features); + if (r < 0) + return r; + if (features & ~VHOST_FEATURES) + return -EOPNOTSUPP; + return vhost_net_set_features(n, features); + case VHOST_RESET_OWNER: + return vhost_net_reset_owner(n); + default: + mutex_lock(&n->dev.mutex); + r = vhost_dev_ioctl(&n->dev, ioctl, arg); + vhost_net_flush(n); + mutex_unlock(&n->dev.mutex); + return r; + } +} + +#ifdef CONFIG_COMPAT +static long vhost_net_compat_ioctl(struct file *f, unsigned int ioctl, + unsigned long arg) +{ + return vhost_net_ioctl(f, ioctl, (unsigned long)compat_ptr(arg)); +} +#endif + +const static struct file_operations vhost_net_fops = { + .owner = THIS_MODULE, + .release = vhost_net_release, + .unlocked_ioctl = vhost_net_ioctl, +#ifdef CONFIG_COMPAT + .compat_ioctl = vhost_net_compat_ioctl, +#endif + .open = vhost_net_open, +}; + +static struct miscdevice vhost_net_misc = { + VHOST_NET_MINOR, + "vhost-net", + &vhost_net_fops, +}; + +int vhost_net_init(void) +{ + int r = vhost_init(); + if (r) + goto err_init; + r = misc_register(&vhost_net_misc); + if (r) + goto err_reg; + return 0; +err_reg: + vhost_cleanup(); +err_init: + return r; + +} +module_init(vhost_net_init); + +void vhost_net_exit(void) +{ + misc_deregister(&vhost_net_misc); + vhost_cleanup(); +} +module_exit(vhost_net_exit); + +MODULE_VERSION("0.0.1"); +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Michael S. Tsirkin"); +MODULE_DESCRIPTION("Host kernel accelerator for virtio net"); diff --git a/drivers/vhost/vhost.c b/drivers/vhost/vhost.c new file mode 100644 index 000000000000..c8c25dbc5857 --- /dev/null +++ b/drivers/vhost/vhost.c @@ -0,0 +1,1098 @@ +/* Copyright (C) 2009 Red Hat, Inc. + * Copyright (C) 2006 Rusty Russell IBM Corporation + * + * Author: Michael S. Tsirkin + * + * Inspiration, some code, and most witty comments come from + * Documentation/lguest/lguest.c, by Rusty Russell + * + * This work is licensed under the terms of the GNU GPL, version 2. + * + * Generic code for virtio server in host kernel. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +#include "vhost.h" + +enum { + VHOST_MEMORY_MAX_NREGIONS = 64, + VHOST_MEMORY_F_LOG = 0x1, +}; + +static struct workqueue_struct *vhost_workqueue; + +static void vhost_poll_func(struct file *file, wait_queue_head_t *wqh, + poll_table *pt) +{ + struct vhost_poll *poll; + poll = container_of(pt, struct vhost_poll, table); + + poll->wqh = wqh; + add_wait_queue(wqh, &poll->wait); +} + +static int vhost_poll_wakeup(wait_queue_t *wait, unsigned mode, int sync, + void *key) +{ + struct vhost_poll *poll; + poll = container_of(wait, struct vhost_poll, wait); + if (!((unsigned long)key & poll->mask)) + return 0; + + queue_work(vhost_workqueue, &poll->work); + return 0; +} + +/* Init poll structure */ +void vhost_poll_init(struct vhost_poll *poll, work_func_t func, + unsigned long mask) +{ + INIT_WORK(&poll->work, func); + init_waitqueue_func_entry(&poll->wait, vhost_poll_wakeup); + init_poll_funcptr(&poll->table, vhost_poll_func); + poll->mask = mask; +} + +/* Start polling a file. We add ourselves to file's wait queue. The caller must + * keep a reference to a file until after vhost_poll_stop is called. */ +void vhost_poll_start(struct vhost_poll *poll, struct file *file) +{ + unsigned long mask; + mask = file->f_op->poll(file, &poll->table); + if (mask) + vhost_poll_wakeup(&poll->wait, 0, 0, (void *)mask); +} + +/* Stop polling a file. After this function returns, it becomes safe to drop the + * file reference. You must also flush afterwards. */ +void vhost_poll_stop(struct vhost_poll *poll) +{ + remove_wait_queue(poll->wqh, &poll->wait); +} + +/* Flush any work that has been scheduled. When calling this, don't hold any + * locks that are also used by the callback. */ +void vhost_poll_flush(struct vhost_poll *poll) +{ + flush_work(&poll->work); +} + +void vhost_poll_queue(struct vhost_poll *poll) +{ + queue_work(vhost_workqueue, &poll->work); +} + +static void vhost_vq_reset(struct vhost_dev *dev, + struct vhost_virtqueue *vq) +{ + vq->num = 1; + vq->desc = NULL; + vq->avail = NULL; + vq->used = NULL; + vq->last_avail_idx = 0; + vq->avail_idx = 0; + vq->last_used_idx = 0; + vq->used_flags = 0; + vq->used_flags = 0; + vq->log_used = false; + vq->log_addr = -1ull; + vq->hdr_size = 0; + vq->private_data = NULL; + vq->log_base = NULL; + vq->error_ctx = NULL; + vq->error = NULL; + vq->kick = NULL; + vq->call_ctx = NULL; + vq->call = NULL; +} + +long vhost_dev_init(struct vhost_dev *dev, + struct vhost_virtqueue *vqs, int nvqs) +{ + int i; + dev->vqs = vqs; + dev->nvqs = nvqs; + mutex_init(&dev->mutex); + dev->log_ctx = NULL; + dev->log_file = NULL; + dev->memory = NULL; + dev->mm = NULL; + + for (i = 0; i < dev->nvqs; ++i) { + dev->vqs[i].dev = dev; + mutex_init(&dev->vqs[i].mutex); + vhost_vq_reset(dev, dev->vqs + i); + if (dev->vqs[i].handle_kick) + vhost_poll_init(&dev->vqs[i].poll, + dev->vqs[i].handle_kick, + POLLIN); + } + return 0; +} + +/* Caller should have device mutex */ +long vhost_dev_check_owner(struct vhost_dev *dev) +{ + /* Are you the owner? If not, I don't think you mean to do that */ + return dev->mm == current->mm ? 0 : -EPERM; +} + +/* Caller should have device mutex */ +static long vhost_dev_set_owner(struct vhost_dev *dev) +{ + /* Is there an owner already? */ + if (dev->mm) + return -EBUSY; + /* No owner, become one */ + dev->mm = get_task_mm(current); + return 0; +} + +/* Caller should have device mutex */ +long vhost_dev_reset_owner(struct vhost_dev *dev) +{ + struct vhost_memory *memory; + + /* Restore memory to default empty mapping. */ + memory = kmalloc(offsetof(struct vhost_memory, regions), GFP_KERNEL); + if (!memory) + return -ENOMEM; + + vhost_dev_cleanup(dev); + + memory->nregions = 0; + dev->memory = memory; + return 0; +} + +/* Caller should have device mutex */ +void vhost_dev_cleanup(struct vhost_dev *dev) +{ + int i; + for (i = 0; i < dev->nvqs; ++i) { + if (dev->vqs[i].kick && dev->vqs[i].handle_kick) { + vhost_poll_stop(&dev->vqs[i].poll); + vhost_poll_flush(&dev->vqs[i].poll); + } + if (dev->vqs[i].error_ctx) + eventfd_ctx_put(dev->vqs[i].error_ctx); + if (dev->vqs[i].error) + fput(dev->vqs[i].error); + if (dev->vqs[i].kick) + fput(dev->vqs[i].kick); + if (dev->vqs[i].call_ctx) + eventfd_ctx_put(dev->vqs[i].call_ctx); + if (dev->vqs[i].call) + fput(dev->vqs[i].call); + vhost_vq_reset(dev, dev->vqs + i); + } + if (dev->log_ctx) + eventfd_ctx_put(dev->log_ctx); + dev->log_ctx = NULL; + if (dev->log_file) + fput(dev->log_file); + dev->log_file = NULL; + /* No one will access memory at this point */ + kfree(dev->memory); + dev->memory = NULL; + if (dev->mm) + mmput(dev->mm); + dev->mm = NULL; +} + +static int log_access_ok(void __user *log_base, u64 addr, unsigned long sz) +{ + u64 a = addr / VHOST_PAGE_SIZE / 8; + /* Make sure 64 bit math will not overflow. */ + if (a > ULONG_MAX - (unsigned long)log_base || + a + (unsigned long)log_base > ULONG_MAX) + return -EFAULT; + + return access_ok(VERIFY_WRITE, log_base + a, + (sz + VHOST_PAGE_SIZE * 8 - 1) / VHOST_PAGE_SIZE / 8); +} + +/* Caller should have vq mutex and device mutex. */ +static int vq_memory_access_ok(void __user *log_base, struct vhost_memory *mem, + int log_all) +{ + int i; + for (i = 0; i < mem->nregions; ++i) { + struct vhost_memory_region *m = mem->regions + i; + unsigned long a = m->userspace_addr; + if (m->memory_size > ULONG_MAX) + return 0; + else if (!access_ok(VERIFY_WRITE, (void __user *)a, + m->memory_size)) + return 0; + else if (log_all && !log_access_ok(log_base, + m->guest_phys_addr, + m->memory_size)) + return 0; + } + return 1; +} + +/* Can we switch to this memory table? */ +/* Caller should have device mutex but not vq mutex */ +static int memory_access_ok(struct vhost_dev *d, struct vhost_memory *mem, + int log_all) +{ + int i; + for (i = 0; i < d->nvqs; ++i) { + int ok; + mutex_lock(&d->vqs[i].mutex); + /* If ring is inactive, will check when it's enabled. */ + if (d->vqs[i].private_data) + ok = vq_memory_access_ok(d->vqs[i].log_base, mem, + log_all); + else + ok = 1; + mutex_unlock(&d->vqs[i].mutex); + if (!ok) + return 0; + } + return 1; +} + +static int vq_access_ok(unsigned int num, + struct vring_desc __user *desc, + struct vring_avail __user *avail, + struct vring_used __user *used) +{ + return access_ok(VERIFY_READ, desc, num * sizeof *desc) && + access_ok(VERIFY_READ, avail, + sizeof *avail + num * sizeof *avail->ring) && + access_ok(VERIFY_WRITE, used, + sizeof *used + num * sizeof *used->ring); +} + +/* Can we log writes? */ +/* Caller should have device mutex but not vq mutex */ +int vhost_log_access_ok(struct vhost_dev *dev) +{ + return memory_access_ok(dev, dev->memory, 1); +} + +/* Verify access for write logging. */ +/* Caller should have vq mutex and device mutex */ +static int vq_log_access_ok(struct vhost_virtqueue *vq, void __user *log_base) +{ + return vq_memory_access_ok(log_base, vq->dev->memory, + vhost_has_feature(vq->dev, VHOST_F_LOG_ALL)) && + (!vq->log_used || log_access_ok(log_base, vq->log_addr, + sizeof *vq->used + + vq->num * sizeof *vq->used->ring)); +} + +/* Can we start vq? */ +/* Caller should have vq mutex and device mutex */ +int vhost_vq_access_ok(struct vhost_virtqueue *vq) +{ + return vq_access_ok(vq->num, vq->desc, vq->avail, vq->used) && + vq_log_access_ok(vq, vq->log_base); +} + +static long vhost_set_memory(struct vhost_dev *d, struct vhost_memory __user *m) +{ + struct vhost_memory mem, *newmem, *oldmem; + unsigned long size = offsetof(struct vhost_memory, regions); + long r; + r = copy_from_user(&mem, m, size); + if (r) + return r; + if (mem.padding) + return -EOPNOTSUPP; + if (mem.nregions > VHOST_MEMORY_MAX_NREGIONS) + return -E2BIG; + newmem = kmalloc(size + mem.nregions * sizeof *m->regions, GFP_KERNEL); + if (!newmem) + return -ENOMEM; + + memcpy(newmem, &mem, size); + r = copy_from_user(newmem->regions, m->regions, + mem.nregions * sizeof *m->regions); + if (r) { + kfree(newmem); + return r; + } + + if (!memory_access_ok(d, newmem, vhost_has_feature(d, VHOST_F_LOG_ALL))) + return -EFAULT; + oldmem = d->memory; + rcu_assign_pointer(d->memory, newmem); + synchronize_rcu(); + kfree(oldmem); + return 0; +} + +static int init_used(struct vhost_virtqueue *vq, + struct vring_used __user *used) +{ + int r = put_user(vq->used_flags, &used->flags); + if (r) + return r; + return get_user(vq->last_used_idx, &used->idx); +} + +static long vhost_set_vring(struct vhost_dev *d, int ioctl, void __user *argp) +{ + struct file *eventfp, *filep = NULL, + *pollstart = NULL, *pollstop = NULL; + struct eventfd_ctx *ctx = NULL; + u32 __user *idxp = argp; + struct vhost_virtqueue *vq; + struct vhost_vring_state s; + struct vhost_vring_file f; + struct vhost_vring_addr a; + u32 idx; + long r; + + r = get_user(idx, idxp); + if (r < 0) + return r; + if (idx > d->nvqs) + return -ENOBUFS; + + vq = d->vqs + idx; + + mutex_lock(&vq->mutex); + + switch (ioctl) { + case VHOST_SET_VRING_NUM: + /* Resizing ring with an active backend? + * You don't want to do that. */ + if (vq->private_data) { + r = -EBUSY; + break; + } + r = copy_from_user(&s, argp, sizeof s); + if (r < 0) + break; + if (!s.num || s.num > 0xffff || (s.num & (s.num - 1))) { + r = -EINVAL; + break; + } + vq->num = s.num; + break; + case VHOST_SET_VRING_BASE: + /* Moving base with an active backend? + * You don't want to do that. */ + if (vq->private_data) { + r = -EBUSY; + break; + } + r = copy_from_user(&s, argp, sizeof s); + if (r < 0) + break; + if (s.num > 0xffff) { + r = -EINVAL; + break; + } + vq->last_avail_idx = s.num; + /* Forget the cached index value. */ + vq->avail_idx = vq->last_avail_idx; + break; + case VHOST_GET_VRING_BASE: + s.index = idx; + s.num = vq->last_avail_idx; + r = copy_to_user(argp, &s, sizeof s); + break; + case VHOST_SET_VRING_ADDR: + r = copy_from_user(&a, argp, sizeof a); + if (r < 0) + break; + if (a.flags & ~(0x1 << VHOST_VRING_F_LOG)) { + r = -EOPNOTSUPP; + break; + } + /* For 32bit, verify that the top 32bits of the user + data are set to zero. */ + if ((u64)(unsigned long)a.desc_user_addr != a.desc_user_addr || + (u64)(unsigned long)a.used_user_addr != a.used_user_addr || + (u64)(unsigned long)a.avail_user_addr != a.avail_user_addr) { + r = -EFAULT; + break; + } + if ((a.avail_user_addr & (sizeof *vq->avail->ring - 1)) || + (a.used_user_addr & (sizeof *vq->used->ring - 1)) || + (a.log_guest_addr & (sizeof *vq->used->ring - 1))) { + r = -EINVAL; + break; + } + + /* We only verify access here if backend is configured. + * If it is not, we don't as size might not have been setup. + * We will verify when backend is configured. */ + if (vq->private_data) { + if (!vq_access_ok(vq->num, + (void __user *)(unsigned long)a.desc_user_addr, + (void __user *)(unsigned long)a.avail_user_addr, + (void __user *)(unsigned long)a.used_user_addr)) { + r = -EINVAL; + break; + } + + /* Also validate log access for used ring if enabled. */ + if ((a.flags & (0x1 << VHOST_VRING_F_LOG)) && + !log_access_ok(vq->log_base, a.log_guest_addr, + sizeof *vq->used + + vq->num * sizeof *vq->used->ring)) { + r = -EINVAL; + break; + } + } + + r = init_used(vq, (struct vring_used __user *)(unsigned long) + a.used_user_addr); + if (r) + break; + vq->log_used = !!(a.flags & (0x1 << VHOST_VRING_F_LOG)); + vq->desc = (void __user *)(unsigned long)a.desc_user_addr; + vq->avail = (void __user *)(unsigned long)a.avail_user_addr; + vq->log_addr = a.log_guest_addr; + vq->used = (void __user *)(unsigned long)a.used_user_addr; + break; + case VHOST_SET_VRING_KICK: + r = copy_from_user(&f, argp, sizeof f); + if (r < 0) + break; + eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd); + if (IS_ERR(eventfp)) + return PTR_ERR(eventfp); + if (eventfp != vq->kick) { + pollstop = filep = vq->kick; + pollstart = vq->kick = eventfp; + } else + filep = eventfp; + break; + case VHOST_SET_VRING_CALL: + r = copy_from_user(&f, argp, sizeof f); + if (r < 0) + break; + eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd); + if (IS_ERR(eventfp)) + return PTR_ERR(eventfp); + if (eventfp != vq->call) { + filep = vq->call; + ctx = vq->call_ctx; + vq->call = eventfp; + vq->call_ctx = eventfp ? + eventfd_ctx_fileget(eventfp) : NULL; + } else + filep = eventfp; + break; + case VHOST_SET_VRING_ERR: + r = copy_from_user(&f, argp, sizeof f); + if (r < 0) + break; + eventfp = f.fd == -1 ? NULL : eventfd_fget(f.fd); + if (IS_ERR(eventfp)) + return PTR_ERR(eventfp); + if (eventfp != vq->error) { + filep = vq->error; + vq->error = eventfp; + ctx = vq->error_ctx; + vq->error_ctx = eventfp ? + eventfd_ctx_fileget(eventfp) : NULL; + } else + filep = eventfp; + break; + default: + r = -ENOIOCTLCMD; + } + + if (pollstop && vq->handle_kick) + vhost_poll_stop(&vq->poll); + + if (ctx) + eventfd_ctx_put(ctx); + if (filep) + fput(filep); + + if (pollstart && vq->handle_kick) + vhost_poll_start(&vq->poll, vq->kick); + + mutex_unlock(&vq->mutex); + + if (pollstop && vq->handle_kick) + vhost_poll_flush(&vq->poll); + return r; +} + +/* Caller must have device mutex */ +long vhost_dev_ioctl(struct vhost_dev *d, unsigned int ioctl, unsigned long arg) +{ + void __user *argp = (void __user *)arg; + struct file *eventfp, *filep = NULL; + struct eventfd_ctx *ctx = NULL; + u64 p; + long r; + int i, fd; + + /* If you are not the owner, you can become one */ + if (ioctl == VHOST_SET_OWNER) { + r = vhost_dev_set_owner(d); + goto done; + } + + /* You must be the owner to do anything else */ + r = vhost_dev_check_owner(d); + if (r) + goto done; + + switch (ioctl) { + case VHOST_SET_MEM_TABLE: + r = vhost_set_memory(d, argp); + break; + case VHOST_SET_LOG_BASE: + r = copy_from_user(&p, argp, sizeof p); + if (r < 0) + break; + if ((u64)(unsigned long)p != p) { + r = -EFAULT; + break; + } + for (i = 0; i < d->nvqs; ++i) { + struct vhost_virtqueue *vq; + void __user *base = (void __user *)(unsigned long)p; + vq = d->vqs + i; + mutex_lock(&vq->mutex); + /* If ring is inactive, will check when it's enabled. */ + if (vq->private_data && !vq_log_access_ok(vq, base)) + r = -EFAULT; + else + vq->log_base = base; + mutex_unlock(&vq->mutex); + } + break; + case VHOST_SET_LOG_FD: + r = get_user(fd, (int __user *)argp); + if (r < 0) + break; + eventfp = fd == -1 ? NULL : eventfd_fget(fd); + if (IS_ERR(eventfp)) { + r = PTR_ERR(eventfp); + break; + } + if (eventfp != d->log_file) { + filep = d->log_file; + ctx = d->log_ctx; + d->log_ctx = eventfp ? + eventfd_ctx_fileget(eventfp) : NULL; + } else + filep = eventfp; + for (i = 0; i < d->nvqs; ++i) { + mutex_lock(&d->vqs[i].mutex); + d->vqs[i].log_ctx = d->log_ctx; + mutex_unlock(&d->vqs[i].mutex); + } + if (ctx) + eventfd_ctx_put(ctx); + if (filep) + fput(filep); + break; + default: + r = vhost_set_vring(d, ioctl, argp); + break; + } +done: + return r; +} + +static const struct vhost_memory_region *find_region(struct vhost_memory *mem, + __u64 addr, __u32 len) +{ + struct vhost_memory_region *reg; + int i; + /* linear search is not brilliant, but we really have on the order of 6 + * regions in practice */ + for (i = 0; i < mem->nregions; ++i) { + reg = mem->regions + i; + if (reg->guest_phys_addr <= addr && + reg->guest_phys_addr + reg->memory_size - 1 >= addr) + return reg; + } + return NULL; +} + +/* TODO: This is really inefficient. We need something like get_user() + * (instruction directly accesses the data, with an exception table entry + * returning -EFAULT). See Documentation/x86/exception-tables.txt. + */ +static int set_bit_to_user(int nr, void __user *addr) +{ + unsigned long log = (unsigned long)addr; + struct page *page; + void *base; + int bit = nr + (log % PAGE_SIZE) * 8; + int r; + r = get_user_pages_fast(log, 1, 1, &page); + if (r) + return r; + base = kmap_atomic(page, KM_USER0); + set_bit(bit, base); + kunmap_atomic(base, KM_USER0); + set_page_dirty_lock(page); + put_page(page); + return 0; +} + +static int log_write(void __user *log_base, + u64 write_address, u64 write_length) +{ + int r; + if (!write_length) + return 0; + write_address /= VHOST_PAGE_SIZE; + for (;;) { + u64 base = (u64)(unsigned long)log_base; + u64 log = base + write_address / 8; + int bit = write_address % 8; + if ((u64)(unsigned long)log != log) + return -EFAULT; + r = set_bit_to_user(bit, (void __user *)(unsigned long)log); + if (r < 0) + return r; + if (write_length <= VHOST_PAGE_SIZE) + break; + write_length -= VHOST_PAGE_SIZE; + write_address += VHOST_PAGE_SIZE; + } + return r; +} + +int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log, + unsigned int log_num, u64 len) +{ + int i, r; + + /* Make sure data written is seen before log. */ + wmb(); + for (i = 0; i < log_num; ++i) { + u64 l = min(log[i].len, len); + r = log_write(vq->log_base, log[i].addr, l); + if (r < 0) + return r; + len -= l; + if (!len) + return 0; + } + if (vq->log_ctx) + eventfd_signal(vq->log_ctx, 1); + /* Length written exceeds what we have stored. This is a bug. */ + BUG(); + return 0; +} + +int translate_desc(struct vhost_dev *dev, u64 addr, u32 len, + struct iovec iov[], int iov_size) +{ + const struct vhost_memory_region *reg; + struct vhost_memory *mem; + struct iovec *_iov; + u64 s = 0; + int ret = 0; + + rcu_read_lock(); + + mem = rcu_dereference(dev->memory); + while ((u64)len > s) { + u64 size; + if (ret >= iov_size) { + ret = -ENOBUFS; + break; + } + reg = find_region(mem, addr, len); + if (!reg) { + ret = -EFAULT; + break; + } + _iov = iov + ret; + size = reg->memory_size - addr + reg->guest_phys_addr; + _iov->iov_len = min((u64)len, size); + _iov->iov_base = (void *)(unsigned long) + (reg->userspace_addr + addr - reg->guest_phys_addr); + s += size; + addr += size; + ++ret; + } + + rcu_read_unlock(); + return ret; +} + +/* Each buffer in the virtqueues is actually a chain of descriptors. This + * function returns the next descriptor in the chain, + * or -1U if we're at the end. */ +static unsigned next_desc(struct vring_desc *desc) +{ + unsigned int next; + + /* If this descriptor says it doesn't chain, we're done. */ + if (!(desc->flags & VRING_DESC_F_NEXT)) + return -1U; + + /* Check they're not leading us off end of descriptors. */ + next = desc->next; + /* Make sure compiler knows to grab that: we don't want it changing! */ + /* We will use the result as an index in an array, so most + * architectures only need a compiler barrier here. */ + read_barrier_depends(); + + return next; +} + +static unsigned get_indirect(struct vhost_dev *dev, struct vhost_virtqueue *vq, + struct iovec iov[], unsigned int iov_size, + unsigned int *out_num, unsigned int *in_num, + struct vhost_log *log, unsigned int *log_num, + struct vring_desc *indirect) +{ + struct vring_desc desc; + unsigned int i = 0, count, found = 0; + int ret; + + /* Sanity check */ + if (indirect->len % sizeof desc) { + vq_err(vq, "Invalid length in indirect descriptor: " + "len 0x%llx not multiple of 0x%zx\n", + (unsigned long long)indirect->len, + sizeof desc); + return -EINVAL; + } + + ret = translate_desc(dev, indirect->addr, indirect->len, vq->indirect, + ARRAY_SIZE(vq->indirect)); + if (ret < 0) { + vq_err(vq, "Translation failure %d in indirect.\n", ret); + return ret; + } + + /* We will use the result as an address to read from, so most + * architectures only need a compiler barrier here. */ + read_barrier_depends(); + + count = indirect->len / sizeof desc; + /* Buffers are chained via a 16 bit next field, so + * we can have at most 2^16 of these. */ + if (count > USHORT_MAX + 1) { + vq_err(vq, "Indirect buffer length too big: %d\n", + indirect->len); + return -E2BIG; + } + + do { + unsigned iov_count = *in_num + *out_num; + if (++found > count) { + vq_err(vq, "Loop detected: last one at %u " + "indirect size %u\n", + i, count); + return -EINVAL; + } + if (memcpy_fromiovec((unsigned char *)&desc, vq->indirect, + sizeof desc)) { + vq_err(vq, "Failed indirect descriptor: idx %d, %zx\n", + i, (size_t)indirect->addr + i * sizeof desc); + return -EINVAL; + } + if (desc.flags & VRING_DESC_F_INDIRECT) { + vq_err(vq, "Nested indirect descriptor: idx %d, %zx\n", + i, (size_t)indirect->addr + i * sizeof desc); + return -EINVAL; + } + + ret = translate_desc(dev, desc.addr, desc.len, iov + iov_count, + iov_size - iov_count); + if (ret < 0) { + vq_err(vq, "Translation failure %d indirect idx %d\n", + ret, i); + return ret; + } + /* If this is an input descriptor, increment that count. */ + if (desc.flags & VRING_DESC_F_WRITE) { + *in_num += ret; + if (unlikely(log)) { + log[*log_num].addr = desc.addr; + log[*log_num].len = desc.len; + ++*log_num; + } + } else { + /* If it's an output descriptor, they're all supposed + * to come before any input descriptors. */ + if (*in_num) { + vq_err(vq, "Indirect descriptor " + "has out after in: idx %d\n", i); + return -EINVAL; + } + *out_num += ret; + } + } while ((i = next_desc(&desc)) != -1); + return 0; +} + +/* This looks in the virtqueue and for the first available buffer, and converts + * it to an iovec for convenient access. Since descriptors consist of some + * number of output then some number of input descriptors, it's actually two + * iovecs, but we pack them into one and note how many of each there were. + * + * This function returns the descriptor number found, or vq->num (which + * is never a valid descriptor number) if none was found. */ +unsigned vhost_get_vq_desc(struct vhost_dev *dev, struct vhost_virtqueue *vq, + struct iovec iov[], unsigned int iov_size, + unsigned int *out_num, unsigned int *in_num, + struct vhost_log *log, unsigned int *log_num) +{ + struct vring_desc desc; + unsigned int i, head, found = 0; + u16 last_avail_idx; + int ret; + + /* Check it isn't doing very strange things with descriptor numbers. */ + last_avail_idx = vq->last_avail_idx; + if (get_user(vq->avail_idx, &vq->avail->idx)) { + vq_err(vq, "Failed to access avail idx at %p\n", + &vq->avail->idx); + return vq->num; + } + + if ((u16)(vq->avail_idx - last_avail_idx) > vq->num) { + vq_err(vq, "Guest moved used index from %u to %u", + last_avail_idx, vq->avail_idx); + return vq->num; + } + + /* If there's nothing new since last we looked, return invalid. */ + if (vq->avail_idx == last_avail_idx) + return vq->num; + + /* Only get avail ring entries after they have been exposed by guest. */ + rmb(); + + /* Grab the next descriptor number they're advertising, and increment + * the index we've seen. */ + if (get_user(head, &vq->avail->ring[last_avail_idx % vq->num])) { + vq_err(vq, "Failed to read head: idx %d address %p\n", + last_avail_idx, + &vq->avail->ring[last_avail_idx % vq->num]); + return vq->num; + } + + /* If their number is silly, that's an error. */ + if (head >= vq->num) { + vq_err(vq, "Guest says index %u > %u is available", + head, vq->num); + return vq->num; + } + + /* When we start there are none of either input nor output. */ + *out_num = *in_num = 0; + if (unlikely(log)) + *log_num = 0; + + i = head; + do { + unsigned iov_count = *in_num + *out_num; + if (i >= vq->num) { + vq_err(vq, "Desc index is %u > %u, head = %u", + i, vq->num, head); + return vq->num; + } + if (++found > vq->num) { + vq_err(vq, "Loop detected: last one at %u " + "vq size %u head %u\n", + i, vq->num, head); + return vq->num; + } + ret = copy_from_user(&desc, vq->desc + i, sizeof desc); + if (ret) { + vq_err(vq, "Failed to get descriptor: idx %d addr %p\n", + i, vq->desc + i); + return vq->num; + } + if (desc.flags & VRING_DESC_F_INDIRECT) { + ret = get_indirect(dev, vq, iov, iov_size, + out_num, in_num, + log, log_num, &desc); + if (ret < 0) { + vq_err(vq, "Failure detected " + "in indirect descriptor at idx %d\n", i); + return vq->num; + } + continue; + } + + ret = translate_desc(dev, desc.addr, desc.len, iov + iov_count, + iov_size - iov_count); + if (ret < 0) { + vq_err(vq, "Translation failure %d descriptor idx %d\n", + ret, i); + return vq->num; + } + if (desc.flags & VRING_DESC_F_WRITE) { + /* If this is an input descriptor, + * increment that count. */ + *in_num += ret; + if (unlikely(log)) { + log[*log_num].addr = desc.addr; + log[*log_num].len = desc.len; + ++*log_num; + } + } else { + /* If it's an output descriptor, they're all supposed + * to come before any input descriptors. */ + if (*in_num) { + vq_err(vq, "Descriptor has out after in: " + "idx %d\n", i); + return vq->num; + } + *out_num += ret; + } + } while ((i = next_desc(&desc)) != -1); + + /* On success, increment avail index. */ + vq->last_avail_idx++; + return head; +} + +/* Reverse the effect of vhost_get_vq_desc. Useful for error handling. */ +void vhost_discard_vq_desc(struct vhost_virtqueue *vq) +{ + vq->last_avail_idx--; +} + +/* After we've used one of their buffers, we tell them about it. We'll then + * want to notify the guest, using eventfd. */ +int vhost_add_used(struct vhost_virtqueue *vq, unsigned int head, int len) +{ + struct vring_used_elem *used; + + /* The virtqueue contains a ring of used buffers. Get a pointer to the + * next entry in that used ring. */ + used = &vq->used->ring[vq->last_used_idx % vq->num]; + if (put_user(head, &used->id)) { + vq_err(vq, "Failed to write used id"); + return -EFAULT; + } + if (put_user(len, &used->len)) { + vq_err(vq, "Failed to write used len"); + return -EFAULT; + } + /* Make sure buffer is written before we update index. */ + wmb(); + if (put_user(vq->last_used_idx + 1, &vq->used->idx)) { + vq_err(vq, "Failed to increment used idx"); + return -EFAULT; + } + if (unlikely(vq->log_used)) { + /* Make sure data is seen before log. */ + wmb(); + log_write(vq->log_base, vq->log_addr + sizeof *vq->used->ring * + (vq->last_used_idx % vq->num), + sizeof *vq->used->ring); + log_write(vq->log_base, vq->log_addr, sizeof *vq->used->ring); + if (vq->log_ctx) + eventfd_signal(vq->log_ctx, 1); + } + vq->last_used_idx++; + return 0; +} + +/* This actually signals the guest, using eventfd. */ +void vhost_signal(struct vhost_dev *dev, struct vhost_virtqueue *vq) +{ + __u16 flags = 0; + if (get_user(flags, &vq->avail->flags)) { + vq_err(vq, "Failed to get flags"); + return; + } + + /* If they don't want an interrupt, don't signal, unless empty. */ + if ((flags & VRING_AVAIL_F_NO_INTERRUPT) && + (vq->avail_idx != vq->last_avail_idx || + !vhost_has_feature(dev, VIRTIO_F_NOTIFY_ON_EMPTY))) + return; + + /* Signal the Guest tell them we used something up. */ + if (vq->call_ctx) + eventfd_signal(vq->call_ctx, 1); +} + +/* And here's the combo meal deal. Supersize me! */ +void vhost_add_used_and_signal(struct vhost_dev *dev, + struct vhost_virtqueue *vq, + unsigned int head, int len) +{ + vhost_add_used(vq, head, len); + vhost_signal(dev, vq); +} + +/* OK, now we need to know about added descriptors. */ +bool vhost_enable_notify(struct vhost_virtqueue *vq) +{ + u16 avail_idx; + int r; + if (!(vq->used_flags & VRING_USED_F_NO_NOTIFY)) + return false; + vq->used_flags &= ~VRING_USED_F_NO_NOTIFY; + r = put_user(vq->used_flags, &vq->used->flags); + if (r) { + vq_err(vq, "Failed to enable notification at %p: %d\n", + &vq->used->flags, r); + return false; + } + /* They could have slipped one in as we were doing that: make + * sure it's written, then check again. */ + mb(); + r = get_user(avail_idx, &vq->avail->idx); + if (r) { + vq_err(vq, "Failed to check avail idx at %p: %d\n", + &vq->avail->idx, r); + return false; + } + + return avail_idx != vq->last_avail_idx; +} + +/* We don't need to be notified again. */ +void vhost_disable_notify(struct vhost_virtqueue *vq) +{ + int r; + if (vq->used_flags & VRING_USED_F_NO_NOTIFY) + return; + vq->used_flags |= VRING_USED_F_NO_NOTIFY; + r = put_user(vq->used_flags, &vq->used->flags); + if (r) + vq_err(vq, "Failed to enable notification at %p: %d\n", + &vq->used->flags, r); +} + +int vhost_init(void) +{ + vhost_workqueue = create_singlethread_workqueue("vhost"); + if (!vhost_workqueue) + return -ENOMEM; + return 0; +} + +void vhost_cleanup(void) +{ + destroy_workqueue(vhost_workqueue); +} diff --git a/drivers/vhost/vhost.h b/drivers/vhost/vhost.h new file mode 100644 index 000000000000..44591ba9b07a --- /dev/null +++ b/drivers/vhost/vhost.h @@ -0,0 +1,161 @@ +#ifndef _VHOST_H +#define _VHOST_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +struct vhost_device; + +enum { + /* Enough place for all fragments, head, and virtio net header. */ + VHOST_NET_MAX_SG = MAX_SKB_FRAGS + 2, +}; + +/* Poll a file (eventfd or socket) */ +/* Note: there's nothing vhost specific about this structure. */ +struct vhost_poll { + poll_table table; + wait_queue_head_t *wqh; + wait_queue_t wait; + /* struct which will handle all actual work. */ + struct work_struct work; + unsigned long mask; +}; + +void vhost_poll_init(struct vhost_poll *poll, work_func_t func, + unsigned long mask); +void vhost_poll_start(struct vhost_poll *poll, struct file *file); +void vhost_poll_stop(struct vhost_poll *poll); +void vhost_poll_flush(struct vhost_poll *poll); +void vhost_poll_queue(struct vhost_poll *poll); + +struct vhost_log { + u64 addr; + u64 len; +}; + +/* The virtqueue structure describes a queue attached to a device. */ +struct vhost_virtqueue { + struct vhost_dev *dev; + + /* The actual ring of buffers. */ + struct mutex mutex; + unsigned int num; + struct vring_desc __user *desc; + struct vring_avail __user *avail; + struct vring_used __user *used; + struct file *kick; + struct file *call; + struct file *error; + struct eventfd_ctx *call_ctx; + struct eventfd_ctx *error_ctx; + struct eventfd_ctx *log_ctx; + + struct vhost_poll poll; + + /* The routine to call when the Guest pings us, or timeout. */ + work_func_t handle_kick; + + /* Last available index we saw. */ + u16 last_avail_idx; + + /* Caches available index value from user. */ + u16 avail_idx; + + /* Last index we used. */ + u16 last_used_idx; + + /* Used flags */ + u16 used_flags; + + /* Log writes to used structure. */ + bool log_used; + u64 log_addr; + + struct iovec indirect[VHOST_NET_MAX_SG]; + struct iovec iov[VHOST_NET_MAX_SG]; + struct iovec hdr[VHOST_NET_MAX_SG]; + size_t hdr_size; + /* We use a kind of RCU to access private pointer. + * All readers access it from workqueue, which makes it possible to + * flush the workqueue instead of synchronize_rcu. Therefore readers do + * not need to call rcu_read_lock/rcu_read_unlock: the beginning of + * work item execution acts instead of rcu_read_lock() and the end of + * work item execution acts instead of rcu_read_lock(). + * Writers use virtqueue mutex. */ + void *private_data; + /* Log write descriptors */ + void __user *log_base; + struct vhost_log log[VHOST_NET_MAX_SG]; +}; + +struct vhost_dev { + /* Readers use RCU to access memory table pointer + * log base pointer and features. + * Writers use mutex below.*/ + struct vhost_memory *memory; + struct mm_struct *mm; + struct mutex mutex; + unsigned acked_features; + struct vhost_virtqueue *vqs; + int nvqs; + struct file *log_file; + struct eventfd_ctx *log_ctx; +}; + +long vhost_dev_init(struct vhost_dev *, struct vhost_virtqueue *vqs, int nvqs); +long vhost_dev_check_owner(struct vhost_dev *); +long vhost_dev_reset_owner(struct vhost_dev *); +void vhost_dev_cleanup(struct vhost_dev *); +long vhost_dev_ioctl(struct vhost_dev *, unsigned int ioctl, unsigned long arg); +int vhost_vq_access_ok(struct vhost_virtqueue *vq); +int vhost_log_access_ok(struct vhost_dev *); + +unsigned vhost_get_vq_desc(struct vhost_dev *, struct vhost_virtqueue *, + struct iovec iov[], unsigned int iov_count, + unsigned int *out_num, unsigned int *in_num, + struct vhost_log *log, unsigned int *log_num); +void vhost_discard_vq_desc(struct vhost_virtqueue *); + +int vhost_add_used(struct vhost_virtqueue *, unsigned int head, int len); +void vhost_signal(struct vhost_dev *, struct vhost_virtqueue *); +void vhost_add_used_and_signal(struct vhost_dev *, struct vhost_virtqueue *, + unsigned int head, int len); +void vhost_disable_notify(struct vhost_virtqueue *); +bool vhost_enable_notify(struct vhost_virtqueue *); + +int vhost_log_write(struct vhost_virtqueue *vq, struct vhost_log *log, + unsigned int log_num, u64 len); + +int vhost_init(void); +void vhost_cleanup(void); + +#define vq_err(vq, fmt, ...) do { \ + pr_debug(pr_fmt(fmt), ##__VA_ARGS__); \ + if ((vq)->error_ctx) \ + eventfd_signal((vq)->error_ctx, 1);\ + } while (0) + +enum { + VHOST_FEATURES = (1 << VIRTIO_F_NOTIFY_ON_EMPTY) | + (1 << VIRTIO_RING_F_INDIRECT_DESC) | + (1 << VHOST_F_LOG_ALL) | + (1 << VHOST_NET_F_VIRTIO_NET_HDR), +}; + +static inline int vhost_has_feature(struct vhost_dev *dev, int bit) +{ + unsigned acked_features = rcu_dereference(dev->acked_features); + return acked_features & (1 << bit); +} + +#endif diff --git a/include/linux/Kbuild b/include/linux/Kbuild index 756f831cbdd5..d93080748a91 100644 --- a/include/linux/Kbuild +++ b/include/linux/Kbuild @@ -362,6 +362,7 @@ unifdef-y += uio.h unifdef-y += unistd.h unifdef-y += usbdevice_fs.h unifdef-y += utsname.h +unifdef-y += vhost.h unifdef-y += videodev2.h unifdef-y += videodev.h unifdef-y += virtio_config.h diff --git a/include/linux/miscdevice.h b/include/linux/miscdevice.h index adaf3c15e449..8b5f7cc0fba6 100644 --- a/include/linux/miscdevice.h +++ b/include/linux/miscdevice.h @@ -30,6 +30,7 @@ #define HPET_MINOR 228 #define FUSE_MINOR 229 #define KVM_MINOR 232 +#define VHOST_NET_MINOR 233 #define MISC_DYNAMIC_MINOR 255 struct device; diff --git a/include/linux/vhost.h b/include/linux/vhost.h new file mode 100644 index 000000000000..e847f1e30756 --- /dev/null +++ b/include/linux/vhost.h @@ -0,0 +1,130 @@ +#ifndef _LINUX_VHOST_H +#define _LINUX_VHOST_H +/* Userspace interface for in-kernel virtio accelerators. */ + +/* vhost is used to reduce the number of system calls involved in virtio. + * + * Existing virtio net code is used in the guest without modification. + * + * This header includes interface used by userspace hypervisor for + * device configuration. + */ + +#include +#include +#include +#include +#include + +struct vhost_vring_state { + unsigned int index; + unsigned int num; +}; + +struct vhost_vring_file { + unsigned int index; + int fd; /* Pass -1 to unbind from file. */ + +}; + +struct vhost_vring_addr { + unsigned int index; + /* Option flags. */ + unsigned int flags; + /* Flag values: */ + /* Whether log address is valid. If set enables logging. */ +#define VHOST_VRING_F_LOG 0 + + /* Start of array of descriptors (virtually contiguous) */ + __u64 desc_user_addr; + /* Used structure address. Must be 32 bit aligned */ + __u64 used_user_addr; + /* Available structure address. Must be 16 bit aligned */ + __u64 avail_user_addr; + /* Logging support. */ + /* Log writes to used structure, at offset calculated from specified + * address. Address must be 32 bit aligned. */ + __u64 log_guest_addr; +}; + +struct vhost_memory_region { + __u64 guest_phys_addr; + __u64 memory_size; /* bytes */ + __u64 userspace_addr; + __u64 flags_padding; /* No flags are currently specified. */ +}; + +/* All region addresses and sizes must be 4K aligned. */ +#define VHOST_PAGE_SIZE 0x1000 + +struct vhost_memory { + __u32 nregions; + __u32 padding; + struct vhost_memory_region regions[0]; +}; + +/* ioctls */ + +#define VHOST_VIRTIO 0xAF + +/* Features bitmask for forward compatibility. Transport bits are used for + * vhost specific features. */ +#define VHOST_GET_FEATURES _IOR(VHOST_VIRTIO, 0x00, __u64) +#define VHOST_SET_FEATURES _IOW(VHOST_VIRTIO, 0x00, __u64) + +/* Set current process as the (exclusive) owner of this file descriptor. This + * must be called before any other vhost command. Further calls to + * VHOST_OWNER_SET fail until VHOST_OWNER_RESET is called. */ +#define VHOST_SET_OWNER _IO(VHOST_VIRTIO, 0x01) +/* Give up ownership, and reset the device to default values. + * Allows subsequent call to VHOST_OWNER_SET to succeed. */ +#define VHOST_RESET_OWNER _IO(VHOST_VIRTIO, 0x02) + +/* Set up/modify memory layout */ +#define VHOST_SET_MEM_TABLE _IOW(VHOST_VIRTIO, 0x03, struct vhost_memory) + +/* Write logging setup. */ +/* Memory writes can optionally be logged by setting bit at an offset + * (calculated from the physical address) from specified log base. + * The bit is set using an atomic 32 bit operation. */ +/* Set base address for logging. */ +#define VHOST_SET_LOG_BASE _IOW(VHOST_VIRTIO, 0x04, __u64) +/* Specify an eventfd file descriptor to signal on log write. */ +#define VHOST_SET_LOG_FD _IOW(VHOST_VIRTIO, 0x07, int) + +/* Ring setup. */ +/* Set number of descriptors in ring. This parameter can not + * be modified while ring is running (bound to a device). */ +#define VHOST_SET_VRING_NUM _IOW(VHOST_VIRTIO, 0x10, struct vhost_vring_state) +/* Set addresses for the ring. */ +#define VHOST_SET_VRING_ADDR _IOW(VHOST_VIRTIO, 0x11, struct vhost_vring_addr) +/* Base value where queue looks for available descriptors */ +#define VHOST_SET_VRING_BASE _IOW(VHOST_VIRTIO, 0x12, struct vhost_vring_state) +/* Get accessor: reads index, writes value in num */ +#define VHOST_GET_VRING_BASE _IOWR(VHOST_VIRTIO, 0x12, struct vhost_vring_state) + +/* The following ioctls use eventfd file descriptors to signal and poll + * for events. */ + +/* Set eventfd to poll for added buffers */ +#define VHOST_SET_VRING_KICK _IOW(VHOST_VIRTIO, 0x20, struct vhost_vring_file) +/* Set eventfd to signal when buffers have beed used */ +#define VHOST_SET_VRING_CALL _IOW(VHOST_VIRTIO, 0x21, struct vhost_vring_file) +/* Set eventfd to signal an error */ +#define VHOST_SET_VRING_ERR _IOW(VHOST_VIRTIO, 0x22, struct vhost_vring_file) + +/* VHOST_NET specific defines */ + +/* Attach virtio net ring to a raw socket, or tap device. + * The socket must be already bound to an ethernet device, this device will be + * used for transmit. Pass fd -1 to unbind from the socket and the transmit + * device. This can be used to stop the ring (e.g. for migration). */ +#define VHOST_NET_SET_BACKEND _IOW(VHOST_VIRTIO, 0x30, struct vhost_vring_file) + +/* Feature bits */ +/* Log all write descriptors. Can be changed while device is active. */ +#define VHOST_F_LOG_ALL 26 +/* vhost-net should add virtio_net_hdr for RX, and strip for TX packets. */ +#define VHOST_NET_F_VIRTIO_NET_HDR 27 + +#endif -- cgit v1.2.3 From 0ec00f0392b807d57a2281576a96552d7694b6bb Mon Sep 17 00:00:00 2001 From: Amit Kumar Salecha Date: Wed, 13 Jan 2010 00:37:26 +0000 Subject: NET: Add Qlogic ethernet driver for CNA devices o Separate Ethernet driver for Qlogic CNA devices Signed-off-by: Amit Kumar Salecha Signed-off-by: David S. Miller --- MAINTAINERS | 7 +++++++ drivers/net/Kconfig | 7 +++++++ drivers/net/Makefile | 1 + 3 files changed, 15 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 337dffbe9a47..61367ec865cb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4452,6 +4452,13 @@ S: Supported F: Documentation/networking/LICENSE.qla3xxx F: drivers/net/qla3xxx.* +QLOGIC QLCNIC (1/10)Gb ETHERNET DRIVER +M: Amit Kumar Salecha +M: linux-driver@qlogic.com +L: netdev@vger.kernel.org +S: Supported +F: drivers/net/qlcnic/ + QLOGIC QLGE 10Gb ETHERNET DRIVER M: Ron Mercer M: linux-driver@qlogic.com diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 5be6a2376f4f..cb0e534418e3 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2778,6 +2778,13 @@ config BNX2X To compile this driver as a module, choose M here: the module will be called bnx2x. This is recommended. +config QLCNIC + tristate "QLOGIC QLCNIC 1/10Gb Converged Ethernet NIC Support" + depends on PCI + help + This driver supports QLogic QLE8240 and QLE8242 Converged Ethernet + devices. + config QLGE tristate "QLogic QLGE 10Gb Ethernet Driver Support" depends on PCI diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 6746e8b8bdfc..0b763cbe9b1f 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -149,6 +149,7 @@ ll_temac-objs := ll_temac_main.o ll_temac_mdio.o obj-$(CONFIG_XILINX_LL_TEMAC) += ll_temac.o obj-$(CONFIG_XILINX_EMACLITE) += xilinx_emaclite.o obj-$(CONFIG_QLA3XXX) += qla3xxx.o +obj-$(CONFIG_QLCNIC) += qlcnic/ obj-$(CONFIG_QLGE) += qlge/ obj-$(CONFIG_PPP) += ppp_generic.o -- cgit v1.2.3 From cae727db30e9bcbc0256ec3282edce98b4a85433 Mon Sep 17 00:00:00 2001 From: Robert Love Date: Tue, 16 Feb 2010 12:16:00 -0800 Subject: [SCSI] MAINTAINERS: Adding FCoE information to the MAINTAINERS file. Signed-off-by: Robert Love Signed-off-by: James Bottomley --- MAINTAINERS | 11 +++++++++++ 1 file changed, 11 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 3f591629e953..358a82761a36 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2145,6 +2145,17 @@ S: Supported F: Documentation/fault-injection/ F: lib/fault-inject.c +FCOE SUBSYSTEM (libfc, libfcoe, fcoe) +M: Robert Love +L: devel@open-fcoe.org +W: www.Open-FCoE.org +S: Supported +F: drivers/scsi/libfc/ +F: drivers/scsi/fcoe/ +F: include/scsi/fc/ +F: include/scsi/libfc.h +F: include/scsi/libfcoe.h + FILE LOCKING (flock() and fcntl()/lockf()) M: Matthew Wilcox L: linux-fsdevel@vger.kernel.org -- cgit v1.2.3 From d4c41139df6e74c6fff0cbac43e51cab782133be Mon Sep 17 00:00:00 2001 From: Kristoffer Glembo Date: Mon, 15 Feb 2010 03:33:44 +0000 Subject: net: Add Aeroflex Gaisler 10/100/1G Ethernet MAC driver Adds device driver for Aeroflex Gaisler 10/100 and 10/100/1G Ethernet MAC IP cores. Signed-off-by: Kristoffer Glembo Signed-off-by: David S. Miller --- MAINTAINERS | 6 + drivers/net/Kconfig | 8 + drivers/net/Makefile | 1 + drivers/net/greth.c | 1645 ++++++++++++++++++++++++++++++++++++++++++++++++++ drivers/net/greth.h | 143 +++++ 5 files changed, 1803 insertions(+) create mode 100644 drivers/net/greth.c create mode 100644 drivers/net/greth.h (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 64a237bb01ab..32f6915ae0eb 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2373,6 +2373,12 @@ F: Documentation/isdn/README.gigaset F: drivers/isdn/gigaset/ F: include/linux/gigaset_dev.h +GRETH 10/100/1G Ethernet MAC device driver +M: Kristoffer Glembo +L: netdev@vger.kernel.org +S: Maintained +F: drivers/net/greth* + HARD DRIVE ACTIVE PROTECTION SYSTEM (HDAPS) DRIVER M: Frank Seidel L: lm-sensors@lm-sensors.org diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 069057796bd7..17ff15f6099a 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -995,6 +995,14 @@ config ETHOC help Say Y here if you want to use the OpenCores 10/100 Mbps Ethernet MAC. +config GRETH + tristate "Aeroflex Gaisler GRETH Ethernet MAC support" + depends on OF + select PHYLIB + select CRC32 + help + Say Y here if you want to use the Aeroflex Gaisler GRETH Ethernet MAC. + config SMC911X tristate "SMSC LAN911[5678] support" select CRC32 diff --git a/drivers/net/Makefile b/drivers/net/Makefile index 622cfd450d48..478886234c28 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -250,6 +250,7 @@ pasemi_mac_driver-objs := pasemi_mac.o pasemi_mac_ethtool.o obj-$(CONFIG_MLX4_CORE) += mlx4/ obj-$(CONFIG_ENC28J60) += enc28j60.o obj-$(CONFIG_ETHOC) += ethoc.o +obj-$(CONFIG_GRETH) += greth.o obj-$(CONFIG_XTENSA_XT2000_SONIC) += xtsonic.o diff --git a/drivers/net/greth.c b/drivers/net/greth.c new file mode 100644 index 000000000000..457da1c2383c --- /dev/null +++ b/drivers/net/greth.c @@ -0,0 +1,1645 @@ +/* + * Aeroflex Gaisler GRETH 10/100/1G Ethernet MAC. + * + * 2005-2009 (c) Aeroflex Gaisler AB + * + * This driver supports GRETH 10/100 and GRETH 10/100/1G Ethernet MACs + * available in the GRLIB VHDL IP core library. + * + * Full documentation of both cores can be found here: + * http://www.gaisler.com/products/grlib/grip.pdf + * + * The Gigabit version supports scatter/gather DMA, any alignment of + * buffers and checksum offloading. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * Contributors: Kristoffer Glembo + * Daniel Hellstrom + * Marko Isomaki + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef CONFIG_SPARC +#include +#endif + +#include "greth.h" + +#define GRETH_DEF_MSG_ENABLE \ + (NETIF_MSG_DRV | \ + NETIF_MSG_PROBE | \ + NETIF_MSG_LINK | \ + NETIF_MSG_IFDOWN | \ + NETIF_MSG_IFUP | \ + NETIF_MSG_RX_ERR | \ + NETIF_MSG_TX_ERR) + +static int greth_debug = -1; /* -1 == use GRETH_DEF_MSG_ENABLE as value */ +module_param(greth_debug, int, 0); +MODULE_PARM_DESC(greth_debug, "GRETH bitmapped debugging message enable value"); + +/* Accept MAC address of the form macaddr=0x08,0x00,0x20,0x30,0x40,0x50 */ +static int macaddr[6]; +module_param_array(macaddr, int, NULL, 0); +MODULE_PARM_DESC(macaddr, "GRETH Ethernet MAC address"); + +static int greth_edcl = 1; +module_param(greth_edcl, int, 0); +MODULE_PARM_DESC(greth_edcl, "GRETH EDCL usage indicator. Set to 1 if EDCL is used."); + +static int greth_open(struct net_device *dev); +static int greth_start_xmit(struct sk_buff *skb, struct net_device *dev); +static int greth_start_xmit_gbit(struct sk_buff *skb, struct net_device *dev); +static int greth_rx(struct net_device *dev, int limit); +static int greth_rx_gbit(struct net_device *dev, int limit); +static void greth_clean_tx(struct net_device *dev); +static void greth_clean_tx_gbit(struct net_device *dev); +static irqreturn_t greth_interrupt(int irq, void *dev_id); +static int greth_close(struct net_device *dev); +static int greth_set_mac_add(struct net_device *dev, void *p); +static void greth_set_multicast_list(struct net_device *dev); + +#define GRETH_REGLOAD(a) (be32_to_cpu(__raw_readl(&(a)))) +#define GRETH_REGSAVE(a, v) (__raw_writel(cpu_to_be32(v), &(a))) +#define GRETH_REGORIN(a, v) (GRETH_REGSAVE(a, (GRETH_REGLOAD(a) | (v)))) +#define GRETH_REGANDIN(a, v) (GRETH_REGSAVE(a, (GRETH_REGLOAD(a) & (v)))) + +#define NEXT_TX(N) (((N) + 1) & GRETH_TXBD_NUM_MASK) +#define SKIP_TX(N, C) (((N) + C) & GRETH_TXBD_NUM_MASK) +#define NEXT_RX(N) (((N) + 1) & GRETH_RXBD_NUM_MASK) + +static void greth_print_rx_packet(void *addr, int len) +{ + print_hex_dump(KERN_DEBUG, "RX: ", DUMP_PREFIX_OFFSET, 16, 1, + addr, len, true); +} + +static void greth_print_tx_packet(struct sk_buff *skb) +{ + int i; + int length; + + if (skb_shinfo(skb)->nr_frags == 0) + length = skb->len; + else + length = skb_headlen(skb); + + print_hex_dump(KERN_DEBUG, "TX: ", DUMP_PREFIX_OFFSET, 16, 1, + skb->data, length, true); + + for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { + + print_hex_dump(KERN_DEBUG, "TX: ", DUMP_PREFIX_OFFSET, 16, 1, + phys_to_virt(page_to_phys(skb_shinfo(skb)->frags[i].page)) + + skb_shinfo(skb)->frags[i].page_offset, + length, true); + } +} + +static inline void greth_enable_tx(struct greth_private *greth) +{ + wmb(); + GRETH_REGORIN(greth->regs->control, GRETH_TXEN); +} + +static inline void greth_disable_tx(struct greth_private *greth) +{ + GRETH_REGANDIN(greth->regs->control, ~GRETH_TXEN); +} + +static inline void greth_enable_rx(struct greth_private *greth) +{ + wmb(); + GRETH_REGORIN(greth->regs->control, GRETH_RXEN); +} + +static inline void greth_disable_rx(struct greth_private *greth) +{ + GRETH_REGANDIN(greth->regs->control, ~GRETH_RXEN); +} + +static inline void greth_enable_irqs(struct greth_private *greth) +{ + GRETH_REGORIN(greth->regs->control, GRETH_RXI | GRETH_TXI); +} + +static inline void greth_disable_irqs(struct greth_private *greth) +{ + GRETH_REGANDIN(greth->regs->control, ~(GRETH_RXI|GRETH_TXI)); +} + +static inline void greth_write_bd(u32 *bd, u32 val) +{ + __raw_writel(cpu_to_be32(val), bd); +} + +static inline u32 greth_read_bd(u32 *bd) +{ + return be32_to_cpu(__raw_readl(bd)); +} + +static void greth_clean_rings(struct greth_private *greth) +{ + int i; + struct greth_bd *rx_bdp = greth->rx_bd_base; + struct greth_bd *tx_bdp = greth->tx_bd_base; + + if (greth->gbit_mac) { + + /* Free and unmap RX buffers */ + for (i = 0; i < GRETH_RXBD_NUM; i++, rx_bdp++) { + if (greth->rx_skbuff[i] != NULL) { + dev_kfree_skb(greth->rx_skbuff[i]); + dma_unmap_single(greth->dev, + greth_read_bd(&rx_bdp->addr), + MAX_FRAME_SIZE+NET_IP_ALIGN, + DMA_FROM_DEVICE); + } + } + + /* TX buffers */ + while (greth->tx_free < GRETH_TXBD_NUM) { + + struct sk_buff *skb = greth->tx_skbuff[greth->tx_last]; + int nr_frags = skb_shinfo(skb)->nr_frags; + tx_bdp = greth->tx_bd_base + greth->tx_last; + greth->tx_last = NEXT_TX(greth->tx_last); + + dma_unmap_single(greth->dev, + greth_read_bd(&tx_bdp->addr), + skb_headlen(skb), + DMA_TO_DEVICE); + + for (i = 0; i < nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + tx_bdp = greth->tx_bd_base + greth->tx_last; + + dma_unmap_page(greth->dev, + greth_read_bd(&tx_bdp->addr), + frag->size, + DMA_TO_DEVICE); + + greth->tx_last = NEXT_TX(greth->tx_last); + } + greth->tx_free += nr_frags+1; + dev_kfree_skb(skb); + } + + + } else { /* 10/100 Mbps MAC */ + + for (i = 0; i < GRETH_RXBD_NUM; i++, rx_bdp++) { + kfree(greth->rx_bufs[i]); + dma_unmap_single(greth->dev, + greth_read_bd(&rx_bdp->addr), + MAX_FRAME_SIZE, + DMA_FROM_DEVICE); + } + for (i = 0; i < GRETH_TXBD_NUM; i++, tx_bdp++) { + kfree(greth->tx_bufs[i]); + dma_unmap_single(greth->dev, + greth_read_bd(&tx_bdp->addr), + MAX_FRAME_SIZE, + DMA_TO_DEVICE); + } + } +} + +static int greth_init_rings(struct greth_private *greth) +{ + struct sk_buff *skb; + struct greth_bd *rx_bd, *tx_bd; + u32 dma_addr; + int i; + + rx_bd = greth->rx_bd_base; + tx_bd = greth->tx_bd_base; + + /* Initialize descriptor rings and buffers */ + if (greth->gbit_mac) { + + for (i = 0; i < GRETH_RXBD_NUM; i++) { + skb = netdev_alloc_skb(greth->netdev, MAX_FRAME_SIZE+NET_IP_ALIGN); + if (skb == NULL) { + if (netif_msg_ifup(greth)) + dev_err(greth->dev, "Error allocating DMA ring.\n"); + goto cleanup; + } + skb_reserve(skb, NET_IP_ALIGN); + dma_addr = dma_map_single(greth->dev, + skb->data, + MAX_FRAME_SIZE+NET_IP_ALIGN, + DMA_FROM_DEVICE); + + if (dma_mapping_error(greth->dev, dma_addr)) { + if (netif_msg_ifup(greth)) + dev_err(greth->dev, "Could not create initial DMA mapping\n"); + goto cleanup; + } + greth->rx_skbuff[i] = skb; + greth_write_bd(&rx_bd[i].addr, dma_addr); + greth_write_bd(&rx_bd[i].stat, GRETH_BD_EN | GRETH_BD_IE); + } + + } else { + + /* 10/100 MAC uses a fixed set of buffers and copy to/from SKBs */ + for (i = 0; i < GRETH_RXBD_NUM; i++) { + + greth->rx_bufs[i] = kmalloc(MAX_FRAME_SIZE, GFP_KERNEL); + + if (greth->rx_bufs[i] == NULL) { + if (netif_msg_ifup(greth)) + dev_err(greth->dev, "Error allocating DMA ring.\n"); + goto cleanup; + } + + dma_addr = dma_map_single(greth->dev, + greth->rx_bufs[i], + MAX_FRAME_SIZE, + DMA_FROM_DEVICE); + + if (dma_mapping_error(greth->dev, dma_addr)) { + if (netif_msg_ifup(greth)) + dev_err(greth->dev, "Could not create initial DMA mapping\n"); + goto cleanup; + } + greth_write_bd(&rx_bd[i].addr, dma_addr); + greth_write_bd(&rx_bd[i].stat, GRETH_BD_EN | GRETH_BD_IE); + } + for (i = 0; i < GRETH_TXBD_NUM; i++) { + + greth->tx_bufs[i] = kmalloc(MAX_FRAME_SIZE, GFP_KERNEL); + + if (greth->tx_bufs[i] == NULL) { + if (netif_msg_ifup(greth)) + dev_err(greth->dev, "Error allocating DMA ring.\n"); + goto cleanup; + } + + dma_addr = dma_map_single(greth->dev, + greth->tx_bufs[i], + MAX_FRAME_SIZE, + DMA_TO_DEVICE); + + if (dma_mapping_error(greth->dev, dma_addr)) { + if (netif_msg_ifup(greth)) + dev_err(greth->dev, "Could not create initial DMA mapping\n"); + goto cleanup; + } + greth_write_bd(&tx_bd[i].addr, dma_addr); + greth_write_bd(&tx_bd[i].stat, 0); + } + } + greth_write_bd(&rx_bd[GRETH_RXBD_NUM - 1].stat, + greth_read_bd(&rx_bd[GRETH_RXBD_NUM - 1].stat) | GRETH_BD_WR); + + /* Initialize pointers. */ + greth->rx_cur = 0; + greth->tx_next = 0; + greth->tx_last = 0; + greth->tx_free = GRETH_TXBD_NUM; + + /* Initialize descriptor base address */ + GRETH_REGSAVE(greth->regs->tx_desc_p, greth->tx_bd_base_phys); + GRETH_REGSAVE(greth->regs->rx_desc_p, greth->rx_bd_base_phys); + + return 0; + +cleanup: + greth_clean_rings(greth); + return -ENOMEM; +} + +static int greth_open(struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + int err; + + err = greth_init_rings(greth); + if (err) { + if (netif_msg_ifup(greth)) + dev_err(&dev->dev, "Could not allocate memory for DMA rings\n"); + return err; + } + + err = request_irq(greth->irq, greth_interrupt, 0, "eth", (void *) dev); + if (err) { + if (netif_msg_ifup(greth)) + dev_err(&dev->dev, "Could not allocate interrupt %d\n", dev->irq); + greth_clean_rings(greth); + return err; + } + + if (netif_msg_ifup(greth)) + dev_dbg(&dev->dev, " starting queue\n"); + netif_start_queue(dev); + + napi_enable(&greth->napi); + + greth_enable_irqs(greth); + greth_enable_tx(greth); + greth_enable_rx(greth); + return 0; + +} + +static int greth_close(struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + + napi_disable(&greth->napi); + + greth_disable_tx(greth); + + netif_stop_queue(dev); + + free_irq(greth->irq, (void *) dev); + + greth_clean_rings(greth); + + return 0; +} + +static int greth_start_xmit(struct sk_buff *skb, struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + struct greth_bd *bdp; + int err = NETDEV_TX_OK; + u32 status, dma_addr; + + bdp = greth->tx_bd_base + greth->tx_next; + + if (unlikely(greth->tx_free <= 0)) { + netif_stop_queue(dev); + return NETDEV_TX_BUSY; + } + + if (netif_msg_pktdata(greth)) + greth_print_tx_packet(skb); + + + if (unlikely(skb->len > MAX_FRAME_SIZE)) { + dev->stats.tx_errors++; + goto out; + } + + dma_addr = greth_read_bd(&bdp->addr); + + memcpy((unsigned char *) phys_to_virt(dma_addr), skb->data, skb->len); + + dma_sync_single_for_device(greth->dev, dma_addr, skb->len, DMA_TO_DEVICE); + + status = GRETH_BD_EN | (skb->len & GRETH_BD_LEN); + + /* Wrap around descriptor ring */ + if (greth->tx_next == GRETH_TXBD_NUM_MASK) { + status |= GRETH_BD_WR; + } + + greth->tx_next = NEXT_TX(greth->tx_next); + greth->tx_free--; + + /* No more descriptors */ + if (unlikely(greth->tx_free == 0)) { + + /* Free transmitted descriptors */ + greth_clean_tx(dev); + + /* If nothing was cleaned, stop queue & wait for irq */ + if (unlikely(greth->tx_free == 0)) { + status |= GRETH_BD_IE; + netif_stop_queue(dev); + } + } + + /* Write descriptor control word and enable transmission */ + greth_write_bd(&bdp->stat, status); + greth_enable_tx(greth); + +out: + dev_kfree_skb(skb); + return err; +} + + +static int greth_start_xmit_gbit(struct sk_buff *skb, struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + struct greth_bd *bdp; + u32 status = 0, dma_addr; + int curr_tx, nr_frags, i, err = NETDEV_TX_OK; + + nr_frags = skb_shinfo(skb)->nr_frags; + + if (greth->tx_free < nr_frags + 1) { + netif_stop_queue(dev); + err = NETDEV_TX_BUSY; + goto out; + } + + if (netif_msg_pktdata(greth)) + greth_print_tx_packet(skb); + + if (unlikely(skb->len > MAX_FRAME_SIZE)) { + dev->stats.tx_errors++; + goto out; + } + + /* Save skb pointer. */ + greth->tx_skbuff[greth->tx_next] = skb; + + /* Linear buf */ + if (nr_frags != 0) + status = GRETH_TXBD_MORE; + + status |= GRETH_TXBD_CSALL; + status |= skb_headlen(skb) & GRETH_BD_LEN; + if (greth->tx_next == GRETH_TXBD_NUM_MASK) + status |= GRETH_BD_WR; + + + bdp = greth->tx_bd_base + greth->tx_next; + greth_write_bd(&bdp->stat, status); + dma_addr = dma_map_single(greth->dev, skb->data, skb_headlen(skb), DMA_TO_DEVICE); + + if (unlikely(dma_mapping_error(greth->dev, dma_addr))) + goto map_error; + + greth_write_bd(&bdp->addr, dma_addr); + + curr_tx = NEXT_TX(greth->tx_next); + + /* Frags */ + for (i = 0; i < nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + greth->tx_skbuff[curr_tx] = NULL; + bdp = greth->tx_bd_base + curr_tx; + + status = GRETH_TXBD_CSALL; + status |= frag->size & GRETH_BD_LEN; + + /* Wrap around descriptor ring */ + if (curr_tx == GRETH_TXBD_NUM_MASK) + status |= GRETH_BD_WR; + + /* More fragments left */ + if (i < nr_frags - 1) + status |= GRETH_TXBD_MORE; + + /* ... last fragment, check if out of descriptors */ + else if (greth->tx_free - nr_frags - 1 < (MAX_SKB_FRAGS + 1)) { + + /* Enable interrupts and stop queue */ + status |= GRETH_BD_IE; + netif_stop_queue(dev); + } + + greth_write_bd(&bdp->stat, status); + + dma_addr = dma_map_page(greth->dev, + frag->page, + frag->page_offset, + frag->size, + DMA_TO_DEVICE); + + if (unlikely(dma_mapping_error(greth->dev, dma_addr))) + goto frag_map_error; + + greth_write_bd(&bdp->addr, dma_addr); + + curr_tx = NEXT_TX(curr_tx); + } + + wmb(); + + /* Enable the descriptors that we configured ... */ + for (i = 0; i < nr_frags + 1; i++) { + bdp = greth->tx_bd_base + greth->tx_next; + greth_write_bd(&bdp->stat, greth_read_bd(&bdp->stat) | GRETH_BD_EN); + greth->tx_next = NEXT_TX(greth->tx_next); + greth->tx_free--; + } + + greth_enable_tx(greth); + + return NETDEV_TX_OK; + +frag_map_error: + /* Unmap SKB mappings that succeeded */ + for (i = 0; greth->tx_next + i != curr_tx; i++) { + bdp = greth->tx_bd_base + greth->tx_next + i; + dma_unmap_single(greth->dev, + greth_read_bd(&bdp->addr), + greth_read_bd(&bdp->stat) & GRETH_BD_LEN, + DMA_TO_DEVICE); + } +map_error: + if (net_ratelimit()) + dev_warn(greth->dev, "Could not create TX DMA mapping\n"); + dev_kfree_skb(skb); + return NETDEV_TX_OK; + +out: + return err; +} + + +static irqreturn_t greth_interrupt(int irq, void *dev_id) +{ + struct net_device *dev = dev_id; + struct greth_private *greth; + u32 status; + irqreturn_t retval = IRQ_NONE; + + greth = netdev_priv(dev); + + spin_lock(&greth->devlock); + + /* Get the interrupt events that caused us to be here. */ + status = GRETH_REGLOAD(greth->regs->status); + + /* Handle rx and tx interrupts through poll */ + if (status & (GRETH_INT_RX | GRETH_INT_TX)) { + + /* Clear interrupt status */ + GRETH_REGORIN(greth->regs->status, + status & (GRETH_INT_RX | GRETH_INT_TX)); + + retval = IRQ_HANDLED; + + /* Disable interrupts and schedule poll() */ + greth_disable_irqs(greth); + napi_schedule(&greth->napi); + } + + mmiowb(); + spin_unlock(&greth->devlock); + + return retval; +} + +static void greth_clean_tx(struct net_device *dev) +{ + struct greth_private *greth; + struct greth_bd *bdp; + u32 stat; + + greth = netdev_priv(dev); + + while (1) { + bdp = greth->tx_bd_base + greth->tx_last; + stat = greth_read_bd(&bdp->stat); + + if (unlikely(stat & GRETH_BD_EN)) + break; + + if (greth->tx_free == GRETH_TXBD_NUM) + break; + + /* Check status for errors */ + if (unlikely(stat & GRETH_TXBD_STATUS)) { + dev->stats.tx_errors++; + if (stat & GRETH_TXBD_ERR_AL) + dev->stats.tx_aborted_errors++; + if (stat & GRETH_TXBD_ERR_UE) + dev->stats.tx_fifo_errors++; + } + dev->stats.tx_packets++; + greth->tx_last = NEXT_TX(greth->tx_last); + greth->tx_free++; + } + + if (greth->tx_free > 0) { + netif_wake_queue(dev); + } + +} + +static inline void greth_update_tx_stats(struct net_device *dev, u32 stat) +{ + /* Check status for errors */ + if (unlikely(stat & GRETH_TXBD_STATUS)) { + dev->stats.tx_errors++; + if (stat & GRETH_TXBD_ERR_AL) + dev->stats.tx_aborted_errors++; + if (stat & GRETH_TXBD_ERR_UE) + dev->stats.tx_fifo_errors++; + if (stat & GRETH_TXBD_ERR_LC) + dev->stats.tx_aborted_errors++; + } + dev->stats.tx_packets++; +} + +static void greth_clean_tx_gbit(struct net_device *dev) +{ + struct greth_private *greth; + struct greth_bd *bdp, *bdp_last_frag; + struct sk_buff *skb; + u32 stat; + int nr_frags, i; + + greth = netdev_priv(dev); + + while (greth->tx_free < GRETH_TXBD_NUM) { + + skb = greth->tx_skbuff[greth->tx_last]; + + nr_frags = skb_shinfo(skb)->nr_frags; + + /* We only clean fully completed SKBs */ + bdp_last_frag = greth->tx_bd_base + SKIP_TX(greth->tx_last, nr_frags); + stat = bdp_last_frag->stat; + + if (stat & GRETH_BD_EN) + break; + + greth->tx_skbuff[greth->tx_last] = NULL; + + greth_update_tx_stats(dev, stat); + + bdp = greth->tx_bd_base + greth->tx_last; + + greth->tx_last = NEXT_TX(greth->tx_last); + + dma_unmap_single(greth->dev, + greth_read_bd(&bdp->addr), + skb_headlen(skb), + DMA_TO_DEVICE); + + for (i = 0; i < nr_frags; i++) { + skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; + bdp = greth->tx_bd_base + greth->tx_last; + + dma_unmap_page(greth->dev, + greth_read_bd(&bdp->addr), + frag->size, + DMA_TO_DEVICE); + + greth->tx_last = NEXT_TX(greth->tx_last); + } + greth->tx_free += nr_frags+1; + dev_kfree_skb(skb); + } + if (greth->tx_free > (MAX_SKB_FRAGS + 1)) { + netif_wake_queue(dev); + } +} + +static int greth_pending_packets(struct greth_private *greth) +{ + struct greth_bd *bdp; + u32 status; + bdp = greth->rx_bd_base + greth->rx_cur; + status = greth_read_bd(&bdp->stat); + if (status & GRETH_BD_EN) + return 0; + else + return 1; +} + +static int greth_rx(struct net_device *dev, int limit) +{ + struct greth_private *greth; + struct greth_bd *bdp; + struct sk_buff *skb; + int pkt_len; + int bad, count; + u32 status, dma_addr; + + greth = netdev_priv(dev); + + for (count = 0; count < limit; ++count) { + + bdp = greth->rx_bd_base + greth->rx_cur; + status = greth_read_bd(&bdp->stat); + dma_addr = greth_read_bd(&bdp->addr); + bad = 0; + + if (unlikely(status & GRETH_BD_EN)) { + break; + } + + /* Check status for errors. */ + if (unlikely(status & GRETH_RXBD_STATUS)) { + if (status & GRETH_RXBD_ERR_FT) { + dev->stats.rx_length_errors++; + bad = 1; + } + if (status & (GRETH_RXBD_ERR_AE | GRETH_RXBD_ERR_OE)) { + dev->stats.rx_frame_errors++; + bad = 1; + } + if (status & GRETH_RXBD_ERR_CRC) { + dev->stats.rx_crc_errors++; + bad = 1; + } + } + if (unlikely(bad)) { + dev->stats.rx_errors++; + + } else { + + pkt_len = status & GRETH_BD_LEN; + + skb = netdev_alloc_skb(dev, pkt_len + NET_IP_ALIGN); + + if (unlikely(skb == NULL)) { + + if (net_ratelimit()) + dev_warn(&dev->dev, "low on memory - " "packet dropped\n"); + + dev->stats.rx_dropped++; + + } else { + skb_reserve(skb, NET_IP_ALIGN); + skb->dev = dev; + + dma_sync_single_for_cpu(greth->dev, + dma_addr, + pkt_len, + DMA_FROM_DEVICE); + + if (netif_msg_pktdata(greth)) + greth_print_rx_packet(phys_to_virt(dma_addr), pkt_len); + + memcpy(skb_put(skb, pkt_len), phys_to_virt(dma_addr), pkt_len); + + skb->protocol = eth_type_trans(skb, dev); + dev->stats.rx_packets++; + netif_receive_skb(skb); + } + } + + status = GRETH_BD_EN | GRETH_BD_IE; + if (greth->rx_cur == GRETH_RXBD_NUM_MASK) { + status |= GRETH_BD_WR; + } + + wmb(); + greth_write_bd(&bdp->stat, status); + + dma_sync_single_for_device(greth->dev, dma_addr, MAX_FRAME_SIZE, DMA_FROM_DEVICE); + + greth_enable_rx(greth); + + greth->rx_cur = NEXT_RX(greth->rx_cur); + } + + return count; +} + +static inline int hw_checksummed(u32 status) +{ + + if (status & GRETH_RXBD_IP_FRAG) + return 0; + + if (status & GRETH_RXBD_IP && status & GRETH_RXBD_IP_CSERR) + return 0; + + if (status & GRETH_RXBD_UDP && status & GRETH_RXBD_UDP_CSERR) + return 0; + + if (status & GRETH_RXBD_TCP && status & GRETH_RXBD_TCP_CSERR) + return 0; + + return 1; +} + +static int greth_rx_gbit(struct net_device *dev, int limit) +{ + struct greth_private *greth; + struct greth_bd *bdp; + struct sk_buff *skb, *newskb; + int pkt_len; + int bad, count = 0; + u32 status, dma_addr; + + greth = netdev_priv(dev); + + for (count = 0; count < limit; ++count) { + + bdp = greth->rx_bd_base + greth->rx_cur; + skb = greth->rx_skbuff[greth->rx_cur]; + status = greth_read_bd(&bdp->stat); + bad = 0; + + if (status & GRETH_BD_EN) + break; + + /* Check status for errors. */ + if (unlikely(status & GRETH_RXBD_STATUS)) { + + if (status & GRETH_RXBD_ERR_FT) { + dev->stats.rx_length_errors++; + bad = 1; + } else if (status & + (GRETH_RXBD_ERR_AE | GRETH_RXBD_ERR_OE | GRETH_RXBD_ERR_LE)) { + dev->stats.rx_frame_errors++; + bad = 1; + } else if (status & GRETH_RXBD_ERR_CRC) { + dev->stats.rx_crc_errors++; + bad = 1; + } + } + + /* Allocate new skb to replace current */ + newskb = netdev_alloc_skb(dev, MAX_FRAME_SIZE + NET_IP_ALIGN); + + if (!bad && newskb) { + skb_reserve(newskb, NET_IP_ALIGN); + + dma_addr = dma_map_single(greth->dev, + newskb->data, + MAX_FRAME_SIZE + NET_IP_ALIGN, + DMA_FROM_DEVICE); + + if (!dma_mapping_error(greth->dev, dma_addr)) { + /* Process the incoming frame. */ + pkt_len = status & GRETH_BD_LEN; + + dma_unmap_single(greth->dev, + greth_read_bd(&bdp->addr), + MAX_FRAME_SIZE + NET_IP_ALIGN, + DMA_FROM_DEVICE); + + if (netif_msg_pktdata(greth)) + greth_print_rx_packet(phys_to_virt(greth_read_bd(&bdp->addr)), pkt_len); + + skb_put(skb, pkt_len); + + if (greth->flags & GRETH_FLAG_RX_CSUM && hw_checksummed(status)) + skb->ip_summed = CHECKSUM_UNNECESSARY; + else + skb->ip_summed = CHECKSUM_NONE; + + skb->dev = dev; + skb->protocol = eth_type_trans(skb, dev); + dev->stats.rx_packets++; + netif_receive_skb(skb); + + greth->rx_skbuff[greth->rx_cur] = newskb; + greth_write_bd(&bdp->addr, dma_addr); + } else { + if (net_ratelimit()) + dev_warn(greth->dev, "Could not create DMA mapping, dropping packet\n"); + dev_kfree_skb(newskb); + dev->stats.rx_dropped++; + } + } else { + if (net_ratelimit()) + dev_warn(greth->dev, "Could not allocate SKB, dropping packet\n"); + dev->stats.rx_dropped++; + } + + status = GRETH_BD_EN | GRETH_BD_IE; + if (greth->rx_cur == GRETH_RXBD_NUM_MASK) { + status |= GRETH_BD_WR; + } + + wmb(); + greth_write_bd(&bdp->stat, status); + greth_enable_rx(greth); + greth->rx_cur = NEXT_RX(greth->rx_cur); + } + + return count; + +} + +static int greth_poll(struct napi_struct *napi, int budget) +{ + struct greth_private *greth; + int work_done = 0; + greth = container_of(napi, struct greth_private, napi); + + if (greth->gbit_mac) { + greth_clean_tx_gbit(greth->netdev); + } else { + greth_clean_tx(greth->netdev); + } + +restart_poll: + if (greth->gbit_mac) { + work_done += greth_rx_gbit(greth->netdev, budget - work_done); + } else { + work_done += greth_rx(greth->netdev, budget - work_done); + } + + if (work_done < budget) { + + napi_complete(napi); + + if (greth_pending_packets(greth)) { + napi_reschedule(napi); + goto restart_poll; + } + } + + greth_enable_irqs(greth); + return work_done; +} + +static int greth_set_mac_add(struct net_device *dev, void *p) +{ + struct sockaddr *addr = p; + struct greth_private *greth; + struct greth_regs *regs; + + greth = (struct greth_private *) netdev_priv(dev); + regs = (struct greth_regs *) greth->regs; + + if (!is_valid_ether_addr(addr->sa_data)) + return -EINVAL; + + memcpy(dev->dev_addr, addr->sa_data, dev->addr_len); + + GRETH_REGSAVE(regs->esa_msb, addr->sa_data[0] << 8 | addr->sa_data[1]); + GRETH_REGSAVE(regs->esa_lsb, + addr->sa_data[2] << 24 | addr-> + sa_data[3] << 16 | addr->sa_data[4] << 8 | addr->sa_data[5]); + return 0; +} + +static u32 greth_hash_get_index(__u8 *addr) +{ + return (ether_crc(6, addr)) & 0x3F; +} + +static void greth_set_hash_filter(struct net_device *dev) +{ + struct dev_mc_list *curr; + struct greth_private *greth = (struct greth_private *) netdev_priv(dev); + struct greth_regs *regs = (struct greth_regs *) greth->regs; + u32 mc_filter[2]; + unsigned int i, bitnr; + + mc_filter[0] = mc_filter[1] = 0; + + curr = dev->mc_list; + + for (i = 0; i < dev->mc_count; i++, curr = curr->next) { + + if (!curr) + break; /* unexpected end of list */ + + bitnr = greth_hash_get_index(curr->dmi_addr); + mc_filter[bitnr >> 5] |= 1 << (bitnr & 31); + } + + GRETH_REGSAVE(regs->hash_msb, mc_filter[1]); + GRETH_REGSAVE(regs->hash_lsb, mc_filter[0]); +} + +static void greth_set_multicast_list(struct net_device *dev) +{ + int cfg; + struct greth_private *greth = netdev_priv(dev); + struct greth_regs *regs = (struct greth_regs *) greth->regs; + + cfg = GRETH_REGLOAD(regs->control); + if (dev->flags & IFF_PROMISC) + cfg |= GRETH_CTRL_PR; + else + cfg &= ~GRETH_CTRL_PR; + + if (greth->multicast) { + if (dev->flags & IFF_ALLMULTI) { + GRETH_REGSAVE(regs->hash_msb, -1); + GRETH_REGSAVE(regs->hash_lsb, -1); + cfg |= GRETH_CTRL_MCEN; + GRETH_REGSAVE(regs->control, cfg); + return; + } + + if (dev->mc_count == 0) { + cfg &= ~GRETH_CTRL_MCEN; + GRETH_REGSAVE(regs->control, cfg); + return; + } + + /* Setup multicast filter */ + greth_set_hash_filter(dev); + cfg |= GRETH_CTRL_MCEN; + } + GRETH_REGSAVE(regs->control, cfg); +} + +static u32 greth_get_msglevel(struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + return greth->msg_enable; +} + +static void greth_set_msglevel(struct net_device *dev, u32 value) +{ + struct greth_private *greth = netdev_priv(dev); + greth->msg_enable = value; +} +static int greth_get_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct greth_private *greth = netdev_priv(dev); + struct phy_device *phy = greth->phy; + + if (!phy) + return -ENODEV; + + return phy_ethtool_gset(phy, cmd); +} + +static int greth_set_settings(struct net_device *dev, struct ethtool_cmd *cmd) +{ + struct greth_private *greth = netdev_priv(dev); + struct phy_device *phy = greth->phy; + + if (!phy) + return -ENODEV; + + return phy_ethtool_sset(phy, cmd); +} + +static int greth_get_regs_len(struct net_device *dev) +{ + return sizeof(struct greth_regs); +} + +static void greth_get_drvinfo(struct net_device *dev, struct ethtool_drvinfo *info) +{ + struct greth_private *greth = netdev_priv(dev); + + strncpy(info->driver, dev_driver_string(greth->dev), 32); + strncpy(info->version, "revision: 1.0", 32); + strncpy(info->bus_info, greth->dev->bus->name, 32); + strncpy(info->fw_version, "N/A", 32); + info->eedump_len = 0; + info->regdump_len = sizeof(struct greth_regs); +} + +static void greth_get_regs(struct net_device *dev, struct ethtool_regs *regs, void *p) +{ + int i; + struct greth_private *greth = netdev_priv(dev); + u32 __iomem *greth_regs = (u32 __iomem *) greth->regs; + u32 *buff = p; + + for (i = 0; i < sizeof(struct greth_regs) / sizeof(u32); i++) + buff[i] = greth_read_bd(&greth_regs[i]); +} + +static u32 greth_get_rx_csum(struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + return (greth->flags & GRETH_FLAG_RX_CSUM) != 0; +} + +static int greth_set_rx_csum(struct net_device *dev, u32 data) +{ + struct greth_private *greth = netdev_priv(dev); + + spin_lock_bh(&greth->devlock); + + if (data) + greth->flags |= GRETH_FLAG_RX_CSUM; + else + greth->flags &= ~GRETH_FLAG_RX_CSUM; + + spin_unlock_bh(&greth->devlock); + + return 0; +} + +static u32 greth_get_tx_csum(struct net_device *dev) +{ + return (dev->features & NETIF_F_IP_CSUM) != 0; +} + +static int greth_set_tx_csum(struct net_device *dev, u32 data) +{ + netif_tx_lock_bh(dev); + ethtool_op_set_tx_csum(dev, data); + netif_tx_unlock_bh(dev); + return 0; +} + +static const struct ethtool_ops greth_ethtool_ops = { + .get_msglevel = greth_get_msglevel, + .set_msglevel = greth_set_msglevel, + .get_settings = greth_get_settings, + .set_settings = greth_set_settings, + .get_drvinfo = greth_get_drvinfo, + .get_regs_len = greth_get_regs_len, + .get_regs = greth_get_regs, + .get_rx_csum = greth_get_rx_csum, + .set_rx_csum = greth_set_rx_csum, + .get_tx_csum = greth_get_tx_csum, + .set_tx_csum = greth_set_tx_csum, + .get_link = ethtool_op_get_link, +}; + +static struct net_device_ops greth_netdev_ops = { + .ndo_open = greth_open, + .ndo_stop = greth_close, + .ndo_start_xmit = greth_start_xmit, + .ndo_set_mac_address = greth_set_mac_add, +}; + +static inline int wait_for_mdio(struct greth_private *greth) +{ + unsigned long timeout = jiffies + 4*HZ/100; + while (GRETH_REGLOAD(greth->regs->mdio) & GRETH_MII_BUSY) { + if (time_after(jiffies, timeout)) + return 0; + } + return 1; +} + +static int greth_mdio_read(struct mii_bus *bus, int phy, int reg) +{ + struct greth_private *greth = bus->priv; + int data; + + if (!wait_for_mdio(greth)) + return -EBUSY; + + GRETH_REGSAVE(greth->regs->mdio, ((phy & 0x1F) << 11) | ((reg & 0x1F) << 6) | 2); + + if (!wait_for_mdio(greth)) + return -EBUSY; + + if (!(GRETH_REGLOAD(greth->regs->mdio) & GRETH_MII_NVALID)) { + data = (GRETH_REGLOAD(greth->regs->mdio) >> 16) & 0xFFFF; + return data; + + } else { + return -1; + } +} + +static int greth_mdio_write(struct mii_bus *bus, int phy, int reg, u16 val) +{ + struct greth_private *greth = bus->priv; + + if (!wait_for_mdio(greth)) + return -EBUSY; + + GRETH_REGSAVE(greth->regs->mdio, + ((val & 0xFFFF) << 16) | ((phy & 0x1F) << 11) | ((reg & 0x1F) << 6) | 1); + + if (!wait_for_mdio(greth)) + return -EBUSY; + + return 0; +} + +static int greth_mdio_reset(struct mii_bus *bus) +{ + return 0; +} + +static void greth_link_change(struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + struct phy_device *phydev = greth->phy; + unsigned long flags; + + int status_change = 0; + + spin_lock_irqsave(&greth->devlock, flags); + + if (phydev->link) { + + if ((greth->speed != phydev->speed) || (greth->duplex != phydev->duplex)) { + + GRETH_REGANDIN(greth->regs->control, + ~(GRETH_CTRL_FD | GRETH_CTRL_SP | GRETH_CTRL_GB)); + + if (phydev->duplex) + GRETH_REGORIN(greth->regs->control, GRETH_CTRL_FD); + + if (phydev->speed == SPEED_100) { + + GRETH_REGORIN(greth->regs->control, GRETH_CTRL_SP); + } + + else if (phydev->speed == SPEED_1000) + GRETH_REGORIN(greth->regs->control, GRETH_CTRL_GB); + + greth->speed = phydev->speed; + greth->duplex = phydev->duplex; + status_change = 1; + } + } + + if (phydev->link != greth->link) { + if (!phydev->link) { + greth->speed = 0; + greth->duplex = -1; + } + greth->link = phydev->link; + + status_change = 1; + } + + spin_unlock_irqrestore(&greth->devlock, flags); + + if (status_change) { + if (phydev->link) + pr_debug("%s: link up (%d/%s)\n", + dev->name, phydev->speed, + DUPLEX_FULL == phydev->duplex ? "Full" : "Half"); + else + pr_debug("%s: link down\n", dev->name); + } +} + +static int greth_mdio_probe(struct net_device *dev) +{ + struct greth_private *greth = netdev_priv(dev); + struct phy_device *phy = NULL; + u32 interface; + int i; + + /* Find the first PHY */ + for (i = 0; i < PHY_MAX_ADDR; i++) { + if (greth->mdio->phy_map[i]) { + phy = greth->mdio->phy_map[i]; + break; + } + } + if (!phy) { + if (netif_msg_probe(greth)) + dev_err(&dev->dev, "no PHY found\n"); + return -ENXIO; + } + + if (greth->gbit_mac) + interface = PHY_INTERFACE_MODE_GMII; + else + interface = PHY_INTERFACE_MODE_MII; + + phy = phy_connect(dev, dev_name(&phy->dev), &greth_link_change, 0, interface); + + if (greth->gbit_mac) + phy->supported &= PHY_GBIT_FEATURES; + else + phy->supported &= PHY_BASIC_FEATURES; + + phy->advertising = phy->supported; + + if (IS_ERR(phy)) { + if (netif_msg_ifup(greth)) + dev_err(&dev->dev, "could not attach to PHY\n"); + return PTR_ERR(phy); + } + + greth->link = 0; + greth->speed = 0; + greth->duplex = -1; + greth->phy = phy; + + return 0; +} + +static inline int phy_aneg_done(struct phy_device *phydev) +{ + int retval; + + retval = phy_read(phydev, MII_BMSR); + + return (retval < 0) ? retval : (retval & BMSR_ANEGCOMPLETE); +} + +static int greth_mdio_init(struct greth_private *greth) +{ + int ret, phy; + unsigned long timeout; + + greth->mdio = mdiobus_alloc(); + if (!greth->mdio) { + return -ENOMEM; + } + + greth->mdio->name = "greth-mdio"; + snprintf(greth->mdio->id, MII_BUS_ID_SIZE, "%s-%d", greth->mdio->name, greth->irq); + greth->mdio->read = greth_mdio_read; + greth->mdio->write = greth_mdio_write; + greth->mdio->reset = greth_mdio_reset; + greth->mdio->priv = greth; + + greth->mdio->irq = greth->mdio_irqs; + + for (phy = 0; phy < PHY_MAX_ADDR; phy++) + greth->mdio->irq[phy] = PHY_POLL; + + ret = mdiobus_register(greth->mdio); + if (ret) { + goto error; + } + + ret = greth_mdio_probe(greth->netdev); + if (ret) { + if (netif_msg_probe(greth)) + dev_err(&greth->netdev->dev, "failed to probe MDIO bus\n"); + goto unreg_mdio; + } + + phy_start(greth->phy); + + /* If Ethernet debug link is used make autoneg happen right away */ + if (greth->edcl && greth_edcl == 1) { + phy_start_aneg(greth->phy); + timeout = jiffies + 6*HZ; + while (!phy_aneg_done(greth->phy) && time_before(jiffies, timeout)) { + } + genphy_read_status(greth->phy); + greth_link_change(greth->netdev); + } + + return 0; + +unreg_mdio: + mdiobus_unregister(greth->mdio); +error: + mdiobus_free(greth->mdio); + return ret; +} + +/* Initialize the GRETH MAC */ +static int __devinit greth_of_probe(struct of_device *ofdev, const struct of_device_id *match) +{ + struct net_device *dev; + struct greth_private *greth; + struct greth_regs *regs; + + int i; + int err; + int tmp; + unsigned long timeout; + + dev = alloc_etherdev(sizeof(struct greth_private)); + + if (dev == NULL) + return -ENOMEM; + + greth = netdev_priv(dev); + greth->netdev = dev; + greth->dev = &ofdev->dev; + + if (greth_debug > 0) + greth->msg_enable = greth_debug; + else + greth->msg_enable = GRETH_DEF_MSG_ENABLE; + + spin_lock_init(&greth->devlock); + + greth->regs = of_ioremap(&ofdev->resource[0], 0, + resource_size(&ofdev->resource[0]), + "grlib-greth regs"); + + if (greth->regs == NULL) { + if (netif_msg_probe(greth)) + dev_err(greth->dev, "ioremap failure.\n"); + err = -EIO; + goto error1; + } + + regs = (struct greth_regs *) greth->regs; + greth->irq = ofdev->irqs[0]; + + dev_set_drvdata(greth->dev, dev); + SET_NETDEV_DEV(dev, greth->dev); + + if (netif_msg_probe(greth)) + dev_dbg(greth->dev, "reseting controller.\n"); + + /* Reset the controller. */ + GRETH_REGSAVE(regs->control, GRETH_RESET); + + /* Wait for MAC to reset itself */ + timeout = jiffies + HZ/100; + while (GRETH_REGLOAD(regs->control) & GRETH_RESET) { + if (time_after(jiffies, timeout)) { + err = -EIO; + if (netif_msg_probe(greth)) + dev_err(greth->dev, "timeout when waiting for reset.\n"); + goto error2; + } + } + + /* Get default PHY address */ + greth->phyaddr = (GRETH_REGLOAD(regs->mdio) >> 11) & 0x1F; + + /* Check if we have GBIT capable MAC */ + tmp = GRETH_REGLOAD(regs->control); + greth->gbit_mac = (tmp >> 27) & 1; + + /* Check for multicast capability */ + greth->multicast = (tmp >> 25) & 1; + + greth->edcl = (tmp >> 31) & 1; + + /* If we have EDCL we disable the EDCL speed-duplex FSM so + * it doesn't interfere with the software */ + if (greth->edcl != 0) + GRETH_REGORIN(regs->control, GRETH_CTRL_DISDUPLEX); + + /* Check if MAC can handle MDIO interrupts */ + greth->mdio_int_en = (tmp >> 26) & 1; + + err = greth_mdio_init(greth); + if (err) { + if (netif_msg_probe(greth)) + dev_err(greth->dev, "failed to register MDIO bus\n"); + goto error2; + } + + /* Allocate TX descriptor ring in coherent memory */ + greth->tx_bd_base = (struct greth_bd *) dma_alloc_coherent(greth->dev, + 1024, + &greth->tx_bd_base_phys, + GFP_KERNEL); + + if (!greth->tx_bd_base) { + if (netif_msg_probe(greth)) + dev_err(&dev->dev, "could not allocate descriptor memory.\n"); + err = -ENOMEM; + goto error3; + } + + memset(greth->tx_bd_base, 0, 1024); + + /* Allocate RX descriptor ring in coherent memory */ + greth->rx_bd_base = (struct greth_bd *) dma_alloc_coherent(greth->dev, + 1024, + &greth->rx_bd_base_phys, + GFP_KERNEL); + + if (!greth->rx_bd_base) { + if (netif_msg_probe(greth)) + dev_err(greth->dev, "could not allocate descriptor memory.\n"); + err = -ENOMEM; + goto error4; + } + + memset(greth->rx_bd_base, 0, 1024); + + /* Get MAC address from: module param, OF property or ID prom */ + for (i = 0; i < 6; i++) { + if (macaddr[i] != 0) + break; + } + if (i == 6) { + const unsigned char *addr; + int len; + addr = of_get_property(ofdev->node, "local-mac-address", &len); + if (addr != NULL && len == 6) { + for (i = 0; i < 6; i++) + macaddr[i] = (unsigned int) addr[i]; + } else { +#ifdef CONFIG_SPARC + for (i = 0; i < 6; i++) + macaddr[i] = (unsigned int) idprom->id_ethaddr[i]; +#endif + } + } + + for (i = 0; i < 6; i++) + dev->dev_addr[i] = macaddr[i]; + + macaddr[5]++; + + if (!is_valid_ether_addr(&dev->dev_addr[0])) { + if (netif_msg_probe(greth)) + dev_err(greth->dev, "no valid ethernet address, aborting.\n"); + err = -EINVAL; + goto error5; + } + + GRETH_REGSAVE(regs->esa_msb, dev->dev_addr[0] << 8 | dev->dev_addr[1]); + GRETH_REGSAVE(regs->esa_lsb, dev->dev_addr[2] << 24 | dev->dev_addr[3] << 16 | + dev->dev_addr[4] << 8 | dev->dev_addr[5]); + + /* Clear all pending interrupts except PHY irq */ + GRETH_REGSAVE(regs->status, 0xFF); + + if (greth->gbit_mac) { + dev->features = NETIF_F_SG | NETIF_F_IP_CSUM | NETIF_F_HIGHDMA; + greth_netdev_ops.ndo_start_xmit = greth_start_xmit_gbit; + greth->flags = GRETH_FLAG_RX_CSUM; + } + + if (greth->multicast) { + greth_netdev_ops.ndo_set_multicast_list = greth_set_multicast_list; + dev->flags |= IFF_MULTICAST; + } else { + dev->flags &= ~IFF_MULTICAST; + } + + dev->netdev_ops = &greth_netdev_ops; + dev->ethtool_ops = &greth_ethtool_ops; + + if (register_netdev(dev)) { + if (netif_msg_probe(greth)) + dev_err(greth->dev, "netdevice registration failed.\n"); + err = -ENOMEM; + goto error5; + } + + /* setup NAPI */ + memset(&greth->napi, 0, sizeof(greth->napi)); + netif_napi_add(dev, &greth->napi, greth_poll, 64); + + return 0; + +error5: + dma_free_coherent(greth->dev, 1024, greth->rx_bd_base, greth->rx_bd_base_phys); +error4: + dma_free_coherent(greth->dev, 1024, greth->tx_bd_base, greth->tx_bd_base_phys); +error3: + mdiobus_unregister(greth->mdio); +error2: + of_iounmap(&ofdev->resource[0], greth->regs, resource_size(&ofdev->resource[0])); +error1: + free_netdev(dev); + return err; +} + +static int __devexit greth_of_remove(struct of_device *of_dev) +{ + struct net_device *ndev = dev_get_drvdata(&of_dev->dev); + struct greth_private *greth = netdev_priv(ndev); + + /* Free descriptor areas */ + dma_free_coherent(&of_dev->dev, 1024, greth->rx_bd_base, greth->rx_bd_base_phys); + + dma_free_coherent(&of_dev->dev, 1024, greth->tx_bd_base, greth->tx_bd_base_phys); + + dev_set_drvdata(&of_dev->dev, NULL); + + if (greth->phy) + phy_stop(greth->phy); + mdiobus_unregister(greth->mdio); + + unregister_netdev(ndev); + free_netdev(ndev); + + of_iounmap(&of_dev->resource[0], greth->regs, resource_size(&of_dev->resource[0])); + + return 0; +} + +static struct of_device_id greth_of_match[] = { + { + .name = "GAISLER_ETHMAC", + }, + {}, +}; + +MODULE_DEVICE_TABLE(of, greth_of_match); + +static struct of_platform_driver greth_of_driver = { + .name = "grlib-greth", + .match_table = greth_of_match, + .probe = greth_of_probe, + .remove = __devexit_p(greth_of_remove), + .driver = { + .owner = THIS_MODULE, + .name = "grlib-greth", + }, +}; + +static int __init greth_init(void) +{ + return of_register_platform_driver(&greth_of_driver); +} + +static void __exit greth_cleanup(void) +{ + of_unregister_platform_driver(&greth_of_driver); +} + +module_init(greth_init); +module_exit(greth_cleanup); + +MODULE_AUTHOR("Aeroflex Gaisler AB."); +MODULE_DESCRIPTION("Aeroflex Gaisler Ethernet MAC driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/net/greth.h b/drivers/net/greth.h new file mode 100644 index 000000000000..973388d6abca --- /dev/null +++ b/drivers/net/greth.h @@ -0,0 +1,143 @@ +#ifndef GRETH_H +#define GRETH_H + +#include + +/* Register bits and masks */ +#define GRETH_RESET 0x40 +#define GRETH_MII_BUSY 0x8 +#define GRETH_MII_NVALID 0x10 + +#define GRETH_CTRL_FD 0x10 +#define GRETH_CTRL_PR 0x20 +#define GRETH_CTRL_SP 0x80 +#define GRETH_CTRL_GB 0x100 +#define GRETH_CTRL_PSTATIEN 0x400 +#define GRETH_CTRL_MCEN 0x800 +#define GRETH_CTRL_DISDUPLEX 0x1000 +#define GRETH_STATUS_PHYSTAT 0x100 + +#define GRETH_BD_EN 0x800 +#define GRETH_BD_WR 0x1000 +#define GRETH_BD_IE 0x2000 +#define GRETH_BD_LEN 0x7FF + +#define GRETH_TXEN 0x1 +#define GRETH_INT_TX 0x8 +#define GRETH_TXI 0x4 +#define GRETH_TXBD_STATUS 0x0001C000 +#define GRETH_TXBD_MORE 0x20000 +#define GRETH_TXBD_IPCS 0x40000 +#define GRETH_TXBD_TCPCS 0x80000 +#define GRETH_TXBD_UDPCS 0x100000 +#define GRETH_TXBD_CSALL (GRETH_TXBD_IPCS | GRETH_TXBD_TCPCS | GRETH_TXBD_UDPCS) +#define GRETH_TXBD_ERR_LC 0x10000 +#define GRETH_TXBD_ERR_UE 0x4000 +#define GRETH_TXBD_ERR_AL 0x8000 + +#define GRETH_INT_RX 0x4 +#define GRETH_RXEN 0x2 +#define GRETH_RXI 0x8 +#define GRETH_RXBD_STATUS 0xFFFFC000 +#define GRETH_RXBD_ERR_AE 0x4000 +#define GRETH_RXBD_ERR_FT 0x8000 +#define GRETH_RXBD_ERR_CRC 0x10000 +#define GRETH_RXBD_ERR_OE 0x20000 +#define GRETH_RXBD_ERR_LE 0x40000 +#define GRETH_RXBD_IP 0x80000 +#define GRETH_RXBD_IP_CSERR 0x100000 +#define GRETH_RXBD_UDP 0x200000 +#define GRETH_RXBD_UDP_CSERR 0x400000 +#define GRETH_RXBD_TCP 0x800000 +#define GRETH_RXBD_TCP_CSERR 0x1000000 +#define GRETH_RXBD_IP_FRAG 0x2000000 +#define GRETH_RXBD_MCAST 0x4000000 + +/* Descriptor parameters */ +#define GRETH_TXBD_NUM 128 +#define GRETH_TXBD_NUM_MASK (GRETH_TXBD_NUM-1) +#define GRETH_TX_BUF_SIZE 2048 +#define GRETH_RXBD_NUM 128 +#define GRETH_RXBD_NUM_MASK (GRETH_RXBD_NUM-1) +#define GRETH_RX_BUF_SIZE 2048 + +/* Buffers per page */ +#define GRETH_RX_BUF_PPGAE (PAGE_SIZE/GRETH_RX_BUF_SIZE) +#define GRETH_TX_BUF_PPGAE (PAGE_SIZE/GRETH_TX_BUF_SIZE) + +/* How many pages are needed for buffers */ +#define GRETH_RX_BUF_PAGE_NUM (GRETH_RXBD_NUM/GRETH_RX_BUF_PPGAE) +#define GRETH_TX_BUF_PAGE_NUM (GRETH_TXBD_NUM/GRETH_TX_BUF_PPGAE) + +/* Buffer size. + * Gbit MAC uses tagged maximum frame size which is 1518 excluding CRC. + * Set to 1520 to make all buffers word aligned for non-gbit MAC. + */ +#define MAX_FRAME_SIZE 1520 + +/* Flags */ +#define GRETH_FLAG_RX_CSUM 0x1 + +/* GRETH APB registers */ +struct greth_regs { + u32 control; + u32 status; + u32 esa_msb; + u32 esa_lsb; + u32 mdio; + u32 tx_desc_p; + u32 rx_desc_p; + u32 edclip; + u32 hash_msb; + u32 hash_lsb; +}; + +/* GRETH buffer descriptor */ +struct greth_bd { + u32 stat; + u32 addr; +}; + +struct greth_private { + struct sk_buff *rx_skbuff[GRETH_RXBD_NUM]; + struct sk_buff *tx_skbuff[GRETH_TXBD_NUM]; + + unsigned char *tx_bufs[GRETH_TXBD_NUM]; + unsigned char *rx_bufs[GRETH_RXBD_NUM]; + + u16 tx_next; + u16 tx_last; + u16 tx_free; + u16 rx_cur; + + struct greth_regs *regs; /* Address of controller registers. */ + struct greth_bd *rx_bd_base; /* Address of Rx BDs. */ + struct greth_bd *tx_bd_base; /* Address of Tx BDs. */ + dma_addr_t rx_bd_base_phys; + dma_addr_t tx_bd_base_phys; + + int irq; + + struct device *dev; /* Pointer to of_device->dev */ + struct net_device *netdev; + struct napi_struct napi; + spinlock_t devlock; + + struct phy_device *phy; + struct mii_bus *mdio; + int mdio_irqs[PHY_MAX_ADDR]; + unsigned int link; + unsigned int speed; + unsigned int duplex; + + u32 msg_enable; + u32 flags; + + u8 phyaddr; + u8 multicast; + u8 gbit_mac; + u8 mdio_int_en; + u8 edcl; +}; + +#endif -- cgit v1.2.3 From f546444d0b4f46d812a374a6eb2c46b7d24541f4 Mon Sep 17 00:00:00 2001 From: Chrissie Caulfield Date: Thu, 18 Feb 2010 01:33:13 +0000 Subject: Orphan DECnet Due to lack of time, space, motivation, hardware and probably expertise, I have reluctantly decided to orphan the DECnet code in the kernel. Judging by the deafening silence on the linux-decnet mailing list I suspect it's either not being used anyway, or the few people that are using it are happy with their older kernels. Signed-Off-By: Christine Caulfield Signed-off-by: David S. Miller --- MAINTAINERS | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 32f6915ae0eb..2c8b0d3d7128 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -1733,10 +1733,9 @@ F: include/linux/tfrc.h F: net/dccp/ DECnet NETWORK LAYER -M: Christine Caulfield W: http://linux-decnet.sourceforge.net L: linux-decnet-user@lists.sourceforge.net -S: Maintained +S: Orphan F: Documentation/networking/decnet.txt F: net/decnet/ -- cgit v1.2.3 From 54f2d7361da09f3fc2b5407f93ad3b86df951577 Mon Sep 17 00:00:00 2001 From: Kalle Valo Date: Thu, 18 Feb 2010 21:47:51 +0200 Subject: MAINTAINERS: update Kalle's email address My nokia.com email address won't work anymore, use my private iki.fi address instead. Signed-off-by: Kalle Valo Signed-off-by: John W. Linville --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index ea781c1cfb5a..2b4a4d2f2ece 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5863,7 +5863,7 @@ S: Maintained F: drivers/input/misc/wistron_btns.c WL1251 WIRELESS DRIVER -M: Kalle Valo +M: Kalle Valo L: linux-wireless@vger.kernel.org W: http://wireless.kernel.org T: git git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-testing.git -- cgit v1.2.3 From f66d744d23dcb7ef659612595e2c9fb2fde4e009 Mon Sep 17 00:00:00 2001 From: Ajit Khaparde Date: Fri, 19 Feb 2010 14:00:03 +0000 Subject: MAINTAINERS: Add two maintainers for be2net driver Signed-off-by: Ajit Khaparde Signed-off-by: David S. Miller --- MAINTAINERS | 2 ++ 1 file changed, 2 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2c8b0d3d7128..9b0557a42a9a 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4844,6 +4844,8 @@ F: drivers/scsi/be2iscsi/ SERVER ENGINES 10Gbps NIC - BladeEngine 2 DRIVER M: Sathya Perla M: Subbu Seetharaman +M: Sarveshwar Bandi +M: Ajit Khaparde L: netdev@vger.kernel.org W: http://www.serverengines.com S: Supported -- cgit v1.2.3 From c610028611479e6b9d8b2f1a6628e9417ef4114f Mon Sep 17 00:00:00 2001 From: Amit Shah Date: Fri, 12 Feb 2010 10:32:19 +0530 Subject: Add MAINTAINERS entry for virtio_console I'm taking ownership of the virtio_console module; but I'll continue feeding patches via Rusty. Signed-off-by: Amit Shah Signed-off-by: Rusty Russell --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c95f727236e1..d0b374094008 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2393,6 +2393,12 @@ L: linuxppc-dev@ozlabs.org S: Odd Fixes F: drivers/char/hvc_* +VIRTIO CONSOLE DRIVER +M: Amit Shah +L: virtualization@lists.linux-foundation.org +S: Maintained +F: drivers/char/virtio_console.c + GSPCA FINEPIX SUBDRIVER M: Frank Zago L: linux-media@vger.kernel.org -- cgit v1.2.3 From d094485323a1f2abc7e4665700d6036de36fdaef Mon Sep 17 00:00:00 2001 From: Matthew Garrett Date: Thu, 11 Feb 2010 10:40:13 -0500 Subject: MAINTAINERS: update drivers/platform/x86 information Many of the drivers/platform/x86 drivers have nothing to do with ACPI, so it's kind of inappropriate for them to be stuck under the ACPI mailing list. Add a new mailing list (platform-driver-x86@vger.kernel.org) and, with Len's blessing, add myself as subsystem maintainer. Signed-off-by: Matthew Garrett Cc: Anisse Astier Cc: Carlos Corbacho Cc: Cezary Jackiewicz Cc: Corentin Chary Cc: Daniel Oliveira Nascimento Cc: Harald Welte Cc: Henrique de Moraes Holschuh Cc: Herton Ronaldo Krzesinski Cc: Jonathan Woithe Cc: Karol Kozimor Cc: Len Brown Cc: Lennart Poettering Cc: Mattia Dongili Cc: Peter Feuerer Cc: Sujith Thomas Cc: Thadeu Lima de Souza Cascardo --- MAINTAINERS | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2533fc45a44a..f355d11e453c 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -221,6 +221,7 @@ F: drivers/net/acenic* ACER ASPIRE ONE TEMPERATURE AND FAN DRIVER M: Peter Feuerer +L: platform-driver-x86@vger.kernel.org W: http://piie.net/?section=acerhdf S: Maintained F: drivers/platform/x86/acerhdf.c @@ -228,6 +229,7 @@ F: drivers/platform/x86/acerhdf.c ACER WMI LAPTOP EXTRAS M: Carlos Corbacho L: aceracpi@googlegroups.com (subscribers-only) +L: platform-driver-x86@vger.kernel.org W: http://code.google.com/p/aceracpi S: Maintained F: drivers/platform/x86/acer-wmi.c @@ -288,7 +290,7 @@ F: drivers/acpi/video.c ACPI WMI DRIVER M: Carlos Corbacho -L: linux-acpi@vger.kernel.org +L: platform-driver-x86@vger.kernel.org W: http://www.lesswatts.org/projects/acpi/ S: Maintained F: drivers/platform/x86/wmi.c @@ -968,6 +970,7 @@ ASUS ACPI EXTRAS DRIVER M: Corentin Chary M: Karol Kozimor L: acpi4asus-user@lists.sourceforge.net +L: platform-driver-x86@vger.kernel.org W: http://acpi4asus.sf.net S: Maintained F: drivers/platform/x86/asus_acpi.c @@ -981,6 +984,7 @@ F: drivers/hwmon/asb100.c ASUS LAPTOP EXTRAS DRIVER M: Corentin Chary L: acpi4asus-user@lists.sourceforge.net +L: platform-driver-x86@vger.kernel.org W: http://acpi4asus.sf.net S: Maintained F: drivers/platform/x86/asus-laptop.c @@ -1473,6 +1477,7 @@ F: drivers/scsi/fnic/ CMPC ACPI DRIVER M: Thadeu Lima de Souza Cascardo M: Daniel Oliveira Nascimento +L: platform-driver-x86@vger.kernel.org S: Supported F: drivers/platform/x86/classmate-laptop.c @@ -1516,6 +1521,7 @@ F: drivers/pci/hotplug/cpcihp_generic.c COMPAL LAPTOP SUPPORT M: Cezary Jackiewicz +L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/compal-laptop.c @@ -1746,6 +1752,7 @@ F: drivers/net/defxx.* DELL LAPTOP DRIVER M: Matthew Garrett +L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/dell-laptop.c @@ -2028,6 +2035,7 @@ F: drivers/edac/r82600_edac.c EEEPC LAPTOP EXTRAS DRIVER M: Corentin Chary L: acpi4asus-user@lists.sourceforge.net +L: platform-driver-x86@vger.kernel.org W: http://acpi4asus.sf.net S: Maintained F: drivers/platform/x86/eeepc-laptop.c @@ -2295,7 +2303,7 @@ F: arch/frv/ FUJITSU LAPTOP EXTRAS M: Jonathan Woithe -L: linux-acpi@vger.kernel.org +L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/fujitsu-laptop.c @@ -2561,6 +2569,7 @@ F: drivers/net/wireless/hostap/ HP COMPAQ TC1100 TABLET WMI EXTRAS DRIVER M: Carlos Corbacho +L: platform-driver-x86@vger.kernel.org S: Odd Fixes F: drivers/platform/x86/tc1100-wmi.c @@ -2771,7 +2780,7 @@ F: drivers/video/i810/ INTEL MENLOW THERMAL DRIVER M: Sujith Thomas -L: linux-acpi@vger.kernel.org +L: platform-driver-x86@vger.kernel.org W: http://www.lesswatts.org/projects/acpi/ S: Supported F: drivers/platform/x86/intel_menlow.c @@ -3637,6 +3646,7 @@ F: drivers/char/mxser.* MSI LAPTOP SUPPORT M: Lennart Poettering +L: platform-driver-x86@vger.kernel.org W: https://tango.0pointer.de/mailman/listinfo/s270-linux W: http://0pointer.de/lennart/tchibo.html S: Maintained @@ -3644,6 +3654,7 @@ F: drivers/platform/x86/msi-laptop.c MSI WMI SUPPORT M: Anisse Astier +L: platform-driver-x86@vger.kernel.org S: Supported F: drivers/platform/x86/msi-wmi.c @@ -4096,6 +4107,7 @@ F: drivers/i2c/busses/i2c-pasemi.c PANASONIC LAPTOP ACPI EXTRAS DRIVER M: Harald Welte +L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/panasonic-laptop.c @@ -5034,7 +5046,7 @@ F: include/linux/ssb/ SONY VAIO CONTROL DEVICE DRIVER M: Mattia Dongili -L: linux-acpi@vger.kernel.org +L: platform-driver-x86@vger.kernel.org W: http://www.linux.it/~malattia/wiki/index.php/Sony_drivers S: Maintained F: Documentation/laptops/sony-laptop.txt @@ -5240,6 +5252,7 @@ F: arch/xtensa/ THINKPAD ACPI EXTRAS DRIVER M: Henrique de Moraes Holschuh L: ibm-acpi-devel@lists.sourceforge.net +L: platform-driver-x86@vger.kernel.org W: http://ibm-acpi.sourceforge.net W: http://thinkwiki.org/wiki/Ibm-acpi T: git git://repo.or.cz/linux-2.6/linux-acpi-2.6/ibm-acpi-2.6.git @@ -5293,10 +5306,12 @@ F: security/tomoyo/ TOPSTAR LAPTOP EXTRAS DRIVER M: Herton Ronaldo Krzesinski +L: platform-driver-x86@vger.kernel.org S: Maintained F: drivers/platform/x86/topstar-laptop.c TOSHIBA ACPI EXTRAS DRIVER +L: platform-driver-x86@vger.kernel.org S: Orphan F: drivers/platform/x86/toshiba_acpi.c @@ -6024,6 +6039,12 @@ S: Maintained F: Documentation/x86/ F: arch/x86/ +X86 PLATFORM DRIVERS +M: Matthew Garrett +L: platform-driver-x86@vger.kernel.org +S: Maintained +F: drivers/platform/x86 + XEN HYPERVISOR INTERFACE M: Jeremy Fitzhardinge M: Chris Wright -- cgit v1.2.3 From 5b3f03f044ad6dffc8cd8c9c50bc5d7769cbd89f Mon Sep 17 00:00:00 2001 From: Huang Shijie Date: Tue, 2 Feb 2010 04:07:47 -0300 Subject: V4L/DVB: Add driver for Telegent tlg2300 pd-common.h contains the common data structures, while vendorcmds.h contains the vendor commands for firmware. [mchehab@redhat.com: Folded the 10 patches with the driver] Signed-off-by: Huang Shijie Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/README.tlg2300 | 231 +++++ MAINTAINERS | 8 + drivers/media/video/Kconfig | 2 + drivers/media/video/Makefile | 1 + drivers/media/video/tlg2300/Kconfig | 16 + drivers/media/video/tlg2300/Makefile | 9 + drivers/media/video/tlg2300/pd-alsa.c | 332 ++++++ drivers/media/video/tlg2300/pd-common.h | 280 +++++ drivers/media/video/tlg2300/pd-dvb.c | 593 +++++++++++ drivers/media/video/tlg2300/pd-main.c | 566 ++++++++++ drivers/media/video/tlg2300/pd-radio.c | 351 +++++++ drivers/media/video/tlg2300/pd-video.c | 1648 ++++++++++++++++++++++++++++++ drivers/media/video/tlg2300/vendorcmds.h | 243 +++++ 13 files changed, 4280 insertions(+) create mode 100644 Documentation/video4linux/README.tlg2300 create mode 100644 drivers/media/video/tlg2300/Kconfig create mode 100644 drivers/media/video/tlg2300/Makefile create mode 100644 drivers/media/video/tlg2300/pd-alsa.c create mode 100644 drivers/media/video/tlg2300/pd-common.h create mode 100644 drivers/media/video/tlg2300/pd-dvb.c create mode 100644 drivers/media/video/tlg2300/pd-main.c create mode 100644 drivers/media/video/tlg2300/pd-radio.c create mode 100644 drivers/media/video/tlg2300/pd-video.c create mode 100644 drivers/media/video/tlg2300/vendorcmds.h (limited to 'MAINTAINERS') diff --git a/Documentation/video4linux/README.tlg2300 b/Documentation/video4linux/README.tlg2300 new file mode 100644 index 000000000000..82417db3256f --- /dev/null +++ b/Documentation/video4linux/README.tlg2300 @@ -0,0 +1,231 @@ +tlg2300 release notes +==================== + +This is a v4l2/dvb device driver for the tlg2300 chip. + + +current status +============== + +video + - support mmap and read().(no overlay) + +audio + - The driver will register a ALSA card for the audio input. + +vbi + - Works for almost TV norms. + +dvb-t + - works for DVB-T + +FM + - Works for radio. + +--------------------------------------------------------------------------- +TESTED APPLICATIONS: + +-VLC1.0.4 test the video and dvb. The GUI is friendly to use. + +-Mplayer test the video. + +-Mplayer test the FM. The mplayer should be compiled with --enable-radio and + --enable-radio-capture. + The command runs as this(The alsa audio registers to card 1): + #mplayer radio://103.7/capture/ -radio adevice=hw=1,0:arate=48000 \ + -rawaudio rate=48000:channels=2 + +--------------------------------------------------------------------------- +KNOWN PROBLEMS: + +country code + - The firmware of the chip needs the country code to determine + the stardards of video and audio when it runs for analog TV or radio. + The DVB-T does not need the country code. + + So you must set the country-code correctly. The V4L2 does not have + the interface,the driver has to provide a parameter `country_code'. + + You could set the coutry code in two ways, take USA as example + (The USA's country code is 1): + + [1] add the following line in /etc/modprobe.conf before you insert the + card into USB hub's port : + poseidon country_code=1 + + [2] You can also modify the parameter at runtime (before you run the + application such as VLC) + #echo 1 > /sys/module/poseidon/parameter/country_code + + The known country codes show below: + country code : country + 93 "Afghanistan" + 355 "Albania" + 213 "Algeria" + 684 "American Samoa" + 376 "Andorra" + 244 "Angola" + 54 "Argentina" + 374 "Armenia" + 61 "Australia" + 43 "Austria" + 994 "Azerbaijan" + 973 "Bahrain" + 880 "Bangladesh" + 375 "Belarus" + 32 "Belgium" + 501 "Belize" + 229 "Benin" + 591 "Bolivia" + 387 "Bosnia and Herzegovina" + 267 "Botswana" + 55 "Brazil" + 673 "Brunei Darussalam" + 359 "Bulgalia" + 226 "Burkina Faso" + 257 "Burundi" + 237 "Cameroon" + 1 "Canada" + 236 "Central African Republic" + 235 "Chad" + 56 "Chile" + 86 "China" + 57 "Colombia" + 242 "Congo" + 243 "Congo, Dem. Rep. of " + 506 "Costa Rica" + 385 "Croatia" + 53 "Cuba or Guantanamo Bay" + 357 "Cyprus" + 420 "Czech Republic" + 45 "Denmark" + 246 "Diego Garcia" + 253 "Djibouti" + 593 "Ecuador" + 20 "Egypt" + 503 "El Salvador" + 240 "Equatorial Guinea" + 372 "Estonia" + 251 "Ethiopia" + 358 "Finland" + 33 "France" + 594 "French Guiana" + 689 "French Polynesia" + 241 "Gabonese Republic" + 220 "Gambia" + 995 "Georgia" + 49 "Germany" + 233 "Ghana" + 350 "Gibraltar" + 30 "Greece" + 299 "Greenland" + 671 "Guam" + 502 "Guatemala" + 592 "Guyana" + 509 "Haiti" + 504 "Honduras" + 852 "Hong Kong SAR, China" + 36 "Hungary" + 354 "Iceland" + 91 "India" + 98 "Iran" + 964 "Iraq" + 353 "Ireland" + 972 "Israel" + 39 "Italy or Vatican City" + 225 "Ivory Coast" + 81 "Japan" + 962 "Jordan" + 7 "Kazakhstan or Kyrgyzstan" + 254 "Kenya" + 686 "Kiribati" + 965 "Kuwait" + 856 "Laos" + 371 "Latvia" + 961 "Lebanon" + 266 "Lesotho" + 231 "Liberia" + 218 "Libya" + 41 "Liechtenstein or Switzerland" + 370 "Lithuania" + 352 "Luxembourg" + 853 "Macau SAR, China" + 261 "Madagascar" + 60 "Malaysia" + 960 "Maldives" + 223 "Mali Republic" + 356 "Malta" + 692 "Marshall Islands" + 596 "Martinique" + 222 "Mauritania" + 230 "Mauritus" + 52 "Mexico" + 691 "Micronesia" + 373 "Moldova" + 377 "Monaco" + 976 "Mongolia" + 212 "Morocco" + 258 "Mozambique" + 95 "Myanmar" + 264 "Namibia" + 674 "Nauru" + 31 "Netherlands" + 687 "New Caledonia" + 64 "New Zealand" + 505 "Nicaragua" + 227 "Niger" + 234 "Nigeria" + 850 "North Korea" + 47 "Norway" + 968 "Oman" + 92 "Pakistan" + 680 "Palau" + 507 "Panama" + 675 "Papua New Guinea" + 595 "Paraguay" + 51 "Peru" + 63 "Philippines" + 48 "Poland" + 351 "Portugal" + 974 "Qatar" + 262 "Reunion Island" + 40 "Romania" + 7 "Russia" + 378 "San Marino" + 239 "Sao Tome and Principe" + 966 "Saudi Arabia" + 221 "Senegal" + 248 "Seychelles Republic" + 232 "Sierra Leone" + 65 "Singapore" + 421 "Slovak Republic" + 386 "Slovenia" + 27 "South Africa" + 82 "South Korea " + 34 "Spain" + 94 "Sri Lanka" + 508 "St. Pierre and Miquelon" + 249 "Sudan" + 597 "Suriname" + 268 "Swaziland" + 46 "Sweden" + 963 "Syria" + 886 "Taiwan Region" + 255 "Tanzania" + 66 "Thailand" + 228 "Togolese Republic" + 216 "Tunisia" + 90 "Turkey" + 993 "Turkmenistan" + 256 "Uganda" + 380 "Ukraine" + 971 "United Arab Emirates" + 44 "United Kingdom" + 1 "United States of America" + 598 "Uruguay" + 58 "Venezuela" + 84 "Vietnam" + 967 "Yemen" + 260 "Zambia" + 255 "Zanzibar" + 263 "Zimbabwe" diff --git a/MAINTAINERS b/MAINTAINERS index 2533fc45a44a..f427294b85e0 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4676,6 +4676,14 @@ F: drivers/media/common/saa7146* F: drivers/media/video/*7146* F: include/media/*7146* +TLG2300 VIDEO4LINUX-2 DRIVER +M Huang Shijie +M Kang Yong +M Zhang Xiaobing +S: Supported +F: drivers/media/video/tlg2300 + + SC1200 WDT DRIVER M: Zwane Mwaikambo S: Maintained diff --git a/drivers/media/video/Kconfig b/drivers/media/video/Kconfig index 64682bff228a..2f9c57d5fda3 100644 --- a/drivers/media/video/Kconfig +++ b/drivers/media/video/Kconfig @@ -949,6 +949,8 @@ source "drivers/media/video/hdpvr/Kconfig" source "drivers/media/video/em28xx/Kconfig" +source "drivers/media/video/tlg2300/Kconfig" + source "drivers/media/video/cx231xx/Kconfig" source "drivers/media/video/usbvision/Kconfig" diff --git a/drivers/media/video/Makefile b/drivers/media/video/Makefile index 2af68ee84122..5163289e13ee 100644 --- a/drivers/media/video/Makefile +++ b/drivers/media/video/Makefile @@ -99,6 +99,7 @@ obj-$(CONFIG_VIDEO_MEYE) += meye.o obj-$(CONFIG_VIDEO_SAA7134) += saa7134/ obj-$(CONFIG_VIDEO_CX88) += cx88/ obj-$(CONFIG_VIDEO_EM28XX) += em28xx/ +obj-$(CONFIG_VIDEO_TLG2300) += tlg2300/ obj-$(CONFIG_VIDEO_CX231XX) += cx231xx/ obj-$(CONFIG_VIDEO_USBVISION) += usbvision/ obj-$(CONFIG_VIDEO_PVRUSB2) += pvrusb2/ diff --git a/drivers/media/video/tlg2300/Kconfig b/drivers/media/video/tlg2300/Kconfig new file mode 100644 index 000000000000..2c29ec659b4e --- /dev/null +++ b/drivers/media/video/tlg2300/Kconfig @@ -0,0 +1,16 @@ +config VIDEO_TLG2300 + tristate "Telegent TLG2300 USB video capture support" + depends on VIDEO_DEV && I2C && INPUT && SND && DVB_CORE + select VIDEO_TUNER + select VIDEO_TVEEPROM + select VIDEO_IR + select VIDEOBUF_VMALLOC + select SND_PCM + select VIDEOBUF_DVB + + ---help--- + This is a video4linux driver for Telegent tlg2300 based TV cards. + The driver supports V4L2, DVB-T and radio. + + To compile this driver as a module, choose M here: the + module will be called poseidon diff --git a/drivers/media/video/tlg2300/Makefile b/drivers/media/video/tlg2300/Makefile new file mode 100644 index 000000000000..81bb7fdd1e3d --- /dev/null +++ b/drivers/media/video/tlg2300/Makefile @@ -0,0 +1,9 @@ +poseidon-objs := pd-video.o pd-alsa.o pd-dvb.o pd-radio.o pd-main.o + +obj-$(CONFIG_VIDEO_TLG2300) += poseidon.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/media/video/tlg2300/pd-alsa.c b/drivers/media/video/tlg2300/pd-alsa.c new file mode 100644 index 000000000000..6f42621ad478 --- /dev/null +++ b/drivers/media/video/tlg2300/pd-alsa.c @@ -0,0 +1,332 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "pd-common.h" +#include "vendorcmds.h" + +static void complete_handler_audio(struct urb *urb); +#define AUDIO_EP (0x83) +#define AUDIO_BUF_SIZE (512) +#define PERIOD_SIZE (1024 * 8) +#define PERIOD_MIN (4) +#define PERIOD_MAX PERIOD_MIN + +static struct snd_pcm_hardware snd_pd_hw_capture = { + .info = SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_MMAP_VALID, + + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_48000, + + .rate_min = 48000, + .rate_max = 48000, + .channels_min = 2, + .channels_max = 2, + .buffer_bytes_max = PERIOD_SIZE * PERIOD_MIN, + .period_bytes_min = PERIOD_SIZE, + .period_bytes_max = PERIOD_SIZE, + .periods_min = PERIOD_MIN, + .periods_max = PERIOD_MAX, + /* + .buffer_bytes_max = 62720 * 8, + .period_bytes_min = 64, + .period_bytes_max = 12544, + .periods_min = 2, + .periods_max = 98 + */ +}; + +static int snd_pd_capture_open(struct snd_pcm_substream *substream) +{ + struct poseidon *p = snd_pcm_substream_chip(substream); + struct poseidon_audio *pa = &p->audio; + struct snd_pcm_runtime *runtime = substream->runtime; + + if (!p) + return -ENODEV; + pa->users++; + pa->card_close = 0; + pa->capture_pcm_substream = substream; + runtime->private_data = p; + + runtime->hw = snd_pd_hw_capture; + snd_pcm_hw_constraint_integer(runtime, SNDRV_PCM_HW_PARAM_PERIODS); + usb_autopm_get_interface(p->interface); + kref_get(&p->kref); + return 0; +} + +static int snd_pd_pcm_close(struct snd_pcm_substream *substream) +{ + struct poseidon *p = snd_pcm_substream_chip(substream); + struct poseidon_audio *pa = &p->audio; + + pa->users--; + pa->card_close = 1; + usb_autopm_put_interface(p->interface); + kref_put(&p->kref, poseidon_delete); + return 0; +} + +static int snd_pd_hw_capture_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct snd_pcm_runtime *runtime = substream->runtime; + unsigned int size; + + size = params_buffer_bytes(hw_params); + if (runtime->dma_area) { + if (runtime->dma_bytes > size) + return 0; + vfree(runtime->dma_area); + } + runtime->dma_area = vmalloc(size); + if (!runtime->dma_area) + return -ENOMEM; + else + runtime->dma_bytes = size; + return 0; +} + +static int audio_buf_free(struct poseidon *p) +{ + struct poseidon_audio *pa = &p->audio; + int i; + + for (i = 0; i < AUDIO_BUFS; i++) + if (pa->urb_array[i]) + usb_kill_urb(pa->urb_array[i]); + free_all_urb_generic(pa->urb_array, AUDIO_BUFS); + logpm(); + return 0; +} + +static int snd_pd_hw_capture_free(struct snd_pcm_substream *substream) +{ + struct poseidon *p = snd_pcm_substream_chip(substream); + + logpm(); + audio_buf_free(p); + return 0; +} + +static int snd_pd_prepare(struct snd_pcm_substream *substream) +{ + return 0; +} + +#define AUDIO_TRAILER_SIZE (16) +static inline void handle_audio_data(struct urb *urb, int *period_elapsed) +{ + struct poseidon_audio *pa = urb->context; + struct snd_pcm_runtime *runtime = pa->capture_pcm_substream->runtime; + + int stride = runtime->frame_bits >> 3; + int len = urb->actual_length / stride; + unsigned char *cp = urb->transfer_buffer; + unsigned int oldptr = pa->rcv_position; + + if (urb->actual_length == AUDIO_BUF_SIZE - 4) + len -= (AUDIO_TRAILER_SIZE / stride); + + /* do the copy */ + if (oldptr + len >= runtime->buffer_size) { + unsigned int cnt = runtime->buffer_size - oldptr; + + memcpy(runtime->dma_area + oldptr * stride, cp, cnt * stride); + memcpy(runtime->dma_area, (cp + cnt * stride), + (len * stride - cnt * stride)); + } else + memcpy(runtime->dma_area + oldptr * stride, cp, len * stride); + + /* update the statas */ + snd_pcm_stream_lock(pa->capture_pcm_substream); + pa->rcv_position += len; + if (pa->rcv_position >= runtime->buffer_size) + pa->rcv_position -= runtime->buffer_size; + + pa->copied_position += (len); + if (pa->copied_position >= runtime->period_size) { + pa->copied_position -= runtime->period_size; + *period_elapsed = 1; + } + snd_pcm_stream_unlock(pa->capture_pcm_substream); +} + +static void complete_handler_audio(struct urb *urb) +{ + struct poseidon_audio *pa = urb->context; + struct snd_pcm_substream *substream = pa->capture_pcm_substream; + int period_elapsed = 0; + int ret; + + if (1 == pa->card_close || pa->capture_stream != STREAM_ON) + return; + + if (urb->status != 0) { + /*if (urb->status == -ESHUTDOWN)*/ + return; + } + + if (substream) { + if (urb->actual_length) { + handle_audio_data(urb, &period_elapsed); + if (period_elapsed) + snd_pcm_period_elapsed(substream); + } + } + + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret < 0) + log("audio urb failed (errcod = %i)", ret); + return; +} + +static int fire_audio_urb(struct poseidon *p) +{ + int i, ret = 0; + struct poseidon_audio *pa = &p->audio; + + alloc_bulk_urbs_generic(pa->urb_array, AUDIO_BUFS, + p->udev, AUDIO_EP, + AUDIO_BUF_SIZE, GFP_ATOMIC, + complete_handler_audio, pa); + + for (i = 0; i < AUDIO_BUFS; i++) { + ret = usb_submit_urb(pa->urb_array[i], GFP_KERNEL); + if (ret) + log("urb err : %d", ret); + } + log(); + return ret; +} + +static int snd_pd_capture_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct poseidon *p = snd_pcm_substream_chip(substream); + struct poseidon_audio *pa = &p->audio; + + if (debug_mode) + log("cmd %d, audio stat : %d\n", cmd, pa->capture_stream); + + switch (cmd) { + case SNDRV_PCM_TRIGGER_RESUME: + case SNDRV_PCM_TRIGGER_START: + if (pa->capture_stream == STREAM_ON) + return 0; + + pa->rcv_position = pa->copied_position = 0; + pa->capture_stream = STREAM_ON; + + if (in_hibernation(p)) + return 0; + fire_audio_urb(p); + return 0; + + case SNDRV_PCM_TRIGGER_SUSPEND: + pa->capture_stream = STREAM_SUSPEND; + return 0; + case SNDRV_PCM_TRIGGER_STOP: + pa->capture_stream = STREAM_OFF; + return 0; + default: + return -EINVAL; + } +} + +static snd_pcm_uframes_t +snd_pd_capture_pointer(struct snd_pcm_substream *substream) +{ + struct poseidon *p = snd_pcm_substream_chip(substream); + struct poseidon_audio *pa = &p->audio; + return pa->rcv_position; +} + +static struct page *snd_pcm_pd_get_page(struct snd_pcm_substream *subs, + unsigned long offset) +{ + void *pageptr = subs->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); +} + +static struct snd_pcm_ops pcm_capture_ops = { + .open = snd_pd_capture_open, + .close = snd_pd_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = snd_pd_hw_capture_params, + .hw_free = snd_pd_hw_capture_free, + .prepare = snd_pd_prepare, + .trigger = snd_pd_capture_trigger, + .pointer = snd_pd_capture_pointer, + .page = snd_pcm_pd_get_page, +}; + +#ifdef CONFIG_PM +int pm_alsa_suspend(struct poseidon *p) +{ + logpm(p); + audio_buf_free(p); + return 0; +} + +int pm_alsa_resume(struct poseidon *p) +{ + logpm(p); + fire_audio_urb(p); + return 0; +} +#endif + +int poseidon_audio_init(struct poseidon *p) +{ + struct poseidon_audio *pa = &p->audio; + struct snd_card *card; + struct snd_pcm *pcm; + int ret; + + ret = snd_card_create(-1, "Telegent", THIS_MODULE, 0, &card); + if (ret != 0) + return ret; + + ret = snd_pcm_new(card, "poseidon audio", 0, 0, 1, &pcm); + snd_pcm_set_ops(pcm, SNDRV_PCM_STREAM_CAPTURE, &pcm_capture_ops); + pcm->info_flags = 0; + pcm->private_data = p; + strcpy(pcm->name, "poseidon audio capture"); + + strcpy(card->driver, "ALSA driver"); + strcpy(card->shortname, "poseidon Audio"); + strcpy(card->longname, "poseidon ALSA Audio"); + + if (snd_card_register(card)) { + snd_card_free(card); + return -ENOMEM; + } + pa->card = card; + return 0; +} + +int poseidon_audio_free(struct poseidon *p) +{ + struct poseidon_audio *pa = &p->audio; + + if (pa->card) + snd_card_free(pa->card); + return 0; +} diff --git a/drivers/media/video/tlg2300/pd-common.h b/drivers/media/video/tlg2300/pd-common.h new file mode 100644 index 000000000000..619fd009e965 --- /dev/null +++ b/drivers/media/video/tlg2300/pd-common.h @@ -0,0 +1,280 @@ +#ifndef PD_COMMON_H +#define PD_COMMON_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "dvb_frontend.h" +#include "dvbdev.h" +#include "dvb_demux.h" +#include "dmxdev.h" + +#define SBUF_NUM 8 +#define MAX_BUFFER_NUM 6 +#define PK_PER_URB 32 +#define ISO_PKT_SIZE 3072 + +#define POSEIDON_STATE_NONE (0x0000) +#define POSEIDON_STATE_ANALOG (0x0001) +#define POSEIDON_STATE_FM (0x0002) +#define POSEIDON_STATE_DVBT (0x0004) +#define POSEIDON_STATE_VBI (0x0008) +#define POSEIDON_STATE_DISCONNECT (0x0080) + +#define PM_SUSPEND_DELAY 3 + +#define V4L_PAL_VBI_LINES 18 +#define V4L_NTSC_VBI_LINES 12 +#define V4L_PAL_VBI_FRAMESIZE (V4L_PAL_VBI_LINES * 1440 * 2) +#define V4L_NTSC_VBI_FRAMESIZE (V4L_NTSC_VBI_LINES * 1440 * 2) + +#define TUNER_FREQ_MIN (45000000) +#define TUNER_FREQ_MAX (862000000) + +struct vbi_data { + struct video_device *v_dev; + struct video_data *video; + struct front_face *front; + + unsigned int copied; + unsigned int vbi_size; /* the whole size of two fields */ + int users; +}; + +/* + * This is the running context of the video, it is useful for + * resume() + */ +struct running_context { + u32 freq; /* VIDIOC_S_FREQUENCY */ + int audio_idx; /* VIDIOC_S_TUNER */ + v4l2_std_id tvnormid; /* VIDIOC_S_STD */ + int sig_index; /* VIDIOC_S_INPUT */ + struct v4l2_pix_format pix; /* VIDIOC_S_FMT */ +}; + +struct video_data { + /* v4l2 video device */ + struct video_device *v_dev; + + /* the working context */ + struct running_context context; + + /* for data copy */ + int field_count; + + char *dst; + int lines_copied; + int prev_left; + + int lines_per_field; + int lines_size; + + /* for communication */ + u8 endpoint_addr; + struct urb *urb_array[SBUF_NUM]; + struct vbi_data *vbi; + struct poseidon *pd; + struct front_face *front; + + int is_streaming; + int users; + + /* for bubble handler */ + struct work_struct bubble_work; +}; + +enum pcm_stream_state { + STREAM_OFF, + STREAM_ON, + STREAM_SUSPEND, +}; + +#define AUDIO_BUFS (3) +#define CAPTURE_STREAM_EN 1 +struct poseidon_audio { + struct urb *urb_array[AUDIO_BUFS]; + unsigned int copied_position; + struct snd_pcm_substream *capture_pcm_substream; + + unsigned int rcv_position; + struct snd_card *card; + int card_close; + + int users; + int pm_state; + enum pcm_stream_state capture_stream; +}; + +struct radio_data { + __u32 fm_freq; + int users; + unsigned int is_radio_streaming; + struct video_device *fm_dev; +}; + +#define DVB_SBUF_NUM 4 +#define DVB_URB_BUF_SIZE 0x2000 +struct pd_dvb_adapter { + struct dvb_adapter dvb_adap; + struct dvb_frontend dvb_fe; + struct dmxdev dmxdev; + struct dvb_demux demux; + + atomic_t users; + atomic_t active_feed; + + /* data transfer */ + s32 is_streaming; + struct urb *urb_array[DVB_SBUF_NUM]; + struct poseidon *pd_device; + u8 ep_addr; + u8 reserved[3]; + + /* data for power resume*/ + struct dvb_frontend_parameters fe_param; + + /* for channel scanning */ + int prev_freq; + int bandwidth; + unsigned long last_jiffies; +}; + +struct front_face { + /* use this field to distinguish VIDEO and VBI */ + enum v4l2_buf_type type; + + /* for host */ + struct videobuf_queue q; + + /* the bridge for host and device */ + struct videobuf_buffer *curr_frame; + + /* for device */ + spinlock_t queue_lock; + struct list_head active; + struct poseidon *pd; +}; + +struct poseidon { + struct list_head device_list; + + struct mutex lock; + struct kref kref; + + /* for V4L2 */ + struct v4l2_device v4l2_dev; + + /* hardware info */ + struct usb_device *udev; + struct usb_interface *interface; + int cur_transfer_mode; + + struct video_data video_data; /* video */ + struct vbi_data vbi_data; /* vbi */ + struct poseidon_audio audio; /* audio (alsa) */ + struct radio_data radio_data; /* FM */ + struct pd_dvb_adapter dvb_data; /* DVB */ + + u32 state; + int country_code; + struct file *file_for_stream; /* the active stream*/ + +#ifdef CONFIG_PM + int (*pm_suspend)(struct poseidon *); + int (*pm_resume)(struct poseidon *); + pm_message_t msg; + + struct work_struct pm_work; + u8 portnum; +#endif +}; + +struct poseidon_format { + char *name; + int fourcc; /* video4linux 2 */ + int depth; /* bit/pixel */ + int flags; +}; + +struct poseidon_tvnorm { + v4l2_std_id v4l2_id; + char name[12]; + u32 tlg_tvnorm; +}; + +/* video */ +int pd_video_init(struct poseidon *); +void pd_video_exit(struct poseidon *); +int stop_all_video_stream(struct poseidon *); + +/* alsa audio */ +int poseidon_audio_init(struct poseidon *); +int poseidon_audio_free(struct poseidon *); +#ifdef CONFIG_PM +int pm_alsa_suspend(struct poseidon *); +int pm_alsa_resume(struct poseidon *); +#endif + +/* dvb */ +int pd_dvb_usb_device_init(struct poseidon *); +void pd_dvb_usb_device_exit(struct poseidon *); +void pd_dvb_usb_device_cleanup(struct poseidon *); +int pd_dvb_get_adapter_num(struct pd_dvb_adapter *); +void dvb_stop_streaming(struct pd_dvb_adapter *); + +/* FM */ +int poseidon_fm_init(struct poseidon *); +int poseidon_fm_exit(struct poseidon *); +struct video_device *vdev_init(struct poseidon *, struct video_device *); + +/* vendor command ops */ +int send_set_req(struct poseidon*, u8, s32, s32*); +int send_get_req(struct poseidon*, u8, s32, void*, s32*, s32); +s32 set_tuner_mode(struct poseidon*, unsigned char); +enum tlg__analog_audio_standard get_audio_std(s32, s32); + +/* bulk urb alloc/free */ +int alloc_bulk_urbs_generic(struct urb **urb_array, int num, + struct usb_device *udev, u8 ep_addr, + int buf_size, gfp_t gfp_flags, + usb_complete_t complete_fn, void *context); +void free_all_urb_generic(struct urb **urb_array, int num); + +/* misc */ +void poseidon_delete(struct kref *kref); +void destroy_video_device(struct video_device **v_dev); +extern int country_code; +extern int debug_mode; +void set_debug_mode(struct video_device *vfd, int debug_mode); + +#define in_hibernation(pd) (pd->msg.event == PM_EVENT_FREEZE) +#define get_pm_count(p) (atomic_read(&(p)->interface->pm_usage_cnt)) + +#define log(a, ...) printk(KERN_DEBUG "\t[ %s : %.3d ] "a"\n", \ + __func__, __LINE__, ## __VA_ARGS__) + +/* for power management */ +#define logpm(pd) do {\ + if (debug_mode & 0x10)\ + log();\ + } while (0) + +#define logs(f) do { \ + if ((debug_mode & 0x4) && \ + (f)->type == V4L2_BUF_TYPE_VBI_CAPTURE) \ + log("type : VBI");\ + \ + if ((debug_mode & 0x8) && \ + (f)->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) \ + log("type : VIDEO");\ + } while (0) +#endif diff --git a/drivers/media/video/tlg2300/pd-dvb.c b/drivers/media/video/tlg2300/pd-dvb.c new file mode 100644 index 000000000000..4133aee568bf --- /dev/null +++ b/drivers/media/video/tlg2300/pd-dvb.c @@ -0,0 +1,593 @@ +#include "pd-common.h" +#include +#include +#include +#include + +#include "vendorcmds.h" +#include +#include + +static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb); + +static int dvb_bandwidth[][2] = { + { TLG_BW_8, BANDWIDTH_8_MHZ }, + { TLG_BW_7, BANDWIDTH_7_MHZ }, + { TLG_BW_6, BANDWIDTH_6_MHZ } +}; +static int dvb_bandwidth_length = ARRAY_SIZE(dvb_bandwidth); + +static s32 dvb_start_streaming(struct pd_dvb_adapter *pd_dvb); +static int poseidon_check_mode_dvbt(struct poseidon *pd) +{ + s32 ret = 0, cmd_status = 0; + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/4); + + ret = usb_set_interface(pd->udev, 0, BULK_ALTERNATE_IFACE); + if (ret != 0) + return ret; + + ret = set_tuner_mode(pd, TLG_MODE_CAPS_DVB_T); + if (ret) + return ret; + + /* signal source */ + ret = send_set_req(pd, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &cmd_status); + if (ret|cmd_status) + return ret; + + return 0; +} + +/* acquire : + * 1 == open + * 0 == release + */ +static int poseidon_ts_bus_ctrl(struct dvb_frontend *fe, int acquire) +{ + struct poseidon *pd = fe->demodulator_priv; + struct pd_dvb_adapter *pd_dvb; + int ret = 0; + + if (!pd) + return -ENODEV; + + pd_dvb = container_of(fe, struct pd_dvb_adapter, dvb_fe); + if (acquire) { + mutex_lock(&pd->lock); + if (pd->state & POSEIDON_STATE_DISCONNECT) { + ret = -ENODEV; + goto open_out; + } + + if (pd->state && !(pd->state & POSEIDON_STATE_DVBT)) { + ret = -EBUSY; + goto open_out; + } + + usb_autopm_get_interface(pd->interface); + if (0 == pd->state) { + ret = poseidon_check_mode_dvbt(pd); + if (ret < 0) { + usb_autopm_put_interface(pd->interface); + goto open_out; + } + pd->state |= POSEIDON_STATE_DVBT; + pd_dvb->bandwidth = 0; + pd_dvb->prev_freq = 0; + } + atomic_inc(&pd_dvb->users); + kref_get(&pd->kref); +open_out: + mutex_unlock(&pd->lock); + } else { + dvb_stop_streaming(pd_dvb); + + if (atomic_dec_and_test(&pd_dvb->users)) { + mutex_lock(&pd->lock); + pd->state &= ~POSEIDON_STATE_DVBT; + mutex_unlock(&pd->lock); + } + kref_put(&pd->kref, poseidon_delete); + usb_autopm_put_interface(pd->interface); + } + return ret; +} + +static void poseidon_fe_release(struct dvb_frontend *fe) +{ + struct poseidon *pd = fe->demodulator_priv; + +#ifdef CONFIG_PM + pd->pm_suspend = NULL; + pd->pm_resume = NULL; +#endif +} + +static s32 poseidon_fe_sleep(struct dvb_frontend *fe) +{ + return 0; +} + +/* + * return true if we can satisfy the conditions, else return false. + */ +static bool check_scan_ok(__u32 freq, int bandwidth, + struct pd_dvb_adapter *adapter) +{ + if (bandwidth < 0) + return false; + + if (adapter->prev_freq == freq + && adapter->bandwidth == bandwidth) { + long nl = jiffies - adapter->last_jiffies; + unsigned int msec ; + + msec = jiffies_to_msecs(abs(nl)); + return msec > 15000 ? true : false; + } + return true; +} + +/* + * Check if the firmware delays too long for an invalid frequency. + */ +static int fw_delay_overflow(struct pd_dvb_adapter *adapter) +{ + long nl = jiffies - adapter->last_jiffies; + unsigned int msec ; + + msec = jiffies_to_msecs(abs(nl)); + return msec > 800 ? true : false; +} + +static int poseidon_set_fe(struct dvb_frontend *fe, + struct dvb_frontend_parameters *fep) +{ + s32 ret = 0, cmd_status = 0; + s32 i, bandwidth = -1; + struct poseidon *pd = fe->demodulator_priv; + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + + if (in_hibernation(pd)) + return -EBUSY; + + mutex_lock(&pd->lock); + for (i = 0; i < dvb_bandwidth_length; i++) + if (fep->u.ofdm.bandwidth == dvb_bandwidth[i][1]) + bandwidth = dvb_bandwidth[i][0]; + + if (check_scan_ok(fep->frequency, bandwidth, pd_dvb)) { + ret = send_set_req(pd, TUNE_FREQ_SELECT, + fep->frequency / 1000, &cmd_status); + if (ret | cmd_status) { + log("error line"); + goto front_out; + } + + ret = send_set_req(pd, DVBT_BANDW_SEL, + bandwidth, &cmd_status); + if (ret | cmd_status) { + log("error line"); + goto front_out; + } + + ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); + if (ret | cmd_status) { + log("error line"); + goto front_out; + } + + /* save the context for future */ + memcpy(&pd_dvb->fe_param, fep, sizeof(*fep)); + pd_dvb->bandwidth = bandwidth; + pd_dvb->prev_freq = fep->frequency; + pd_dvb->last_jiffies = jiffies; + } +front_out: + mutex_unlock(&pd->lock); + return ret; +} + +#ifdef CONFIG_PM +static int pm_dvb_suspend(struct poseidon *pd) +{ + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + dvb_stop_streaming(pd_dvb); + dvb_urb_cleanup(pd_dvb); + msleep(500); + return 0; +} + +static int pm_dvb_resume(struct poseidon *pd) +{ + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + + poseidon_check_mode_dvbt(pd); + msleep(300); + poseidon_set_fe(&pd_dvb->dvb_fe, &pd_dvb->fe_param); + + dvb_start_streaming(pd_dvb); + return 0; +} +#endif + +static s32 poseidon_fe_init(struct dvb_frontend *fe) +{ + struct poseidon *pd = fe->demodulator_priv; + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + +#ifdef CONFIG_PM + pd->pm_suspend = pm_dvb_suspend; + pd->pm_resume = pm_dvb_resume; +#endif + memset(&pd_dvb->fe_param, 0, + sizeof(struct dvb_frontend_parameters)); + return 0; +} + +static int poseidon_get_fe(struct dvb_frontend *fe, + struct dvb_frontend_parameters *fep) +{ + struct poseidon *pd = fe->demodulator_priv; + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + + memcpy(fep, &pd_dvb->fe_param, sizeof(*fep)); + return 0; +} + +static int poseidon_fe_get_tune_settings(struct dvb_frontend *fe, + struct dvb_frontend_tune_settings *tune) +{ + tune->min_delay_ms = 1000; + return 0; +} + +static int poseidon_read_status(struct dvb_frontend *fe, fe_status_t *stat) +{ + struct poseidon *pd = fe->demodulator_priv; + s32 ret = -1, cmd_status; + struct tuner_dtv_sig_stat_s status = {}; + + if (in_hibernation(pd)) + return -EBUSY; + mutex_lock(&pd->lock); + + ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_DVB_T, + &status, &cmd_status, sizeof(status)); + if (ret | cmd_status) { + log("get tuner status error"); + goto out; + } + + if (debug_mode) + log("P : %d, L %d, LB :%d", status.sig_present, + status.sig_locked, status.sig_lock_busy); + + if (status.sig_lock_busy) { + goto out; + } else if (status.sig_present || status.sig_locked) { + *stat |= FE_HAS_LOCK | FE_HAS_SIGNAL | FE_HAS_CARRIER + | FE_HAS_SYNC | FE_HAS_VITERBI; + } else { + if (fw_delay_overflow(&pd->dvb_data)) + *stat |= FE_TIMEDOUT; + } +out: + mutex_unlock(&pd->lock); + return ret; +} + +static int poseidon_read_ber(struct dvb_frontend *fe, u32 *ber) +{ + struct poseidon *pd = fe->demodulator_priv; + struct tuner_ber_rate_s tlg_ber = {}; + s32 ret = -1, cmd_status; + + mutex_lock(&pd->lock); + ret = send_get_req(pd, TUNER_BER_RATE, 0, + &tlg_ber, &cmd_status, sizeof(tlg_ber)); + if (ret | cmd_status) + goto out; + *ber = tlg_ber.ber_rate; +out: + mutex_unlock(&pd->lock); + return ret; +} + +static s32 poseidon_read_signal_strength(struct dvb_frontend *fe, u16 *strength) +{ + struct poseidon *pd = fe->demodulator_priv; + struct tuner_dtv_sig_stat_s status = {}; + s32 ret = 0, cmd_status; + + mutex_lock(&pd->lock); + ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_DVB_T, + &status, &cmd_status, sizeof(status)); + if (ret | cmd_status) + goto out; + if ((status.sig_present || status.sig_locked) && !status.sig_strength) + *strength = 0xFFFF; + else + *strength = status.sig_strength; +out: + mutex_unlock(&pd->lock); + return ret; +} + +static int poseidon_read_snr(struct dvb_frontend *fe, u16 *snr) +{ + return 0; +} + +static int poseidon_read_unc_blocks(struct dvb_frontend *fe, u32 *unc) +{ + *unc = 0; + return 0; +} + +static struct dvb_frontend_ops poseidon_frontend_ops = { + .info = { + .name = "Poseidon DVB-T", + .type = FE_OFDM, + .frequency_min = 174000000, + .frequency_max = 862000000, + .frequency_stepsize = 62500,/* FIXME */ + .caps = FE_CAN_INVERSION_AUTO | + FE_CAN_FEC_1_2 | FE_CAN_FEC_2_3 | FE_CAN_FEC_3_4 | + FE_CAN_FEC_5_6 | FE_CAN_FEC_7_8 | FE_CAN_FEC_AUTO | + FE_CAN_QPSK | FE_CAN_QAM_16 | FE_CAN_QAM_64 | + FE_CAN_QAM_AUTO | FE_CAN_TRANSMISSION_MODE_AUTO | + FE_CAN_GUARD_INTERVAL_AUTO | + FE_CAN_RECOVER | + FE_CAN_HIERARCHY_AUTO, + }, + + .release = poseidon_fe_release, + + .init = poseidon_fe_init, + .sleep = poseidon_fe_sleep, + + .set_frontend = poseidon_set_fe, + .get_frontend = poseidon_get_fe, + .get_tune_settings = poseidon_fe_get_tune_settings, + + .read_status = poseidon_read_status, + .read_ber = poseidon_read_ber, + .read_signal_strength = poseidon_read_signal_strength, + .read_snr = poseidon_read_snr, + .read_ucblocks = poseidon_read_unc_blocks, + + .ts_bus_ctrl = poseidon_ts_bus_ctrl, +}; + +static void dvb_urb_irq(struct urb *urb) +{ + struct pd_dvb_adapter *pd_dvb = urb->context; + int len = urb->transfer_buffer_length; + struct dvb_demux *demux = &pd_dvb->demux; + s32 ret; + + if (!pd_dvb->is_streaming || urb->status) { + if (urb->status == -EPROTO) + goto resend; + return; + } + + if (urb->actual_length == len) + dvb_dmx_swfilter(demux, urb->transfer_buffer, len); + else if (urb->actual_length == len - 4) { + int offset; + u8 *buf = urb->transfer_buffer; + + /* + * The packet size is 512, + * last packet contains 456 bytes tsp data + */ + for (offset = 456; offset < len; offset += 512) { + if (!strncmp(buf + offset, "DVHS", 4)) { + dvb_dmx_swfilter(demux, buf, offset); + if (len > offset + 52 + 4) { + /*16 bytes trailer + 36 bytes padding */ + buf += offset + 52; + len -= offset + 52 + 4; + dvb_dmx_swfilter(demux, buf, len); + } + break; + } + } + } + +resend: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + log(" usb_submit_urb failed: error %d", ret); +} + +static int dvb_urb_init(struct pd_dvb_adapter *pd_dvb) +{ + if (pd_dvb->urb_array[0]) + return 0; + + alloc_bulk_urbs_generic(pd_dvb->urb_array, DVB_SBUF_NUM, + pd_dvb->pd_device->udev, pd_dvb->ep_addr, + DVB_URB_BUF_SIZE, GFP_KERNEL, + dvb_urb_irq, pd_dvb); + return 0; +} + +static void dvb_urb_cleanup(struct pd_dvb_adapter *pd_dvb) +{ + free_all_urb_generic(pd_dvb->urb_array, DVB_SBUF_NUM); +} + +static s32 dvb_start_streaming(struct pd_dvb_adapter *pd_dvb) +{ + struct poseidon *pd = pd_dvb->pd_device; + int ret = 0; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + mutex_lock(&pd->lock); + if (!pd_dvb->is_streaming) { + s32 i, cmd_status = 0; + /* + * Once upon a time, there was a difficult bug lying here. + * ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); + */ + + ret = send_set_req(pd, PLAY_SERVICE, 1, &cmd_status); + if (ret | cmd_status) + goto out; + + ret = dvb_urb_init(pd_dvb); + if (ret < 0) + goto out; + + pd_dvb->is_streaming = 1; + for (i = 0; i < DVB_SBUF_NUM; i++) { + ret = usb_submit_urb(pd_dvb->urb_array[i], + GFP_KERNEL); + if (ret) { + log(" submit urb error %d", ret); + goto out; + } + } + } +out: + mutex_unlock(&pd->lock); + return ret; +} + +void dvb_stop_streaming(struct pd_dvb_adapter *pd_dvb) +{ + struct poseidon *pd = pd_dvb->pd_device; + + mutex_lock(&pd->lock); + if (pd_dvb->is_streaming) { + s32 i, ret, cmd_status = 0; + + pd_dvb->is_streaming = 0; + + for (i = 0; i < DVB_SBUF_NUM; i++) + if (pd_dvb->urb_array[i]) + usb_kill_urb(pd_dvb->urb_array[i]); + + ret = send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, + &cmd_status); + if (ret | cmd_status) + log("error"); + } + mutex_unlock(&pd->lock); +} + +static int pd_start_feed(struct dvb_demux_feed *feed) +{ + struct pd_dvb_adapter *pd_dvb = feed->demux->priv; + int ret = 0; + + if (!pd_dvb) + return -1; + if (atomic_inc_return(&pd_dvb->active_feed) == 1) + ret = dvb_start_streaming(pd_dvb); + return ret; +} + +static int pd_stop_feed(struct dvb_demux_feed *feed) +{ + struct pd_dvb_adapter *pd_dvb = feed->demux->priv; + + if (!pd_dvb) + return -1; + if (atomic_dec_and_test(&pd_dvb->active_feed)) + dvb_stop_streaming(pd_dvb); + return 0; +} + +DVB_DEFINE_MOD_OPT_ADAPTER_NR(adapter_nr); +int pd_dvb_usb_device_init(struct poseidon *pd) +{ + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + struct dvb_demux *dvbdemux; + int ret = 0; + + pd_dvb->ep_addr = 0x82; + atomic_set(&pd_dvb->users, 0); + atomic_set(&pd_dvb->active_feed, 0); + pd_dvb->pd_device = pd; + + ret = dvb_register_adapter(&pd_dvb->dvb_adap, + "Poseidon dvbt adapter", + THIS_MODULE, + NULL /* for hibernation correctly*/, + adapter_nr); + if (ret < 0) + goto error1; + + /* register frontend */ + pd_dvb->dvb_fe.demodulator_priv = pd; + memcpy(&pd_dvb->dvb_fe.ops, &poseidon_frontend_ops, + sizeof(struct dvb_frontend_ops)); + ret = dvb_register_frontend(&pd_dvb->dvb_adap, &pd_dvb->dvb_fe); + if (ret < 0) + goto error2; + + /* register demux device */ + dvbdemux = &pd_dvb->demux; + dvbdemux->dmx.capabilities = DMX_TS_FILTERING | DMX_SECTION_FILTERING; + dvbdemux->priv = pd_dvb; + dvbdemux->feednum = dvbdemux->filternum = 64; + dvbdemux->start_feed = pd_start_feed; + dvbdemux->stop_feed = pd_stop_feed; + dvbdemux->write_to_decoder = NULL; + + ret = dvb_dmx_init(dvbdemux); + if (ret < 0) + goto error3; + + pd_dvb->dmxdev.filternum = pd_dvb->demux.filternum; + pd_dvb->dmxdev.demux = &pd_dvb->demux.dmx; + pd_dvb->dmxdev.capabilities = 0; + + ret = dvb_dmxdev_init(&pd_dvb->dmxdev, &pd_dvb->dvb_adap); + if (ret < 0) + goto error3; + return 0; + +error3: + dvb_unregister_frontend(&pd_dvb->dvb_fe); +error2: + dvb_unregister_adapter(&pd_dvb->dvb_adap); +error1: + return ret; +} + +void pd_dvb_usb_device_exit(struct poseidon *pd) +{ + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + + while (atomic_read(&pd_dvb->users) != 0 + || atomic_read(&pd_dvb->active_feed) != 0) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + } + dvb_dmxdev_release(&pd_dvb->dmxdev); + dvb_unregister_frontend(&pd_dvb->dvb_fe); + dvb_unregister_adapter(&pd_dvb->dvb_adap); + pd_dvb_usb_device_cleanup(pd); +} + +void pd_dvb_usb_device_cleanup(struct poseidon *pd) +{ + struct pd_dvb_adapter *pd_dvb = &pd->dvb_data; + + dvb_urb_cleanup(pd_dvb); +} + +int pd_dvb_get_adapter_num(struct pd_dvb_adapter *pd_dvb) +{ + return pd_dvb->dvb_adap.num; +} diff --git a/drivers/media/video/tlg2300/pd-main.c b/drivers/media/video/tlg2300/pd-main.c new file mode 100644 index 000000000000..6df93803e3a8 --- /dev/null +++ b/drivers/media/video/tlg2300/pd-main.c @@ -0,0 +1,566 @@ +/* + * device driver for Telegent tlg2300 based TV cards + * + * Author : + * Kang Yong + * Zhang Xiaobing + * Huang Shijie or + * + * (c) 2009 Telegent Systems + * (c) 2010 Telegent Systems + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "vendorcmds.h" +#include "pd-common.h" + +#define VENDOR_ID 0x1B24 +#define PRODUCT_ID 0x4001 +static struct usb_device_id id_table[] = { + { USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 0) }, + { USB_DEVICE_AND_INTERFACE_INFO(VENDOR_ID, PRODUCT_ID, 255, 1, 1) }, + { }, +}; +MODULE_DEVICE_TABLE(usb, id_table); + +int debug_mode; +module_param(debug_mode, int, 0644); +MODULE_PARM_DESC(debug_mode, "0 = disable, 1 = enable, 2 = verbose"); + +const char *firmware_name = "tlg2300_firmware.bin"; +struct usb_driver poseidon_driver; +static LIST_HEAD(pd_device_list); + +/* + * send set request to USB firmware. + */ +s32 send_set_req(struct poseidon *pd, u8 cmdid, s32 param, s32 *cmd_status) +{ + s32 ret; + s8 data[32] = {}; + u16 lower_16, upper_16; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + mdelay(30); + + if (param == 0) { + upper_16 = lower_16 = 0; + } else { + /* send 32 bit param as two 16 bit param,little endian */ + lower_16 = (unsigned short)(param & 0xffff); + upper_16 = (unsigned short)((param >> 16) & 0xffff); + } + ret = usb_control_msg(pd->udev, + usb_rcvctrlpipe(pd->udev, 0), + REQ_SET_CMD | cmdid, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + lower_16, + upper_16, + &data, + sizeof(*cmd_status), + USB_CTRL_GET_TIMEOUT); + + if (!ret) { + return -ENXIO; + } else { + /* 1st 4 bytes into cmd_status */ + memcpy((char *)cmd_status, &(data[0]), sizeof(*cmd_status)); + } + return 0; +} + +/* + * send get request to Poseidon firmware. + */ +s32 send_get_req(struct poseidon *pd, u8 cmdid, s32 param, + void *buf, s32 *cmd_status, s32 datalen) +{ + s32 ret; + s8 data[128] = {}; + u16 lower_16, upper_16; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + mdelay(30); + if (param == 0) { + upper_16 = lower_16 = 0; + } else { + /*send 32 bit param as two 16 bit param, little endian */ + lower_16 = (unsigned short)(param & 0xffff); + upper_16 = (unsigned short)((param >> 16) & 0xffff); + } + ret = usb_control_msg(pd->udev, + usb_rcvctrlpipe(pd->udev, 0), + REQ_GET_CMD | cmdid, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + lower_16, + upper_16, + &data, + (datalen + sizeof(*cmd_status)), + USB_CTRL_GET_TIMEOUT); + + if (ret < 0) { + return -ENXIO; + } else { + /* 1st 4 bytes into cmd_status, remaining data into cmd_data */ + memcpy((char *)cmd_status, &data[0], sizeof(*cmd_status)); + memcpy((char *)buf, &data[sizeof(*cmd_status)], datalen); + } + return 0; +} + +static int pm_notifier_block(struct notifier_block *nb, + unsigned long event, void *dummy) +{ + struct poseidon *pd = NULL; + struct list_head *node, *next; + + switch (event) { + case PM_POST_HIBERNATION: + list_for_each_safe(node, next, &pd_device_list) { + struct usb_device *udev; + struct usb_interface *iface; + int rc = 0; + + pd = container_of(node, struct poseidon, device_list); + udev = pd->udev; + iface = pd->interface; + + /* It will cause the system to reload the firmware */ + rc = usb_lock_device_for_reset(udev, iface); + if (rc >= 0) { + usb_reset_device(udev); + usb_unlock_device(udev); + } + } + break; + default: + break; + } + log("event :%ld\n", event); + return 0; +} + +static struct notifier_block pm_notifer = { + .notifier_call = pm_notifier_block, +}; + +int set_tuner_mode(struct poseidon *pd, unsigned char mode) +{ + s32 ret, cmd_status; + + if (pd->state & POSEIDON_STATE_DISCONNECT) + return -ENODEV; + + ret = send_set_req(pd, TUNE_MODE_SELECT, mode, &cmd_status); + if (ret || cmd_status) + return -ENXIO; + return 0; +} + +enum tlg__analog_audio_standard get_audio_std(s32 mode, s32 country_code) +{ + s32 nicam[] = {27, 32, 33, 34, 36, 44, 45, 46, 47, 48, 64, + 65, 86, 351, 352, 353, 354, 358, 372, 852, 972}; + s32 btsc[] = {1, 52, 54, 55, 886}; + s32 eiaj[] = {81}; + s32 i; + + if (mode == TLG_MODE_FM_RADIO) { + if (country_code == 1) + return TLG_TUNE_ASTD_FM_US; + else + return TLG_TUNE_ASTD_FM_EUR; + } else if (mode == TLG_MODE_ANALOG_TV_UNCOMP) { + for (i = 0; i < sizeof(nicam) / sizeof(s32); i++) { + if (country_code == nicam[i]) + return TLG_TUNE_ASTD_NICAM; + } + + for (i = 0; i < sizeof(btsc) / sizeof(s32); i++) { + if (country_code == btsc[i]) + return TLG_TUNE_ASTD_BTSC; + } + + for (i = 0; i < sizeof(eiaj) / sizeof(s32); i++) { + if (country_code == eiaj[i]) + return TLG_TUNE_ASTD_EIAJ; + } + + return TLG_TUNE_ASTD_A2; + } else { + return TLG_TUNE_ASTD_NONE; + } +} + +void poseidon_delete(struct kref *kref) +{ + struct poseidon *pd = container_of(kref, struct poseidon, kref); + + if (!pd) + return; + list_del_init(&pd->device_list); + + pd_dvb_usb_device_cleanup(pd); + /* clean_audio_data(&pd->audio_data);*/ + + if (pd->udev) { + usb_put_dev(pd->udev); + pd->udev = NULL; + } + if (pd->interface) { + usb_put_intf(pd->interface); + pd->interface = NULL; + } + kfree(pd); + log(); +} + +static int firmware_download(struct usb_device *udev) +{ + int ret = 0, actual_length; + const struct firmware *fw = NULL; + void *fwbuf = NULL; + size_t fwlength = 0, offset; + size_t max_packet_size; + + ret = request_firmware(&fw, firmware_name, &udev->dev); + if (ret) { + log("download err : %d", ret); + return ret; + } + + fwlength = fw->size; + + fwbuf = kzalloc(fwlength, GFP_KERNEL); + if (!fwbuf) { + ret = -ENOMEM; + goto out; + } + memcpy(fwbuf, fw->data, fwlength); + + max_packet_size = udev->ep_out[0x1]->desc.wMaxPacketSize; + log("\t\t download size : %d", (int)max_packet_size); + + for (offset = 0; offset < fwlength; offset += max_packet_size) { + actual_length = 0; + ret = usb_bulk_msg(udev, + usb_sndbulkpipe(udev, 0x01), /* ep 1 */ + fwbuf + offset, + min(max_packet_size, fwlength - offset), + &actual_length, + HZ * 10); + if (ret) + break; + } + kfree(fwbuf); +out: + release_firmware(fw); + return ret; +} + +#ifdef CONFIG_PM +/* one-to-one map : poseidon{} <----> usb_device{}'s port */ +static inline void set_map_flags(struct poseidon *pd, struct usb_device *udev) +{ + pd->portnum = udev->portnum; +} + +static inline int get_autopm_ref(struct poseidon *pd) +{ + return pd->video_data.users + pd->vbi_data.users + pd->audio.users + + atomic_read(&pd->dvb_data.users) + pd->radio_data.users; +} + +/* fixup something for poseidon */ +static inline struct poseidon *fixup(struct poseidon *pd) +{ + int count; + + /* old udev and interface have gone, so put back reference . */ + count = get_autopm_ref(pd); + log("count : %d, ref count : %d", count, get_pm_count(pd)); + while (count--) + usb_autopm_put_interface(pd->interface); + /*usb_autopm_set_interface(pd->interface); */ + + usb_put_dev(pd->udev); + usb_put_intf(pd->interface); + log("event : %d\n", pd->msg.event); + return pd; +} + +static struct poseidon *find_old_poseidon(struct usb_device *udev) +{ + struct poseidon *pd; + + list_for_each_entry(pd, &pd_device_list, device_list) { + if (pd->portnum == udev->portnum && in_hibernation(pd)) + return fixup(pd); + } + return NULL; +} + +/* Is the card working now ? */ +static inline int is_working(struct poseidon *pd) +{ + return get_pm_count(pd) > 0; +} + +static inline struct poseidon *get_pd(struct usb_interface *intf) +{ + return usb_get_intfdata(intf); +} + +static int poseidon_suspend(struct usb_interface *intf, pm_message_t msg) +{ + struct poseidon *pd = get_pd(intf); + + if (!pd) + return 0; + if (!is_working(pd)) { + if (get_pm_count(pd) <= 0 && !in_hibernation(pd)) { + pd->msg.event = PM_EVENT_AUTO_SUSPEND; + pd->pm_resume = NULL; /* a good guard */ + printk(KERN_DEBUG "\n\t+ TLG2300 auto suspend +\n\n"); + } + return 0; + } + pd->msg = msg; /* save it here */ + logpm(pd); + return pd->pm_suspend ? pd->pm_suspend(pd) : 0; +} + +static int poseidon_resume(struct usb_interface *intf) +{ + struct poseidon *pd = get_pd(intf); + + if (!pd) + return 0; + printk(KERN_DEBUG "\n\t ++ TLG2300 resume ++\n\n"); + + if (!is_working(pd)) { + if (PM_EVENT_AUTO_SUSPEND == pd->msg.event) + pd->msg = PMSG_ON; + return 0; + } + if (in_hibernation(pd)) { + logpm(pd); + return 0; + } + logpm(pd); + return pd->pm_resume ? pd->pm_resume(pd) : 0; +} + +static void hibernation_resume(struct work_struct *w) +{ + struct poseidon *pd = container_of(w, struct poseidon, pm_work); + int count; + + pd->msg.event = 0; /* clear it here */ + pd->state &= ~POSEIDON_STATE_DISCONNECT; + + /* set the new interface's reference */ + count = get_autopm_ref(pd); + while (count--) + usb_autopm_get_interface(pd->interface); + + /* resume the context */ + logpm(pd); + if (pd->pm_resume) + pd->pm_resume(pd); +} +#endif + +static bool check_firmware(struct usb_device *udev, int *down_firmware) +{ + void *buf; + int ret; + struct cmd_firmware_vers_s *cmd_firm; + + buf = kzalloc(sizeof(*cmd_firm) + sizeof(u32), GFP_KERNEL); + if (!buf) + return -ENOMEM; + ret = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + REQ_GET_CMD | GET_FW_ID, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0, + 0, + buf, + sizeof(*cmd_firm) + sizeof(u32), + USB_CTRL_GET_TIMEOUT); + kfree(buf); + + if (ret < 0) { + *down_firmware = 1; + return firmware_download(udev); + } + return ret; +} + +static int poseidon_probe(struct usb_interface *interface, + const struct usb_device_id *id) +{ + struct usb_device *udev = interface_to_usbdev(interface); + struct poseidon *pd = NULL; + int ret = 0; + int new_one = 0; + + /* download firmware */ + check_firmware(udev, &ret); + if (ret) + return 0; + + /* Do I recovery from the hibernate ? */ + pd = find_old_poseidon(udev); + if (!pd) { + pd = kzalloc(sizeof(*pd), GFP_KERNEL); + if (!pd) + return -ENOMEM; + kref_init(&pd->kref); + set_map_flags(pd, udev); + new_one = 1; + } + + pd->udev = usb_get_dev(udev); + pd->interface = usb_get_intf(interface); + usb_set_intfdata(interface, pd); + + if (new_one) { + struct device *dev = &interface->dev; + + logpm(pd); + pd->country_code = 86; + mutex_init(&pd->lock); + + /* register v4l2 device */ + snprintf(pd->v4l2_dev.name, sizeof(pd->v4l2_dev.name), "%s %s", + dev->driver->name, dev_name(dev)); + ret = v4l2_device_register(NULL, &pd->v4l2_dev); + + /* register devices in directory /dev */ + ret = pd_video_init(pd); + poseidon_audio_init(pd); + poseidon_fm_init(pd); + pd_dvb_usb_device_init(pd); + + INIT_LIST_HEAD(&pd->device_list); + list_add_tail(&pd->device_list, &pd_device_list); + } + + device_init_wakeup(&udev->dev, 1); +#ifdef CONFIG_PM + pd->udev->autosuspend_disabled = 0; + pd->udev->autosuspend_delay = HZ * PM_SUSPEND_DELAY; + + if (in_hibernation(pd)) { + INIT_WORK(&pd->pm_work, hibernation_resume); + schedule_work(&pd->pm_work); + } +#endif + return 0; +} + +static void poseidon_disconnect(struct usb_interface *interface) +{ + struct poseidon *pd = get_pd(interface); + + if (!pd) + return; + logpm(pd); + if (in_hibernation(pd)) + return; + + mutex_lock(&pd->lock); + pd->state |= POSEIDON_STATE_DISCONNECT; + mutex_unlock(&pd->lock); + + /* stop urb transferring */ + stop_all_video_stream(pd); + dvb_stop_streaming(&pd->dvb_data); + + /*unregister v4l2 device */ + v4l2_device_unregister(&pd->v4l2_dev); + + lock_kernel(); + { + pd_dvb_usb_device_exit(pd); + poseidon_fm_exit(pd); + + poseidon_audio_free(pd); + pd_video_exit(pd); + } + unlock_kernel(); + + usb_set_intfdata(interface, NULL); + kref_put(&pd->kref, poseidon_delete); +} + +struct usb_driver poseidon_driver = { + .name = "poseidon", + .probe = poseidon_probe, + .disconnect = poseidon_disconnect, + .id_table = id_table, +#ifdef CONFIG_PM + .suspend = poseidon_suspend, + .resume = poseidon_resume, +#endif + .supports_autosuspend = 1, +}; + +static int __init poseidon_init(void) +{ + int ret; + + ret = usb_register(&poseidon_driver); + if (ret) + return ret; + register_pm_notifier(&pm_notifer); + return ret; +} + +static void __exit poseidon_exit(void) +{ + log(); + unregister_pm_notifier(&pm_notifer); + usb_deregister(&poseidon_driver); +} + +module_init(poseidon_init); +module_exit(poseidon_exit); + +MODULE_AUTHOR("Telegent Systems"); +MODULE_DESCRIPTION("For tlg2300-based USB device "); +MODULE_LICENSE("GPL"); diff --git a/drivers/media/video/tlg2300/pd-radio.c b/drivers/media/video/tlg2300/pd-radio.c new file mode 100644 index 000000000000..bdbb0c11b3a9 --- /dev/null +++ b/drivers/media/video/tlg2300/pd-radio.c @@ -0,0 +1,351 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "pd-common.h" +#include "vendorcmds.h" + +static int set_frequency(struct poseidon *p, __u32 frequency); +static int poseidon_fm_close(struct file *filp); +static int poseidon_fm_open(struct file *filp); + +#define TUNER_FREQ_MIN_FM 76000000 +#define TUNER_FREQ_MAX_FM 108000000 + +static int poseidon_check_mode_radio(struct poseidon *p) +{ + int ret, radiomode; + u32 status; + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/2); + ret = usb_set_interface(p->udev, 0, BULK_ALTERNATE_IFACE); + if (ret < 0) + goto out; + + ret = set_tuner_mode(p, TLG_MODE_FM_RADIO); + if (ret != 0) + goto out; + + ret = send_set_req(p, SGNL_SRC_SEL, TLG_SIG_SRC_ANTENNA, &status); + radiomode = get_audio_std(TLG_MODE_FM_RADIO, p->country_code); + ret = send_set_req(p, TUNER_AUD_ANA_STD, radiomode, &status); + ret |= send_set_req(p, TUNER_AUD_MODE, + TLG_TUNE_TVAUDIO_MODE_STEREO, &status); + ret |= send_set_req(p, AUDIO_SAMPLE_RATE_SEL, + ATV_AUDIO_RATE_48K, &status); + ret |= send_set_req(p, TUNE_FREQ_SELECT, TUNER_FREQ_MIN_FM, &status); +out: + return ret; +} + +#ifdef CONFIG_PM +static int pm_fm_suspend(struct poseidon *p) +{ + logpm(p); + pm_alsa_suspend(p); + usb_set_interface(p->udev, 0, 0); + msleep(300); + return 0; +} + +static int pm_fm_resume(struct poseidon *p) +{ + logpm(p); + poseidon_check_mode_radio(p); + set_frequency(p, p->radio_data.fm_freq); + pm_alsa_resume(p); + return 0; +} +#endif + +static int poseidon_fm_open(struct file *filp) +{ + struct video_device *vfd = video_devdata(filp); + struct poseidon *p = video_get_drvdata(vfd); + int ret = 0; + + if (!p) + return -1; + + mutex_lock(&p->lock); + if (p->state & POSEIDON_STATE_DISCONNECT) { + ret = -ENODEV; + goto out; + } + + if (p->state && !(p->state & POSEIDON_STATE_FM)) { + ret = -EBUSY; + goto out; + } + + usb_autopm_get_interface(p->interface); + if (0 == p->state) { + p->country_code = country_code; + set_debug_mode(vfd, debug_mode); + + ret = poseidon_check_mode_radio(p); + if (ret < 0) { + usb_autopm_put_interface(p->interface); + goto out; + } + p->state |= POSEIDON_STATE_FM; + } + p->radio_data.users++; + kref_get(&p->kref); + filp->private_data = p; +out: + mutex_unlock(&p->lock); + return ret; +} + +static int poseidon_fm_close(struct file *filp) +{ + struct poseidon *p = filp->private_data; + struct radio_data *fm = &p->radio_data; + uint32_t status; + + mutex_lock(&p->lock); + fm->users--; + if (0 == fm->users) + p->state &= ~POSEIDON_STATE_FM; + + if (fm->is_radio_streaming && filp == p->file_for_stream) { + fm->is_radio_streaming = 0; + send_set_req(p, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, &status); + } + usb_autopm_put_interface(p->interface); + mutex_unlock(&p->lock); + + kref_put(&p->kref, poseidon_delete); + filp->private_data = NULL; + return 0; +} + +static int vidioc_querycap(struct file *file, void *priv, + struct v4l2_capability *v) +{ + struct poseidon *p = file->private_data; + + strlcpy(v->driver, "tele-radio", sizeof(v->driver)); + strlcpy(v->card, "Telegent Poseidon", sizeof(v->card)); + usb_make_path(p->udev, v->bus_info, sizeof(v->bus_info)); + v->version = KERNEL_VERSION(0, 0, 1); + v->capabilities = V4L2_CAP_TUNER | V4L2_CAP_RADIO; + return 0; +} + +static const struct v4l2_file_operations poseidon_fm_fops = { + .owner = THIS_MODULE, + .open = poseidon_fm_open, + .release = poseidon_fm_close, + .ioctl = video_ioctl2, +}; + +int tlg_fm_vidioc_g_tuner(struct file *file, void *priv, struct v4l2_tuner *vt) +{ + struct tuner_fm_sig_stat_s fm_stat = {}; + int ret, status, count = 5; + struct poseidon *p = file->private_data; + + if (vt->index != 0) + return -EINVAL; + + vt->type = V4L2_TUNER_RADIO; + vt->capability = V4L2_TUNER_CAP_STEREO; + vt->rangelow = TUNER_FREQ_MIN_FM / 62500; + vt->rangehigh = TUNER_FREQ_MAX_FM / 62500; + vt->rxsubchans = V4L2_TUNER_SUB_STEREO; + vt->audmode = V4L2_TUNER_MODE_STEREO; + vt->signal = 0; + vt->afc = 0; + + mutex_lock(&p->lock); + ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, + &fm_stat, &status, sizeof(fm_stat)); + + while (fm_stat.sig_lock_busy && count-- && !ret) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + + ret = send_get_req(p, TUNER_STATUS, TLG_MODE_FM_RADIO, + &fm_stat, &status, sizeof(fm_stat)); + } + mutex_unlock(&p->lock); + + if (ret || status) { + vt->signal = 0; + } else if ((fm_stat.sig_present || fm_stat.sig_locked) + && fm_stat.sig_strength == 0) { + vt->signal = 0xffff; + } else + vt->signal = (fm_stat.sig_strength * 255 / 10) << 8; + + return 0; +} + +int fm_get_freq(struct file *file, void *priv, struct v4l2_frequency *argp) +{ + struct poseidon *p = file->private_data; + + argp->frequency = p->radio_data.fm_freq; + return 0; +} + +static int set_frequency(struct poseidon *p, __u32 frequency) +{ + __u32 freq ; + int ret, status, radiomode; + + mutex_lock(&p->lock); + + radiomode = get_audio_std(TLG_MODE_FM_RADIO, p->country_code); + /*NTSC 8,PAL 2 */ + ret = send_set_req(p, TUNER_AUD_ANA_STD, radiomode, &status); + + freq = (frequency * 125) * 500 / 1000;/* kHZ */ + if (freq < TUNER_FREQ_MIN_FM/1000 || freq > TUNER_FREQ_MAX_FM/1000) { + ret = -EINVAL; + goto error; + } + + ret = send_set_req(p, TUNE_FREQ_SELECT, freq, &status); + if (ret < 0) + goto error ; + ret = send_set_req(p, TAKE_REQUEST, 0, &status); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/4); + if (!p->radio_data.is_radio_streaming) { + ret = send_set_req(p, TAKE_REQUEST, 0, &status); + ret = send_set_req(p, PLAY_SERVICE, + TLG_TUNE_PLAY_SVC_START, &status); + p->radio_data.is_radio_streaming = 1; + } + p->radio_data.fm_freq = frequency; +error: + mutex_unlock(&p->lock); + return ret; +} + +int fm_set_freq(struct file *file, void *priv, struct v4l2_frequency *argp) +{ + struct poseidon *p = file->private_data; + + p->file_for_stream = file; +#ifdef CONFIG_PM + p->pm_suspend = pm_fm_suspend; + p->pm_resume = pm_fm_resume; +#endif + return set_frequency(p, argp->frequency); +} + +int tlg_fm_vidioc_g_ctrl(struct file *file, void *priv, + struct v4l2_control *arg) +{ + return 0; +} + +int tlg_fm_vidioc_exts_ctrl(struct file *file, void *fh, + struct v4l2_ext_controls *a) +{ + return 0; +} + +int tlg_fm_vidioc_s_ctrl(struct file *file, void *priv, + struct v4l2_control *arg) +{ + return 0; +} + +int tlg_fm_vidioc_queryctrl(struct file *file, void *priv, + struct v4l2_queryctrl *arg) +{ + arg->minimum = 0; + arg->maximum = 65535; + return 0; +} + +static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *vt) +{ + return vt->index > 0 ? -EINVAL : 0; +} +static int vidioc_s_audio(struct file *file, void *priv, struct v4l2_audio *va) +{ + return (va->index != 0) ? -EINVAL : 0; +} + +static int vidioc_g_audio(struct file *file, void *priv, struct v4l2_audio *a) +{ + a->index = 0; + a->mode = 0; + a->capability = V4L2_AUDCAP_STEREO; + strcpy(a->name, "Radio"); + return 0; +} + +static int vidioc_s_input(struct file *filp, void *priv, u32 i) +{ + return (i != 0) ? -EINVAL : 0; +} + +static int vidioc_g_input(struct file *filp, void *priv, u32 *i) +{ + return (*i != 0) ? -EINVAL : 0; +} + +static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + .vidioc_g_audio = vidioc_g_audio, + .vidioc_s_audio = vidioc_s_audio, + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_queryctrl = tlg_fm_vidioc_queryctrl, + .vidioc_g_ctrl = tlg_fm_vidioc_g_ctrl, + .vidioc_s_ctrl = tlg_fm_vidioc_s_ctrl, + .vidioc_s_ext_ctrls = tlg_fm_vidioc_exts_ctrl, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_g_tuner = tlg_fm_vidioc_g_tuner, + .vidioc_g_frequency = fm_get_freq, + .vidioc_s_frequency = fm_set_freq, +}; + +static struct video_device poseidon_fm_template = { + .name = "Telegent-Radio", + .fops = &poseidon_fm_fops, + .minor = -1, + .release = video_device_release, + .ioctl_ops = &poseidon_fm_ioctl_ops, +}; + +int poseidon_fm_init(struct poseidon *p) +{ + struct video_device *fm_dev; + + fm_dev = vdev_init(p, &poseidon_fm_template); + if (fm_dev == NULL) + return -1; + + if (video_register_device(fm_dev, VFL_TYPE_RADIO, -1) < 0) { + video_device_release(fm_dev); + return -1; + } + p->radio_data.fm_dev = fm_dev; + return 0; +} + +int poseidon_fm_exit(struct poseidon *p) +{ + destroy_video_device(&p->radio_data.fm_dev); + return 0; +} diff --git a/drivers/media/video/tlg2300/pd-video.c b/drivers/media/video/tlg2300/pd-video.c new file mode 100644 index 000000000000..5f0300ac465c --- /dev/null +++ b/drivers/media/video/tlg2300/pd-video.c @@ -0,0 +1,1648 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "pd-common.h" +#include "vendorcmds.h" + +static int pm_video_suspend(struct poseidon *pd); +static int pm_video_resume(struct poseidon *pd); +static void iso_bubble_handler(struct work_struct *w); + +int country_code = 86; +module_param(country_code, int, 0644); +MODULE_PARM_DESC(country_code, "country code (e.g China is 86)"); + +int usb_transfer_mode; +module_param(usb_transfer_mode, int, 0644); +MODULE_PARM_DESC(usb_transfer_mode, "0 = Bulk, 1 = Isochronous"); + +static const struct poseidon_format poseidon_formats[] = { + { "YUV 422", V4L2_PIX_FMT_YUYV, 16, 0}, + { "RGB565", V4L2_PIX_FMT_RGB565, 16, 0}, +}; + +static const struct poseidon_tvnorm poseidon_tvnorms[] = { + { V4L2_STD_PAL_D, "PAL-D", TLG_TUNE_VSTD_PAL_D }, + { V4L2_STD_PAL_B, "PAL-B", TLG_TUNE_VSTD_PAL_B }, + { V4L2_STD_PAL_G, "PAL-G", TLG_TUNE_VSTD_PAL_G }, + { V4L2_STD_PAL_H, "PAL-H", TLG_TUNE_VSTD_PAL_H }, + { V4L2_STD_PAL_I, "PAL-I", TLG_TUNE_VSTD_PAL_I }, + { V4L2_STD_PAL_M, "PAL-M", TLG_TUNE_VSTD_PAL_M }, + { V4L2_STD_PAL_N, "PAL-N", TLG_TUNE_VSTD_PAL_N_COMBO }, + { V4L2_STD_PAL_Nc, "PAL-Nc", TLG_TUNE_VSTD_PAL_N_COMBO }, + { V4L2_STD_NTSC_M, "NTSC-M", TLG_TUNE_VSTD_NTSC_M }, + { V4L2_STD_NTSC_M_JP, "NTSC-JP", TLG_TUNE_VSTD_NTSC_M_J }, + { V4L2_STD_SECAM_B, "SECAM-B", TLG_TUNE_VSTD_SECAM_B }, + { V4L2_STD_SECAM_D, "SECAM-D", TLG_TUNE_VSTD_SECAM_D }, + { V4L2_STD_SECAM_G, "SECAM-G", TLG_TUNE_VSTD_SECAM_G }, + { V4L2_STD_SECAM_H, "SECAM-H", TLG_TUNE_VSTD_SECAM_H }, + { V4L2_STD_SECAM_K, "SECAM-K", TLG_TUNE_VSTD_SECAM_K }, + { V4L2_STD_SECAM_K1, "SECAM-K1", TLG_TUNE_VSTD_SECAM_K1 }, + { V4L2_STD_SECAM_L, "SECAM-L", TLG_TUNE_VSTD_SECAM_L }, + { V4L2_STD_SECAM_LC, "SECAM-LC", TLG_TUNE_VSTD_SECAM_L1 }, +}; +static const unsigned int POSEIDON_TVNORMS = ARRAY_SIZE(poseidon_tvnorms); + +struct pd_audio_mode { + u32 tlg_audio_mode; + u32 v4l2_audio_sub; + u32 v4l2_audio_mode; +}; + +static const struct pd_audio_mode pd_audio_modes[] = { + { TLG_TUNE_TVAUDIO_MODE_MONO, V4L2_TUNER_SUB_MONO, + V4L2_TUNER_MODE_MONO }, + { TLG_TUNE_TVAUDIO_MODE_STEREO, V4L2_TUNER_SUB_STEREO, + V4L2_TUNER_MODE_STEREO }, + { TLG_TUNE_TVAUDIO_MODE_LANG_A, V4L2_TUNER_SUB_LANG1, + V4L2_TUNER_MODE_LANG1 }, + { TLG_TUNE_TVAUDIO_MODE_LANG_B, V4L2_TUNER_SUB_LANG2, + V4L2_TUNER_MODE_LANG2 }, + { TLG_TUNE_TVAUDIO_MODE_LANG_C, V4L2_TUNER_SUB_LANG1, + V4L2_TUNER_MODE_LANG1_LANG2 } +}; +static const unsigned int POSEIDON_AUDIOMODS = ARRAY_SIZE(pd_audio_modes); + +struct pd_input { + char *name; + uint32_t tlg_src; +}; + +static const struct pd_input pd_inputs[] = { + { "TV Antenna", TLG_SIG_SRC_ANTENNA }, + { "TV Cable", TLG_SIG_SRC_CABLE }, + { "TV SVideo", TLG_SIG_SRC_SVIDEO }, + { "TV Composite", TLG_SIG_SRC_COMPOSITE } +}; +static const unsigned int POSEIDON_INPUTS = ARRAY_SIZE(pd_inputs); + +struct poseidon_control { + struct v4l2_queryctrl v4l2_ctrl; + enum cmd_custom_param_id vc_id; +}; + +static struct poseidon_control controls[] = { + { + { V4L2_CID_BRIGHTNESS, V4L2_CTRL_TYPE_INTEGER, + "brightness", 0, 10000, 1, 100, 0, }, + CUST_PARM_ID_BRIGHTNESS_CTRL + }, + + { + { V4L2_CID_CONTRAST, V4L2_CTRL_TYPE_INTEGER, + "contrast", 0, 10000, 1, 100, 0, }, + CUST_PARM_ID_CONTRAST_CTRL, + }, + + { + { V4L2_CID_HUE, V4L2_CTRL_TYPE_INTEGER, + "hue", 0, 10000, 1, 100, 0, }, + CUST_PARM_ID_HUE_CTRL, + }, + + { + { V4L2_CID_SATURATION, V4L2_CTRL_TYPE_INTEGER, + "saturation", 0, 10000, 1, 100, 0, }, + CUST_PARM_ID_SATURATION_CTRL, + }, +}; + +static int vidioc_querycap(struct file *file, void *fh, + struct v4l2_capability *cap) +{ + struct front_face *front = fh; + struct poseidon *p = front->pd; + + logs(front); + + strcpy(cap->driver, "tele-video"); + strcpy(cap->card, "Telegent Poseidon"); + usb_make_path(p->udev, cap->bus_info, sizeof(cap->bus_info)); + cap->version = KERNEL_VERSION(0, 0, 1); + cap->capabilities = V4L2_CAP_VIDEO_CAPTURE | V4L2_CAP_TUNER | + V4L2_CAP_AUDIO | V4L2_CAP_STREAMING | + V4L2_CAP_READWRITE | V4L2_CAP_VBI_CAPTURE; + return 0; +} + +/*====================================================================*/ +static void init_copy(struct video_data *video, bool index) +{ + struct front_face *front = video->front; + + video->field_count = index; + video->lines_copied = 0; + video->prev_left = 0 ; + video->dst = (char *)videobuf_to_vmalloc(front->curr_frame) + + index * video->lines_size; + video->vbi->copied = 0; /* set it here */ +} + +static bool get_frame(struct front_face *front, int *need_init) +{ + struct videobuf_buffer *vb = front->curr_frame; + + if (vb) + return true; + + spin_lock(&front->queue_lock); + if (!list_empty(&front->active)) { + vb = list_entry(front->active.next, + struct videobuf_buffer, queue); + if (need_init) + *need_init = 1; + front->curr_frame = vb; + list_del_init(&vb->queue); + } + spin_unlock(&front->queue_lock); + + return !!vb; +} + +/* check if the video's buffer is ready */ +static bool get_video_frame(struct front_face *front, struct video_data *video) +{ + int need_init = 0; + bool ret = true; + + ret = get_frame(front, &need_init); + if (ret && need_init) + init_copy(video, 0); + return ret; +} + +static void submit_frame(struct front_face *front) +{ + struct videobuf_buffer *vb = front->curr_frame; + + if (vb == NULL) + return; + + front->curr_frame = NULL; + vb->state = VIDEOBUF_DONE; + vb->field_count++; + do_gettimeofday(&vb->ts); + + wake_up(&vb->done); +} + +/* + * A frame is composed of two fields. If we receive all the two fields, + * call the submit_frame() to submit the whole frame to applications. + */ +static void end_field(struct video_data *video) +{ + /* logs(video->front); */ + if (1 == video->field_count) + submit_frame(video->front); + else + init_copy(video, 1); +} + +static void copy_video_data(struct video_data *video, char *src, + unsigned int count) +{ +#define copy_data(len) \ + do { \ + if (++video->lines_copied > video->lines_per_field) \ + goto overflow; \ + memcpy(video->dst, src, len);\ + video->dst += len + video->lines_size; \ + src += len; \ + count -= len; \ + } while (0) + + while (count && count >= video->lines_size) { + if (video->prev_left) { + copy_data(video->prev_left); + video->prev_left = 0; + continue; + } + copy_data(video->lines_size); + } + if (count && count < video->lines_size) { + memcpy(video->dst, src, count); + + video->prev_left = video->lines_size - count; + video->dst += count; + } + return; + +overflow: + end_field(video); +} + +static void check_trailer(struct video_data *video, char *src, int count) +{ + struct vbi_data *vbi = video->vbi; + int offset; /* trailer's offset */ + char *buf; + + offset = (video->context.pix.sizeimage / 2 + vbi->vbi_size / 2) + - (vbi->copied + video->lines_size * video->lines_copied); + if (video->prev_left) + offset -= (video->lines_size - video->prev_left); + + if (offset > count || offset <= 0) + goto short_package; + + buf = src + offset; + + /* trailer : (VFHS) + U32 + U32 + field_num */ + if (!strncmp(buf, "VFHS", 4)) { + int field_num = *((u32 *)(buf + 12)); + + if ((field_num & 1) ^ video->field_count) { + init_copy(video, video->field_count); + return; + } + copy_video_data(video, src, offset); + } +short_package: + end_field(video); +} + +/* ========== Check this more carefully! =========== */ +static inline void copy_vbi_data(struct vbi_data *vbi, + char *src, unsigned int count) +{ + struct front_face *front = vbi->front; + + if (front && get_frame(front, NULL)) { + char *buf = videobuf_to_vmalloc(front->curr_frame); + + if (vbi->video->field_count) + buf += (vbi->vbi_size / 2); + memcpy(buf + vbi->copied, src, count); + } + vbi->copied += count; +} + +/* + * Copy the normal data (VBI or VIDEO) without the trailer. + * VBI is not interlaced, while VIDEO is interlaced. + */ +static inline void copy_vbi_video_data(struct video_data *video, + char *src, unsigned int count) +{ + struct vbi_data *vbi = video->vbi; + unsigned int vbi_delta = (vbi->vbi_size / 2) - vbi->copied; + + if (vbi_delta >= count) { + copy_vbi_data(vbi, src, count); + } else { + if (vbi_delta) { + copy_vbi_data(vbi, src, vbi_delta); + + /* we receive the two fields of the VBI*/ + if (vbi->front && video->field_count) + submit_frame(vbi->front); + } + copy_video_data(video, src + vbi_delta, count - vbi_delta); + } +} + +static void urb_complete_bulk(struct urb *urb) +{ + struct front_face *front = urb->context; + struct video_data *video = &front->pd->video_data; + char *src = (char *)urb->transfer_buffer; + int count = urb->actual_length; + int ret = 0; + + if (!video->is_streaming || urb->status) { + if (urb->status == -EPROTO) + goto resend_it; + return; + } + if (!get_video_frame(front, video)) + goto resend_it; + + if (count == urb->transfer_buffer_length) + copy_vbi_video_data(video, src, count); + else + check_trailer(video, src, count); + +resend_it: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + log(" submit failed: error %d", ret); +} + +/************************* for ISO *********************/ +#define GET_SUCCESS (0) +#define GET_TRAILER (1) +#define GET_TOO_MUCH_BUBBLE (2) +#define GET_NONE (3) +static int get_chunk(int start, struct urb *urb, + int *head, int *tail, int *bubble_err) +{ + struct usb_iso_packet_descriptor *pkt = NULL; + int ret = GET_SUCCESS; + + for (*head = *tail = -1; start < urb->number_of_packets; start++) { + pkt = &urb->iso_frame_desc[start]; + + /* handle the bubble of the Hub */ + if (-EOVERFLOW == pkt->status) { + if (++*bubble_err > urb->number_of_packets / 3) + return GET_TOO_MUCH_BUBBLE; + continue; + } + + /* This is the gap */ + if (pkt->status || pkt->actual_length <= 0 + || pkt->actual_length > ISO_PKT_SIZE) { + if (*head != -1) + break; + continue; + } + + /* a good isochronous packet */ + if (pkt->actual_length == ISO_PKT_SIZE) { + if (*head == -1) + *head = start; + *tail = start; + continue; + } + + /* trailer is here */ + if (pkt->actual_length < ISO_PKT_SIZE) { + if (*head == -1) { + *head = start; + *tail = start; + return GET_TRAILER; + } + break; + } + } + + if (*head == -1 && *tail == -1) + ret = GET_NONE; + return ret; +} + +/* + * |__|------|___|-----|_______| + * ^ ^ + * | | + * gap gap + */ +static void urb_complete_iso(struct urb *urb) +{ + struct front_face *front = urb->context; + struct video_data *video = &front->pd->video_data; + int bubble_err = 0, head = 0, tail = 0; + char *src = (char *)urb->transfer_buffer; + int ret = 0; + + if (!video->is_streaming) + return; + + do { + if (!get_video_frame(front, video)) + goto out; + + switch (get_chunk(head, urb, &head, &tail, &bubble_err)) { + case GET_SUCCESS: + copy_vbi_video_data(video, src + (head * ISO_PKT_SIZE), + (tail - head + 1) * ISO_PKT_SIZE); + break; + case GET_TRAILER: + check_trailer(video, src + (head * ISO_PKT_SIZE), + ISO_PKT_SIZE); + break; + case GET_NONE: + goto out; + case GET_TOO_MUCH_BUBBLE: + log("\t We got too much bubble"); + schedule_work(&video->bubble_work); + return; + } + } while (head = tail + 1, head < urb->number_of_packets); + +out: + ret = usb_submit_urb(urb, GFP_ATOMIC); + if (ret) + log("usb_submit_urb err : %d", ret); +} +/*============================= [ end ] =====================*/ + +static int prepare_iso_urb(struct video_data *video) +{ + struct usb_device *udev = video->pd->udev; + int i; + + if (video->urb_array[0]) + return 0; + + for (i = 0; i < SBUF_NUM; i++) { + struct urb *urb; + void *mem; + int j; + + urb = usb_alloc_urb(PK_PER_URB, GFP_KERNEL); + if (urb == NULL) + goto out; + + video->urb_array[i] = urb; + mem = usb_buffer_alloc(udev, + ISO_PKT_SIZE * PK_PER_URB, + GFP_KERNEL, + &urb->transfer_dma); + + urb->complete = urb_complete_iso; /* handler */ + urb->dev = udev; + urb->context = video->front; + urb->pipe = usb_rcvisocpipe(udev, + video->endpoint_addr); + urb->interval = 1; + urb->transfer_flags = URB_ISO_ASAP | URB_NO_TRANSFER_DMA_MAP; + urb->number_of_packets = PK_PER_URB; + urb->transfer_buffer = mem; + urb->transfer_buffer_length = PK_PER_URB * ISO_PKT_SIZE; + + for (j = 0; j < PK_PER_URB; j++) { + urb->iso_frame_desc[j].offset = ISO_PKT_SIZE * j; + urb->iso_frame_desc[j].length = ISO_PKT_SIZE; + } + } + return 0; +out: + for (; i > 0; i--) + ; + return -ENOMEM; +} + +/* return the succeeded number of the allocation */ +int alloc_bulk_urbs_generic(struct urb **urb_array, int num, + struct usb_device *udev, u8 ep_addr, + int buf_size, gfp_t gfp_flags, + usb_complete_t complete_fn, void *context) +{ + struct urb *urb; + void *mem; + int i; + + for (i = 0; i < num; i++) { + urb = usb_alloc_urb(0, gfp_flags); + if (urb == NULL) + return i; + + mem = usb_buffer_alloc(udev, buf_size, gfp_flags, + &urb->transfer_dma); + if (mem == NULL) + return i; + + usb_fill_bulk_urb(urb, udev, usb_rcvbulkpipe(udev, ep_addr), + mem, buf_size, complete_fn, context); + urb->transfer_flags |= URB_NO_TRANSFER_DMA_MAP; + urb_array[i] = urb; + } + return i; +} + +void free_all_urb_generic(struct urb **urb_array, int num) +{ + int i; + struct urb *urb; + + for (i = 0; i < num; i++) { + urb = urb_array[i]; + if (urb) { + usb_buffer_free(urb->dev, + urb->transfer_buffer_length, + urb->transfer_buffer, + urb->transfer_dma); + usb_free_urb(urb); + urb_array[i] = NULL; + } + } +} + +static int prepare_bulk_urb(struct video_data *video) +{ + if (video->urb_array[0]) + return 0; + + alloc_bulk_urbs_generic(video->urb_array, SBUF_NUM, + video->pd->udev, video->endpoint_addr, + 0x2000, GFP_KERNEL, + urb_complete_bulk, video->front); + return 0; +} + +/* free the URBs */ +static void free_all_urb(struct video_data *video) +{ + free_all_urb_generic(video->urb_array, SBUF_NUM); +} + +static void pd_buf_release(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + videobuf_vmalloc_free(vb); + vb->state = VIDEOBUF_NEEDS_INIT; +} + +static void pd_buf_queue(struct videobuf_queue *q, struct videobuf_buffer *vb) +{ + struct front_face *front = q->priv_data; + vb->state = VIDEOBUF_QUEUED; + list_add_tail(&vb->queue, &front->active); +} + +static int pd_buf_prepare(struct videobuf_queue *q, struct videobuf_buffer *vb, + enum v4l2_field field) +{ + struct front_face *front = q->priv_data; + int rc; + + switch (front->type) { + case V4L2_BUF_TYPE_VIDEO_CAPTURE: + if (VIDEOBUF_NEEDS_INIT == vb->state) { + struct v4l2_pix_format *pix; + + pix = &front->pd->video_data.context.pix; + vb->size = pix->sizeimage; /* real frame size */ + vb->width = pix->width; + vb->height = pix->height; + rc = videobuf_iolock(q, vb, NULL); + if (rc < 0) + return rc; + } + break; + case V4L2_BUF_TYPE_VBI_CAPTURE: + if (VIDEOBUF_NEEDS_INIT == vb->state) { + vb->size = front->pd->vbi_data.vbi_size; + rc = videobuf_iolock(q, vb, NULL); + if (rc < 0) + return rc; + } + break; + default: + return -EINVAL; + } + vb->field = field; + vb->state = VIDEOBUF_PREPARED; + return 0; +} + +int fire_all_urb(struct video_data *video) +{ + int i, ret; + + video->is_streaming = 1; + + for (i = 0; i < SBUF_NUM; i++) { + ret = usb_submit_urb(video->urb_array[i], GFP_KERNEL); + if (ret) + log("(%d) failed: error %d", i, ret); + } + return ret; +} + +static int start_video_stream(struct poseidon *pd) +{ + struct video_data *video = &pd->video_data; + s32 cmd_status; + + send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); + send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_START, &cmd_status); + + if (pd->cur_transfer_mode) { + prepare_iso_urb(video); + INIT_WORK(&video->bubble_work, iso_bubble_handler); + } else { + /* The bulk mode does not need a bubble handler */ + prepare_bulk_urb(video); + } + fire_all_urb(video); + return 0; +} + +static int pd_buf_setup(struct videobuf_queue *q, unsigned int *count, + unsigned int *size) +{ + struct front_face *front = q->priv_data; + struct poseidon *pd = front->pd; + + switch (front->type) { + default: + return -EINVAL; + case V4L2_BUF_TYPE_VIDEO_CAPTURE: { + struct video_data *video = &pd->video_data; + struct v4l2_pix_format *pix = &video->context.pix; + + *size = PAGE_ALIGN(pix->sizeimage);/* page aligned frame size */ + if (*count < 4) + *count = 4; + if (1) { + /* same in different altersetting */ + video->endpoint_addr = 0x82; + video->vbi = &pd->vbi_data; + video->vbi->video = video; + video->pd = pd; + video->lines_per_field = pix->height / 2; + video->lines_size = pix->width * 2; + video->front = front; + } + return start_video_stream(pd); + } + + case V4L2_BUF_TYPE_VBI_CAPTURE: { + struct vbi_data *vbi = &pd->vbi_data; + + *size = PAGE_ALIGN(vbi->vbi_size); + log("size : %d", *size); + if (*count == 0) + *count = 4; + } + break; + } + return 0; +} + +static struct videobuf_queue_ops pd_video_qops = { + .buf_setup = pd_buf_setup, + .buf_prepare = pd_buf_prepare, + .buf_queue = pd_buf_queue, + .buf_release = pd_buf_release, +}; + +static int vidioc_enum_fmt(struct file *file, void *fh, + struct v4l2_fmtdesc *f) +{ + if (ARRAY_SIZE(poseidon_formats) <= f->index) + return -EINVAL; + f->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + f->flags = 0; + f->pixelformat = poseidon_formats[f->index].fourcc; + strcpy(f->description, poseidon_formats[f->index].name); + return 0; +} + +static int vidioc_g_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + + logs(front); + f->fmt.pix = pd->video_data.context.pix; + return 0; +} + +static int vidioc_try_fmt(struct file *file, void *fh, + struct v4l2_format *f) +{ + return 0; +} + +/* + * VLC calls VIDIOC_S_STD before VIDIOC_S_FMT, while + * Mplayer calls them in the reverse order. + */ +static int pd_vidioc_s_fmt(struct poseidon *pd, struct v4l2_pix_format *pix) +{ + struct video_data *video = &pd->video_data; + struct running_context *context = &video->context; + struct v4l2_pix_format *pix_def = &context->pix; + s32 ret = 0, cmd_status = 0, vid_resol; + + /* set the pixel format to firmware */ + if (pix->pixelformat == V4L2_PIX_FMT_RGB565) { + vid_resol = TLG_TUNER_VID_FORMAT_RGB_565; + } else { + pix->pixelformat = V4L2_PIX_FMT_YUYV; + vid_resol = TLG_TUNER_VID_FORMAT_YUV; + } + ret = send_set_req(pd, VIDEO_STREAM_FMT_SEL, + vid_resol, &cmd_status); + + /* set the resolution to firmware */ + vid_resol = TLG_TUNE_VID_RES_720; + switch (pix->width) { + case 704: + vid_resol = TLG_TUNE_VID_RES_704; + break; + default: + pix->width = 720; + case 720: + break; + } + ret |= send_set_req(pd, VIDEO_ROSOLU_SEL, + vid_resol, &cmd_status); + if (ret || cmd_status) { + mutex_unlock(&pd->lock); + return -EBUSY; + } + + pix_def->pixelformat = pix->pixelformat; /* save it */ + pix->height = (context->tvnormid & V4L2_STD_525_60) ? 480 : 576; + + /* Compare with the default setting */ + if ((pix_def->width != pix->width) + || (pix_def->height != pix->height)) { + pix_def->width = pix->width; + pix_def->height = pix->height; + pix_def->bytesperline = pix->width * 2; + pix_def->sizeimage = pix->width * pix->height * 2; + } + *pix = *pix_def; + + return 0; +} + +static int vidioc_s_fmt(struct file *file, void *fh, struct v4l2_format *f) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + + logs(front); + /* stop VBI here */ + if (V4L2_BUF_TYPE_VIDEO_CAPTURE != f->type) + return -EINVAL; + + mutex_lock(&pd->lock); + if (pd->file_for_stream == NULL) + pd->file_for_stream = file; + else if (file != pd->file_for_stream) { + mutex_unlock(&pd->lock); + return -EINVAL; + } + + pd_vidioc_s_fmt(pd, &f->fmt.pix); + mutex_unlock(&pd->lock); + return 0; +} + +static int vidioc_g_fmt_vbi(struct file *file, void *fh, + struct v4l2_format *v4l2_f) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + struct v4l2_vbi_format *vbi_fmt = &v4l2_f->fmt.vbi; + + vbi_fmt->samples_per_line = 720 * 2; + vbi_fmt->sampling_rate = 6750000 * 4; + vbi_fmt->sample_format = V4L2_PIX_FMT_GREY; + vbi_fmt->offset = 64 * 4; /*FIXME: why offset */ + if (pd->video_data.context.tvnormid & V4L2_STD_525_60) { + vbi_fmt->start[0] = 10; + vbi_fmt->start[1] = 264; + vbi_fmt->count[0] = V4L_NTSC_VBI_LINES; + vbi_fmt->count[1] = V4L_NTSC_VBI_LINES; + } else { + vbi_fmt->start[0] = 6; + vbi_fmt->start[1] = 314; + vbi_fmt->count[0] = V4L_PAL_VBI_LINES; + vbi_fmt->count[1] = V4L_PAL_VBI_LINES; + } + vbi_fmt->flags = V4L2_VBI_UNSYNC; + logs(front); + return 0; +} + +static int set_std(struct poseidon *pd, v4l2_std_id *norm) +{ + struct video_data *video = &pd->video_data; + struct vbi_data *vbi = &pd->vbi_data; + struct running_context *context; + struct v4l2_pix_format *pix; + s32 i, ret = 0, cmd_status, param; + int height; + + for (i = 0; i < POSEIDON_TVNORMS; i++) { + if (*norm & poseidon_tvnorms[i].v4l2_id) { + param = poseidon_tvnorms[i].tlg_tvnorm; + log("name : %s", poseidon_tvnorms[i].name); + goto found; + } + } + return -EINVAL; +found: + mutex_lock(&pd->lock); + ret = send_set_req(pd, VIDEO_STD_SEL, param, &cmd_status); + if (ret || cmd_status) + goto out; + + /* Set vbi size and check the height of the frame */ + context = &video->context; + context->tvnormid = poseidon_tvnorms[i].v4l2_id; + if (context->tvnormid & V4L2_STD_525_60) { + vbi->vbi_size = V4L_NTSC_VBI_FRAMESIZE; + height = 480; + } else { + vbi->vbi_size = V4L_PAL_VBI_FRAMESIZE; + height = 576; + } + + pix = &context->pix; + if (pix->height != height) { + pix->height = height; + pix->sizeimage = pix->width * pix->height * 2; + } + +out: + mutex_unlock(&pd->lock); + return ret; +} + +int vidioc_s_std(struct file *file, void *fh, v4l2_std_id *norm) +{ + struct front_face *front = fh; + logs(front); + return set_std(front->pd, norm); +} + +static int vidioc_enum_input(struct file *file, void *fh, struct v4l2_input *in) +{ + struct front_face *front = fh; + + if (in->index < 0 || in->index >= POSEIDON_INPUTS) + return -EINVAL; + strcpy(in->name, pd_inputs[in->index].name); + in->type = V4L2_INPUT_TYPE_TUNER; + + /* + * the audio input index mixed with this video input, + * Poseidon only have one audio/video, set to "0" + */ + in->audioset = 0; + in->tuner = 0; + in->std = V4L2_STD_ALL; + in->status = 0; + logs(front); + return 0; +} + +static int vidioc_g_input(struct file *file, void *fh, unsigned int *i) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + struct running_context *context = &pd->video_data.context; + + logs(front); + *i = context->sig_index; + return 0; +} + +/* We can support several inputs */ +static int vidioc_s_input(struct file *file, void *fh, unsigned int i) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + s32 ret, cmd_status; + + if (i < 0 || i >= POSEIDON_INPUTS) + return -EINVAL; + ret = send_set_req(pd, SGNL_SRC_SEL, + pd_inputs[i].tlg_src, &cmd_status); + if (ret) + return ret; + + pd->video_data.context.sig_index = i; + return 0; +} + +static struct poseidon_control *check_control_id(__u32 id) +{ + struct poseidon_control *control = &controls[0]; + int array_size = ARRAY_SIZE(controls); + + for (; control < &controls[array_size]; control++) + if (control->v4l2_ctrl.id == id) + return control; + return NULL; +} + +static int vidioc_queryctrl(struct file *file, void *fh, + struct v4l2_queryctrl *a) +{ + struct poseidon_control *control = NULL; + + control = check_control_id(a->id); + if (!control) + return -EINVAL; + + *a = control->v4l2_ctrl; + return 0; +} + +static int vidioc_g_ctrl(struct file *file, void *fh, struct v4l2_control *ctrl) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + struct poseidon_control *control = NULL; + struct tuner_custom_parameter_s tuner_param; + s32 ret = 0, cmd_status; + + control = check_control_id(ctrl->id); + if (!control) + return -EINVAL; + + mutex_lock(&pd->lock); + ret = send_get_req(pd, TUNER_CUSTOM_PARAMETER, control->vc_id, + &tuner_param, &cmd_status, sizeof(tuner_param)); + mutex_unlock(&pd->lock); + + if (ret || cmd_status) + return -1; + + ctrl->value = tuner_param.param_value; + return 0; +} + +static int vidioc_s_ctrl(struct file *file, void *fh, struct v4l2_control *a) +{ + struct tuner_custom_parameter_s param = {0}; + struct poseidon_control *control = NULL; + struct front_face *front = fh; + struct poseidon *pd = front->pd; + s32 ret = 0, cmd_status, params; + + control = check_control_id(a->id); + if (!control) + return -EINVAL; + + param.param_value = a->value; + param.param_id = control->vc_id; + params = *(s32 *)¶m; /* temp code */ + + mutex_lock(&pd->lock); + ret = send_set_req(pd, TUNER_CUSTOM_PARAMETER, params, &cmd_status); + ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); + mutex_unlock(&pd->lock); + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/4); + return ret; +} + +/* Audio ioctls */ +static int vidioc_enumaudio(struct file *file, void *fh, struct v4l2_audio *a) +{ + if (0 != a->index) + return -EINVAL; + a->capability = V4L2_AUDCAP_STEREO; + strcpy(a->name, "USB audio in"); + /*Poseidon have no AVL function.*/ + a->mode = 0; + return 0; +} + +int vidioc_g_audio(struct file *file, void *fh, struct v4l2_audio *a) +{ + a->index = 0; + a->capability = V4L2_AUDCAP_STEREO; + strcpy(a->name, "USB audio in"); + a->mode = 0; + return 0; +} + +int vidioc_s_audio(struct file *file, void *fh, struct v4l2_audio *a) +{ + return (0 == a->index) ? 0 : -EINVAL; +} + +/* Tuner ioctls */ +static int vidioc_g_tuner(struct file *file, void *fh, struct v4l2_tuner *tuner) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + struct tuner_atv_sig_stat_s atv_stat; + s32 count = 5, ret, cmd_status; + int index; + + if (0 != tuner->index) + return -EINVAL; + + mutex_lock(&pd->lock); + ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV, + &atv_stat, &cmd_status, sizeof(atv_stat)); + + while (atv_stat.sig_lock_busy && count-- && !ret) { + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ); + + ret = send_get_req(pd, TUNER_STATUS, TLG_MODE_ANALOG_TV, + &atv_stat, &cmd_status, sizeof(atv_stat)); + } + mutex_unlock(&pd->lock); + + if (debug_mode) + log("P:%d,S:%d", atv_stat.sig_present, atv_stat.sig_strength); + + if (ret || cmd_status) + tuner->signal = 0; + else if (atv_stat.sig_present && !atv_stat.sig_strength) + tuner->signal = 0xFFFF; + else + tuner->signal = (atv_stat.sig_strength * 255 / 10) << 8; + + strcpy(tuner->name, "Telegent Systems"); + tuner->type = V4L2_TUNER_ANALOG_TV; + tuner->rangelow = TUNER_FREQ_MIN / 62500; + tuner->rangehigh = TUNER_FREQ_MAX / 62500; + tuner->capability = V4L2_TUNER_CAP_NORM | V4L2_TUNER_CAP_STEREO | + V4L2_TUNER_CAP_LANG1 | V4L2_TUNER_CAP_LANG2; + index = pd->video_data.context.audio_idx; + tuner->rxsubchans = pd_audio_modes[index].v4l2_audio_sub; + tuner->audmode = pd_audio_modes[index].v4l2_audio_mode; + tuner->afc = 0; + logs(front); + return 0; +} + +static int pd_vidioc_s_tuner(struct poseidon *pd, int index) +{ + s32 ret = 0, cmd_status, param, audiomode; + + mutex_lock(&pd->lock); + param = pd_audio_modes[index].tlg_audio_mode; + ret = send_set_req(pd, TUNER_AUD_MODE, param, &cmd_status); + audiomode = get_audio_std(TLG_MODE_ANALOG_TV, pd->country_code); + ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode, + &cmd_status); + if (!ret) + pd->video_data.context.audio_idx = index; + mutex_unlock(&pd->lock); + return ret; +} + +static int vidioc_s_tuner(struct file *file, void *fh, struct v4l2_tuner *a) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + int index; + + if (0 != a->index) + return -EINVAL; + logs(front); + for (index = 0; index < POSEIDON_AUDIOMODS; index++) + if (a->audmode == pd_audio_modes[index].v4l2_audio_mode) + return pd_vidioc_s_tuner(pd, index); + return -EINVAL; +} + +static int vidioc_g_frequency(struct file *file, void *fh, + struct v4l2_frequency *freq) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + struct running_context *context = &pd->video_data.context; + + if (0 != freq->tuner) + return -EINVAL; + freq->frequency = context->freq; + freq->type = V4L2_TUNER_ANALOG_TV; + return 0; +} + +static int set_frequency(struct poseidon *pd, __u32 frequency) +{ + s32 ret = 0, param, cmd_status; + struct running_context *context = &pd->video_data.context; + + param = frequency * 62500 / 1000; + if (param < TUNER_FREQ_MIN/1000 || param > TUNER_FREQ_MAX / 1000) + return -EINVAL; + + mutex_lock(&pd->lock); + ret = send_set_req(pd, TUNE_FREQ_SELECT, param, &cmd_status); + ret = send_set_req(pd, TAKE_REQUEST, 0, &cmd_status); + + msleep(250); /* wait for a while until the hardware is ready. */ + context->freq = frequency; + mutex_unlock(&pd->lock); + return ret; +} + +static int vidioc_s_frequency(struct file *file, void *fh, + struct v4l2_frequency *freq) +{ + struct front_face *front = fh; + struct poseidon *pd = front->pd; + + logs(front); +#ifdef CONFIG_PM + pd->pm_suspend = pm_video_suspend; + pd->pm_resume = pm_video_resume; +#endif + return set_frequency(pd, freq->frequency); +} + +static int vidioc_reqbufs(struct file *file, void *fh, + struct v4l2_requestbuffers *b) +{ + struct front_face *front = file->private_data; + logs(front); + return videobuf_reqbufs(&front->q, b); +} + +static int vidioc_querybuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct front_face *front = file->private_data; + logs(front); + return videobuf_querybuf(&front->q, b); +} + +static int vidioc_qbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct front_face *front = file->private_data; + return videobuf_qbuf(&front->q, b); +} + +static int vidioc_dqbuf(struct file *file, void *fh, struct v4l2_buffer *b) +{ + struct front_face *front = file->private_data; + return videobuf_dqbuf(&front->q, b, file->f_flags & O_NONBLOCK); +} + +/* Just stop the URBs, do not free the URBs */ +int usb_transfer_stop(struct video_data *video) +{ + if (video->is_streaming) { + int i; + s32 cmd_status; + struct poseidon *pd = video->pd; + + video->is_streaming = 0; + for (i = 0; i < SBUF_NUM; ++i) { + if (video->urb_array[i]) + usb_kill_urb(video->urb_array[i]); + } + + send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, + &cmd_status); + } + return 0; +} + +int stop_all_video_stream(struct poseidon *pd) +{ + struct video_data *video = &pd->video_data; + struct vbi_data *vbi = &pd->vbi_data; + + mutex_lock(&pd->lock); + if (video->is_streaming) { + struct front_face *front = video->front; + + /* stop the URBs */ + usb_transfer_stop(video); + free_all_urb(video); + + /* stop the host side of VIDEO */ + videobuf_stop(&front->q); + videobuf_mmap_free(&front->q); + + /* stop the host side of VBI */ + front = vbi->front; + if (front) { + videobuf_stop(&front->q); + videobuf_mmap_free(&front->q); + } + } + mutex_unlock(&pd->lock); + return 0; +} + +/* + * The bubbles can seriously damage the video's quality, + * though it occurs in very rare situation. + */ +static void iso_bubble_handler(struct work_struct *w) +{ + struct video_data *video; + struct poseidon *pd; + + video = container_of(w, struct video_data, bubble_work); + pd = video->pd; + + mutex_lock(&pd->lock); + usb_transfer_stop(video); + msleep(500); + start_video_stream(pd); + mutex_unlock(&pd->lock); +} + + +static int vidioc_streamon(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct front_face *front = fh; + + logs(front); + if (unlikely(type != front->type)) + return -EINVAL; + return videobuf_streamon(&front->q); +} + +static int vidioc_streamoff(struct file *file, void *fh, + enum v4l2_buf_type type) +{ + struct front_face *front = file->private_data; + + logs(front); + if (unlikely(type != front->type)) + return -EINVAL; + return videobuf_streamoff(&front->q); +} + +/* + * Set the firmware' default values : need altersetting and country code + */ +static int pd_video_checkmode(struct poseidon *pd) +{ + s32 ret = 0, cmd_status, audiomode; + + set_current_state(TASK_INTERRUPTIBLE); + schedule_timeout(HZ/2); + + /* choose the altersetting */ + ret = usb_set_interface(pd->udev, 0, + (pd->cur_transfer_mode ? + ISO_3K_BULK_ALTERNATE_IFACE : + BULK_ALTERNATE_IFACE)); + if (ret < 0) + goto error; + + /* set default parameters for PAL-D , with the VBI enabled*/ + ret = set_tuner_mode(pd, TLG_MODE_ANALOG_TV); + ret |= send_set_req(pd, SGNL_SRC_SEL, + TLG_SIG_SRC_ANTENNA, &cmd_status); + ret |= send_set_req(pd, VIDEO_STD_SEL, + TLG_TUNE_VSTD_PAL_D, &cmd_status); + ret |= send_set_req(pd, VIDEO_STREAM_FMT_SEL, + TLG_TUNER_VID_FORMAT_YUV, &cmd_status); + ret |= send_set_req(pd, VIDEO_ROSOLU_SEL, + TLG_TUNE_VID_RES_720, &cmd_status); + ret |= send_set_req(pd, TUNE_FREQ_SELECT, TUNER_FREQ_MIN, &cmd_status); + ret |= send_set_req(pd, VBI_DATA_SEL, 1, &cmd_status);/* enable vbi */ + + /* need country code to set the audio */ + audiomode = get_audio_std(TLG_MODE_ANALOG_TV, pd->country_code); + ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode, &cmd_status); + ret |= send_set_req(pd, TUNER_AUD_MODE, + TLG_TUNE_TVAUDIO_MODE_STEREO, &cmd_status); + ret |= send_set_req(pd, AUDIO_SAMPLE_RATE_SEL, + ATV_AUDIO_RATE_48K, &cmd_status); +error: + return ret; +} + +#ifdef CONFIG_PM +static int pm_video_suspend(struct poseidon *pd) +{ + /* stop audio */ + pm_alsa_suspend(pd); + + /* stop and free all the URBs */ + usb_transfer_stop(&pd->video_data); + free_all_urb(&pd->video_data); + + /* reset the interface */ + usb_set_interface(pd->udev, 0, 0); + msleep(300); + return 0; +} + +static int restore_v4l2_context(struct poseidon *pd, + struct running_context *context) +{ + struct front_face *front = pd->video_data.front; + + pd_video_checkmode(pd); + + set_std(pd, &context->tvnormid); + vidioc_s_input(NULL, front, context->sig_index); + pd_vidioc_s_tuner(pd, context->audio_idx); + pd_vidioc_s_fmt(pd, &context->pix); + set_frequency(pd, context->freq); + return 0; +} + +static int pm_video_resume(struct poseidon *pd) +{ + struct video_data *video = &pd->video_data; + + /* resume the video */ + /* [1] restore the origin V4L2 parameters */ + restore_v4l2_context(pd, &video->context); + + /* [2] initiate video copy variables */ + if (video->front->curr_frame) + init_copy(video, 0); + + /* [3] fire urbs */ + start_video_stream(pd); + + /* resume the audio */ + pm_alsa_resume(pd); + return 0; +} +#endif + +void set_debug_mode(struct video_device *vfd, int debug_mode) +{ + vfd->debug = 0; + if (debug_mode & 0x1) + vfd->debug = V4L2_DEBUG_IOCTL; + if (debug_mode & 0x2) + vfd->debug = V4L2_DEBUG_IOCTL | V4L2_DEBUG_IOCTL_ARG; +} + +static void init_video_context(struct running_context *context) +{ + context->sig_index = 0; + context->audio_idx = 1; /* stereo */ + context->tvnormid = V4L2_STD_PAL_D; + context->pix = (struct v4l2_pix_format) { + .width = 720, + .height = 576, + .pixelformat = V4L2_PIX_FMT_YUYV, + .field = V4L2_FIELD_INTERLACED, + .bytesperline = 720 * 2, + .sizeimage = 720 * 576 * 2, + .colorspace = V4L2_COLORSPACE_SMPTE170M, + .priv = 0 + }; +} + +static int pd_video_open(struct file *file) +{ + struct video_device *vfd = video_devdata(file); + struct poseidon *pd = video_get_drvdata(vfd); + struct front_face *front = NULL; + int ret = -ENOMEM; + + mutex_lock(&pd->lock); + usb_autopm_get_interface(pd->interface); + + if (vfd->vfl_type == VFL_TYPE_GRABBER + && !(pd->state & POSEIDON_STATE_ANALOG)) { + front = kzalloc(sizeof(struct front_face), GFP_KERNEL); + if (!front) + goto out; + + pd->cur_transfer_mode = usb_transfer_mode;/* bulk or iso */ + pd->country_code = country_code; + init_video_context(&pd->video_data.context); + + ret = pd_video_checkmode(pd); + if (ret < 0) { + kfree(front); + ret = -1; + goto out; + } + + pd->state |= POSEIDON_STATE_ANALOG; + front->type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + pd->video_data.users++; + set_debug_mode(vfd, debug_mode); + + videobuf_queue_vmalloc_init(&front->q, &pd_video_qops, + NULL, &front->queue_lock, + V4L2_BUF_TYPE_VIDEO_CAPTURE, + V4L2_FIELD_INTERLACED,/* video is interlacd */ + sizeof(struct videobuf_buffer),/*it's enough*/ + front); + } else if (vfd->vfl_type == VFL_TYPE_VBI + && !(pd->state & POSEIDON_STATE_VBI)) { + front = kzalloc(sizeof(struct front_face), GFP_KERNEL); + if (!front) + goto out; + + pd->state |= POSEIDON_STATE_VBI; + front->type = V4L2_BUF_TYPE_VBI_CAPTURE; + pd->vbi_data.front = front; + pd->vbi_data.users++; + + videobuf_queue_vmalloc_init(&front->q, &pd_video_qops, + NULL, &front->queue_lock, + V4L2_BUF_TYPE_VBI_CAPTURE, + V4L2_FIELD_NONE, /* vbi is NONE mode */ + sizeof(struct videobuf_buffer), + front); + } else { + /* maybe add FM support here */ + log("other "); + ret = -EINVAL; + goto out; + } + + front->pd = pd; + front->curr_frame = NULL; + INIT_LIST_HEAD(&front->active); + spin_lock_init(&front->queue_lock); + + file->private_data = front; + kref_get(&pd->kref); + + mutex_unlock(&pd->lock); + return 0; +out: + usb_autopm_put_interface(pd->interface); + mutex_unlock(&pd->lock); + return ret; +} + +static int pd_video_release(struct file *file) +{ + struct front_face *front = file->private_data; + struct poseidon *pd = front->pd; + s32 cmd_status = 0; + + logs(front); + mutex_lock(&pd->lock); + + if (front->type == V4L2_BUF_TYPE_VIDEO_CAPTURE) { + pd->state &= ~POSEIDON_STATE_ANALOG; + + /* stop the device, and free the URBs */ + usb_transfer_stop(&pd->video_data); + free_all_urb(&pd->video_data); + + /* stop the firmware */ + send_set_req(pd, PLAY_SERVICE, TLG_TUNE_PLAY_SVC_STOP, + &cmd_status); + + pd->file_for_stream = NULL; + pd->video_data.users--; + } else if (front->type == V4L2_BUF_TYPE_VBI_CAPTURE) { + pd->state &= ~POSEIDON_STATE_VBI; + pd->vbi_data.front = NULL; + pd->vbi_data.users--; + } + videobuf_stop(&front->q); + videobuf_mmap_free(&front->q); + + usb_autopm_put_interface(pd->interface); + mutex_unlock(&pd->lock); + + kfree(front); + file->private_data = NULL; + kref_put(&pd->kref, poseidon_delete); + return 0; +} + +static int pd_video_mmap(struct file *file, struct vm_area_struct *vma) +{ + struct front_face *front = file->private_data; + return videobuf_mmap_mapper(&front->q, vma); +} + +unsigned int pd_video_poll(struct file *file, poll_table *table) +{ + struct front_face *front = file->private_data; + return videobuf_poll_stream(file, &front->q, table); +} + +ssize_t pd_video_read(struct file *file, char __user *buffer, + size_t count, loff_t *ppos) +{ + struct front_face *front = file->private_data; + return videobuf_read_stream(&front->q, buffer, count, ppos, + 0, file->f_flags & O_NONBLOCK); +} + +/* This struct works for both VIDEO and VBI */ +static const struct v4l2_file_operations pd_video_fops = { + .owner = THIS_MODULE, + .open = pd_video_open, + .release = pd_video_release, + .read = pd_video_read, + .poll = pd_video_poll, + .mmap = pd_video_mmap, + .ioctl = video_ioctl2, /* maybe changed in future */ +}; + +static const struct v4l2_ioctl_ops pd_video_ioctl_ops = { + .vidioc_querycap = vidioc_querycap, + + /* Video format */ + .vidioc_g_fmt_vid_cap = vidioc_g_fmt, + .vidioc_enum_fmt_vid_cap = vidioc_enum_fmt, + .vidioc_s_fmt_vid_cap = vidioc_s_fmt, + .vidioc_g_fmt_vbi_cap = vidioc_g_fmt_vbi, /* VBI */ + .vidioc_try_fmt_vid_cap = vidioc_try_fmt, + + /* Input */ + .vidioc_g_input = vidioc_g_input, + .vidioc_s_input = vidioc_s_input, + .vidioc_enum_input = vidioc_enum_input, + + /* Audio ioctls */ + .vidioc_enumaudio = vidioc_enumaudio, + .vidioc_g_audio = vidioc_g_audio, + .vidioc_s_audio = vidioc_s_audio, + + /* Tuner ioctls */ + .vidioc_g_tuner = vidioc_g_tuner, + .vidioc_s_tuner = vidioc_s_tuner, + .vidioc_s_std = vidioc_s_std, + .vidioc_g_frequency = vidioc_g_frequency, + .vidioc_s_frequency = vidioc_s_frequency, + + /* Buffer handlers */ + .vidioc_reqbufs = vidioc_reqbufs, + .vidioc_querybuf = vidioc_querybuf, + .vidioc_qbuf = vidioc_qbuf, + .vidioc_dqbuf = vidioc_dqbuf, + + /* Stream on/off */ + .vidioc_streamon = vidioc_streamon, + .vidioc_streamoff = vidioc_streamoff, + + /* Control handling */ + .vidioc_queryctrl = vidioc_queryctrl, + .vidioc_g_ctrl = vidioc_g_ctrl, + .vidioc_s_ctrl = vidioc_s_ctrl, +}; + +static struct video_device pd_video_template = { + .name = "Telegent-Video", + .fops = &pd_video_fops, + .minor = -1, + .release = video_device_release, + .tvnorms = V4L2_STD_ALL, + .ioctl_ops = &pd_video_ioctl_ops, +}; + +struct video_device *vdev_init(struct poseidon *pd, struct video_device *tmp) +{ + struct video_device *vfd; + + vfd = video_device_alloc(); + if (vfd == NULL) + return NULL; + *vfd = *tmp; + vfd->minor = -1; + vfd->v4l2_dev = &pd->v4l2_dev; + /*vfd->parent = &(pd->udev->dev); */ + vfd->release = video_device_release; + video_set_drvdata(vfd, pd); + return vfd; +} + +void destroy_video_device(struct video_device **v_dev) +{ + struct video_device *dev = *v_dev; + + if (dev == NULL) + return; + + if (video_is_registered(dev)) + video_unregister_device(dev); + else + video_device_release(dev); + *v_dev = NULL; +} + +void pd_video_exit(struct poseidon *pd) +{ + struct video_data *video = &pd->video_data; + struct vbi_data *vbi = &pd->vbi_data; + + destroy_video_device(&video->v_dev); + destroy_video_device(&vbi->v_dev); + log(); +} + +int pd_video_init(struct poseidon *pd) +{ + struct video_data *video = &pd->video_data; + struct vbi_data *vbi = &pd->vbi_data; + int ret = -ENOMEM; + + video->v_dev = vdev_init(pd, &pd_video_template); + if (video->v_dev == NULL) + goto out; + + ret = video_register_device(video->v_dev, VFL_TYPE_GRABBER, -1); + if (ret != 0) + goto out; + + /* VBI uses the same template as video */ + vbi->v_dev = vdev_init(pd, &pd_video_template); + if (vbi->v_dev == NULL) { + ret = -ENOMEM; + goto out; + } + ret = video_register_device(vbi->v_dev, VFL_TYPE_VBI, -1); + if (ret != 0) + goto out; + log("register VIDEO/VBI devices"); + return 0; +out: + log("VIDEO/VBI devices register failed, : %d", ret); + pd_video_exit(pd); + return ret; +} + diff --git a/drivers/media/video/tlg2300/vendorcmds.h b/drivers/media/video/tlg2300/vendorcmds.h new file mode 100644 index 000000000000..ba6f4ae3b2c2 --- /dev/null +++ b/drivers/media/video/tlg2300/vendorcmds.h @@ -0,0 +1,243 @@ +#ifndef VENDOR_CMD_H_ +#define VENDOR_CMD_H_ + +#define BULK_ALTERNATE_IFACE (2) +#define ISO_3K_BULK_ALTERNATE_IFACE (1) +#define REQ_SET_CMD (0X00) +#define REQ_GET_CMD (0X80) + +enum tlg__analog_audio_standard { + TLG_TUNE_ASTD_NONE = 0x00000000, + TLG_TUNE_ASTD_A2 = 0x00000001, + TLG_TUNE_ASTD_NICAM = 0x00000002, + TLG_TUNE_ASTD_EIAJ = 0x00000004, + TLG_TUNE_ASTD_BTSC = 0x00000008, + TLG_TUNE_ASTD_FM_US = 0x00000010, + TLG_TUNE_ASTD_FM_EUR = 0x00000020, + TLG_TUNE_ASTD_ALL = 0x0000003f +}; + +/* + * identifiers for Custom Parameter messages. + * @typedef cmd_custom_param_id_t + */ +enum cmd_custom_param_id { + CUST_PARM_ID_NONE = 0x00, + CUST_PARM_ID_BRIGHTNESS_CTRL = 0x01, + CUST_PARM_ID_CONTRAST_CTRL = 0x02, + CUST_PARM_ID_HUE_CTRL = 0x03, + CUST_PARM_ID_SATURATION_CTRL = 0x04, + CUST_PARM_ID_AUDIO_SNR_THRESHOLD = 0x10, + CUST_PARM_ID_AUDIO_AGC_THRESHOLD = 0x11, + CUST_PARM_ID_MAX +}; + +struct tuner_custom_parameter_s { + uint16_t param_id; /* Parameter identifier */ + uint16_t param_value; /* Parameter value */ +}; + +struct tuner_ber_rate_s { + uint32_t ber_rate; /* BER sample rate in seconds */ +}; + +struct tuner_atv_sig_stat_s { + uint32_t sig_present; + uint32_t sig_locked; + uint32_t sig_lock_busy; + uint32_t sig_strength; /* milliDb */ + uint32_t tv_audio_chan; /* mono/stereo/sap*/ + uint32_t mvision_stat; /* macrovision status */ +}; + +struct tuner_dtv_sig_stat_s { + uint32_t sig_present; /* Boolean*/ + uint32_t sig_locked; /* Boolean */ + uint32_t sig_lock_busy; /* Boolean (Can this time-out?) */ + uint32_t sig_strength; /* milliDb*/ +}; + +struct tuner_fm_sig_stat_s { + uint32_t sig_present; /* Boolean*/ + uint32_t sig_locked; /* Boolean */ + uint32_t sig_lock_busy; /* Boolean */ + uint32_t sig_stereo_mono;/* TBD*/ + uint32_t sig_strength; /* milliDb*/ +}; + +enum _tag_tlg_tune_srv_cmd { + TLG_TUNE_PLAY_SVC_START = 1, + TLG_TUNE_PLAY_SVC_STOP +}; + +enum _tag_tune_atv_audio_mode_caps { + TLG_TUNE_TVAUDIO_MODE_MONO = 0x00000001, + TLG_TUNE_TVAUDIO_MODE_STEREO = 0x00000002, + TLG_TUNE_TVAUDIO_MODE_LANG_A = 0x00000010,/* Primary language*/ + TLG_TUNE_TVAUDIO_MODE_LANG_B = 0x00000020,/* 2nd avail language*/ + TLG_TUNE_TVAUDIO_MODE_LANG_C = 0x00000040 +}; + + +enum _tag_tuner_atv_audio_rates { + ATV_AUDIO_RATE_NONE = 0x00,/* Audio not supported*/ + ATV_AUDIO_RATE_32K = 0x01,/* Audio rate = 32 KHz*/ + ATV_AUDIO_RATE_48K = 0x02, /* Audio rate = 48 KHz*/ + ATV_AUDIO_RATE_31_25K = 0x04 /* Audio rate = 31.25KHz */ +}; + +enum _tag_tune_atv_vid_res_caps { + TLG_TUNE_VID_RES_NONE = 0x00000000, + TLG_TUNE_VID_RES_720 = 0x00000001, + TLG_TUNE_VID_RES_704 = 0x00000002, + TLG_TUNE_VID_RES_360 = 0x00000004 +}; + +enum _tag_tuner_analog_video_format { + TLG_TUNER_VID_FORMAT_YUV = 0x00000001, + TLG_TUNER_VID_FORMAT_YCRCB = 0x00000002, + TLG_TUNER_VID_FORMAT_RGB_565 = 0x00000004, +}; + +enum tlg_ext_audio_support { + TLG_EXT_AUDIO_NONE = 0x00,/* No external audio input supported */ + TLG_EXT_AUDIO_LR = 0x01/* LR external audio inputs supported*/ +}; + +enum { + TLG_MODE_NONE = 0x00, /* No Mode specified*/ + TLG_MODE_ANALOG_TV = 0x01, /* Analog Television mode*/ + TLG_MODE_ANALOG_TV_UNCOMP = 0x01, /* Analog Television mode*/ + TLG_MODE_ANALOG_TV_COMP = 0x02, /* Analog TV mode (compressed)*/ + TLG_MODE_FM_RADIO = 0x04, /* FM Radio mode*/ + TLG_MODE_DVB_T = 0x08, /* Digital TV (DVB-T)*/ +}; + +enum tlg_signal_sources_t { + TLG_SIG_SRC_NONE = 0x00,/* Signal source not specified */ + TLG_SIG_SRC_ANTENNA = 0x01,/* Signal src is: Antenna */ + TLG_SIG_SRC_CABLE = 0x02,/* Signal src is: Coax Cable*/ + TLG_SIG_SRC_SVIDEO = 0x04,/* Signal src is: S_VIDEO */ + TLG_SIG_SRC_COMPOSITE = 0x08 /* Signal src is: Composite Video */ +}; + +enum tuner_analog_video_standard { + TLG_TUNE_VSTD_NONE = 0x00000000, + TLG_TUNE_VSTD_NTSC_M = 0x00000001, + TLG_TUNE_VSTD_NTSC_M_J = 0x00000002,/* Japan */ + TLG_TUNE_VSTD_PAL_B = 0x00000010, + TLG_TUNE_VSTD_PAL_D = 0x00000020, + TLG_TUNE_VSTD_PAL_G = 0x00000040, + TLG_TUNE_VSTD_PAL_H = 0x00000080, + TLG_TUNE_VSTD_PAL_I = 0x00000100, + TLG_TUNE_VSTD_PAL_M = 0x00000200, + TLG_TUNE_VSTD_PAL_N = 0x00000400, + TLG_TUNE_VSTD_SECAM_B = 0x00001000, + TLG_TUNE_VSTD_SECAM_D = 0x00002000, + TLG_TUNE_VSTD_SECAM_G = 0x00004000, + TLG_TUNE_VSTD_SECAM_H = 0x00008000, + TLG_TUNE_VSTD_SECAM_K = 0x00010000, + TLG_TUNE_VSTD_SECAM_K1 = 0x00020000, + TLG_TUNE_VSTD_SECAM_L = 0x00040000, + TLG_TUNE_VSTD_SECAM_L1 = 0x00080000, + TLG_TUNE_VSTD_PAL_N_COMBO = 0x00100000 +}; + +enum tlg_mode_caps { + TLG_MODE_CAPS_NONE = 0x00, /* No Mode specified */ + TLG_MODE_CAPS_ANALOG_TV_UNCOMP = 0x01, /* Analog TV mode */ + TLG_MODE_CAPS_ANALOG_TV_COMP = 0x02, /* Analog TV (compressed)*/ + TLG_MODE_CAPS_FM_RADIO = 0x04, /* FM Radio mode */ + TLG_MODE_CAPS_DVB_T = 0x08, /* Digital TV (DVB-T) */ +}; + +enum poseidon_vendor_cmds { + LAST_CMD_STAT = 0x00, + GET_CHIP_ID = 0x01, + GET_FW_ID = 0x02, + PRODUCT_CAPS = 0x03, + + TUNE_MODE_CAP_ATV = 0x10, + TUNE_MODE_CAP_ATVCOMP = 0X10, + TUNE_MODE_CAP_DVBT = 0x10, + TUNE_MODE_CAP_FM = 0x10, + TUNE_MODE_SELECT = 0x11, + TUNE_FREQ_SELECT = 0x12, + SGNL_SRC_SEL = 0x13, + + VIDEO_STD_SEL = 0x14, + VIDEO_STREAM_FMT_SEL = 0x15, + VIDEO_ROSOLU_AVAIL = 0x16, + VIDEO_ROSOLU_SEL = 0x17, + VIDEO_CONT_PROTECT = 0x20, + + VCR_TIMING_MODSEL = 0x21, + EXT_AUDIO_CAP = 0x22, + EXT_AUDIO_SEL = 0x23, + TEST_PATTERN_SEL = 0x24, + VBI_DATA_SEL = 0x25, + AUDIO_SAMPLE_RATE_CAP = 0x28, + AUDIO_SAMPLE_RATE_SEL = 0x29, + TUNER_AUD_MODE = 0x2a, + TUNER_AUD_MODE_AVAIL = 0x2b, + TUNER_AUD_ANA_STD = 0x2c, + TUNER_CUSTOM_PARAMETER = 0x2f, + + DVBT_TUNE_MODE_SEL = 0x30, + DVBT_BANDW_CAP = 0x31, + DVBT_BANDW_SEL = 0x32, + DVBT_GUARD_INTERV_CAP = 0x33, + DVBT_GUARD_INTERV_SEL = 0x34, + DVBT_MODULATION_CAP = 0x35, + DVBT_MODULATION_SEL = 0x36, + DVBT_INNER_FEC_RATE_CAP = 0x37, + DVBT_INNER_FEC_RATE_SEL = 0x38, + DVBT_TRANS_MODE_CAP = 0x39, + DVBT_TRANS_MODE_SEL = 0x3a, + DVBT_SEARCH_RANG = 0x3c, + + TUNER_SETUP_ANALOG = 0x40, + TUNER_SETUP_DIGITAL = 0x41, + TUNER_SETUP_FM_RADIO = 0x42, + TAKE_REQUEST = 0x43, /* Take effect of the command */ + PLAY_SERVICE = 0x44, /* Play start or Play stop */ + TUNER_STATUS = 0x45, + TUNE_PROP_DVBT = 0x46, + ERR_RATE_STATS = 0x47, + TUNER_BER_RATE = 0x48, + + SCAN_CAPS = 0x50, + SCAN_SETUP = 0x51, + SCAN_SERVICE = 0x52, + SCAN_STATS = 0x53, + + PID_SET = 0x58, + PID_UNSET = 0x59, + PID_LIST = 0x5a, + + IRD_CAP = 0x60, + IRD_MODE_SEL = 0x61, + IRD_SETUP = 0x62, + + PTM_MODE_CAP = 0x70, + PTM_MODE_SEL = 0x71, + PTM_SERVICE = 0x72, + TUNER_REG_SCRIPT = 0x73, + CMD_CHIP_RST = 0x74, +}; + +enum tlg_bw { + TLG_BW_5 = 5, + TLG_BW_6 = 6, + TLG_BW_7 = 7, + TLG_BW_8 = 8, + TLG_BW_12 = 12, + TLG_BW_15 = 15 +}; + +struct cmd_firmware_vers_s { + uint8_t fw_rev_major; + uint8_t fw_rev_minor; + uint16_t fw_patch; +}; +#endif /* VENDOR_CMD_H_ */ -- cgit v1.2.3 From d2fa21876147005ba942cef1d9e5a5039b4326bd Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Tue, 23 Feb 2010 14:08:20 -0300 Subject: V4L/DVB: MAINTAINERS: Telegent tlg2300 section fix linux-next commit 2ff8223957d901999bf76aaf2c6183e33a6ad14e exposes an infinite loop defect in scripts/get_maintainer.pl Fix the incorrect format of the MAINTAINERS "M:" entries. Signed-off-by: Joe Perches Acked-by: Huang Shijie Signed-off-by: Mauro Carvalho Chehab --- MAINTAINERS | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index f427294b85e0..f8bd5814bdda 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4677,13 +4677,12 @@ F: drivers/media/video/*7146* F: include/media/*7146* TLG2300 VIDEO4LINUX-2 DRIVER -M Huang Shijie -M Kang Yong -M Zhang Xiaobing +M: Huang Shijie +M: Kang Yong +M: Zhang Xiaobing S: Supported F: drivers/media/video/tlg2300 - SC1200 WDT DRIVER M: Zwane Mwaikambo S: Maintained -- cgit v1.2.3 From c4d1409bbed8cf2a11b48ce3a075e142f686a588 Mon Sep 17 00:00:00 2001 From: Konrad Rzeszutek Wilk Date: Fri, 26 Feb 2010 03:53:00 +0000 Subject: ibft: Update MAINTAINERS file. Provide the right e-mail and names for me and Peter. Signed-off-by: Konrad Rzeszutek Wilk --- MAINTAINERS | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 317ed38826d7..0d8a948878e2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2399,6 +2399,12 @@ L: virtualization@lists.linux-foundation.org S: Maintained F: drivers/char/virtio_console.c +iSCSI BOOT FIRMWARE TABLE (iBFT) DRIVER +M: Peter Jones +M: Konrad Rzeszutek Wilk +S: Maintained +F: drivers/firmware/iscsi_ibft* + GSPCA FINEPIX SUBDRIVER M: Frank Zago L: linux-media@vger.kernel.org -- cgit v1.2.3 From a040d532b912b5dd7d88692b580cff9c88b987e3 Mon Sep 17 00:00:00 2001 From: Lennert Buytenhek Date: Tue, 23 Feb 2010 09:34:38 +0100 Subject: MAINTAINERS: update mwl8k maintenance status I am no longer with Marvell. Signed-off-by: Lennert Buytenhek Signed-off-by: John W. Linville --- MAINTAINERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 2b4a4d2f2ece..cb0a8003b5b9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3450,9 +3450,9 @@ F: drivers/net/mv643xx_eth.* F: include/linux/mv643xx.h MARVELL MWL8K WIRELESS DRIVER -M: Lennert Buytenhek +M: Lennert Buytenhek L: linux-wireless@vger.kernel.org -S: Supported +S: Maintained F: drivers/net/wireless/mwl8k.c MARVELL SOC MMC/SD/SDIO CONTROLLER DRIVER -- cgit v1.2.3 From ddf0289db22c4ae2192a252706792837528605c6 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Sun, 20 Dec 2009 22:24:07 +0100 Subject: KVM: powerpc: Change maintainer Progress on KVM for Embedded PowerPC has stalled, but for Book3S there's quite a lot of work to do and going on. So in agreement with Hollis and Avi, we should switch maintainers for PowerPC. Signed-off-by: Alexander Graf Acked-by: Hollis Blanchard Signed-off-by: Avi Kivity --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index f520dd0862b1..cfca7d4201b2 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3167,7 +3167,7 @@ F: arch/x86/include/asm/svm.h F: arch/x86/kvm/svm.c KERNEL VIRTUAL MACHINE (KVM) FOR POWERPC -M: Hollis Blanchard +M: Alexander Graf L: kvm-ppc@vger.kernel.org W: http://kvm.qumranet.com S: Supported -- cgit v1.2.3 From 661cb9fbf2ce580000a792bf1d394fc341c9ba69 Mon Sep 17 00:00:00 2001 From: Thadeu Lima de Souza Cascardo Date: Mon, 1 Mar 2010 14:03:48 -0500 Subject: MAINTAINERS: Add git tree to x86 Platform Drivers Add the x86 platform driver git tree to MAINTAINERS. Signed-off-by: Thadeu Lima de Souza Cascardo --- MAINTAINERS | 1 + 1 file changed, 1 insertion(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index f520dd0862b1..0525001d1c16 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6073,6 +6073,7 @@ F: arch/x86/ X86 PLATFORM DRIVERS M: Matthew Garrett L: platform-driver-x86@vger.kernel.org +T: git git://git.kernel.org/pub/scm/linux/kernel/git/mjg59/platform-drivers-x86.git S: Maintained F: drivers/platform/x86 -- cgit v1.2.3 From d624870ffe6e13df94671be9a0e0e2fb001f1bef Mon Sep 17 00:00:00 2001 From: Jean Delvare Date: Fri, 5 Mar 2010 22:17:20 +0100 Subject: hwmon: (it87) Add an entry in MAINTAINERS As I've just done a lot of changes to the it87 driver, I volunteer to maintain it for the year to come. Signed-off-by: Jean Delvare --- MAINTAINERS | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index c6591bca646b..bb6ec71f025b 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -3046,6 +3046,13 @@ W: http://www.melware.de S: Maintained F: drivers/isdn/hardware/eicon/ +IT87 HARDWARE MONITORING DRIVER +M: Jean Delvare +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/it87 +F: drivers/hwmon/it87.c + IVTV VIDEO4LINUX DRIVER M: Andy Walls L: ivtv-devel@ivtvdriver.org (moderated for non-subscribers) -- cgit v1.2.3 From d58de038728221f780e11d50b32aa40d420c1150 Mon Sep 17 00:00:00 2001 From: George Joseph Date: Fri, 5 Mar 2010 22:17:25 +0100 Subject: hwmon: Driver for Andigilog aSC7621 family monitoring chips Hwmon driver for Andigilog aSC7621 family monitoring chips. Signed-off-by: George Joseph Acked-by: Hans de Goede Signed-off-by: Jean Delvare --- Documentation/hwmon/asc7621 | 296 ++++++++++ MAINTAINERS | 7 + drivers/hwmon/Kconfig | 13 + drivers/hwmon/Makefile | 1 + drivers/hwmon/asc7621.c | 1255 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 1572 insertions(+) create mode 100644 Documentation/hwmon/asc7621 create mode 100644 drivers/hwmon/asc7621.c (limited to 'MAINTAINERS') diff --git a/Documentation/hwmon/asc7621 b/Documentation/hwmon/asc7621 new file mode 100644 index 000000000000..7287be7e1f21 --- /dev/null +++ b/Documentation/hwmon/asc7621 @@ -0,0 +1,296 @@ +Kernel driver asc7621 +================== + +Supported chips: + Andigilog aSC7621 and aSC7621a + Prefix: 'asc7621' + Addresses scanned: I2C 0x2c, 0x2d, 0x2e + Datasheet: http://www.fairview5.com/linux/asc7621/asc7621.pdf + +Author: + George Joseph + +Description provided by Dave Pivin @ Andigilog: + +Andigilog has both the PECI and pre-PECI versions of the Heceta-6, as +Intel calls them. Heceta-6e has high frequency PWM and Heceta-6p has +added PECI and a 4th thermal zone. The Andigilog aSC7611 is the +Heceta-6e part and aSC7621 is the Heceta-6p part. They are both in +volume production, shipping to Intel and their subs. + +We have enhanced both parts relative to the governing Intel +specification. First enhancement is temperature reading resolution. We +have used registers below 20h for vendor-specific functions in addition +to those in the Intel-specified vendor range. + +Our conversion process produces a result that is reported as two bytes. +The fan speed control uses this finer value to produce a "step-less" fan +PWM output. These two bytes are "read-locked" to guarantee that once a +high or low byte is read, the other byte is locked-in until after the +next read of any register. So to get an atomic reading, read high or low +byte, then the very next read should be the opposite byte. Our data +sheet says 10-bits of resolution, although you may find the lower bits +are active, they are not necessarily reliable or useful externally. We +chose not to mask them. + +We employ significant filtering that is user tunable as described in the +data sheet. Our temperature reports and fan PWM outputs are very smooth +when compared to the competition, in addition to the higher resolution +temperature reports. The smoother PWM output does not require user +intervention. + +We offer GPIO features on the former VID pins. These are open-drain +outputs or inputs and may be used as general purpose I/O or as alarm +outputs that are based on temperature limits. These are in 19h and 1Ah. + +We offer flexible mapping of temperature readings to thermal zones. Any +temperature may be mapped to any zone, which has a default assignment +that follows Intel's specs. + +Since there is a fan to zone assignment that allows for the "hotter" of +a set of zones to control the PWM of an individual fan, but there is no +indication to the user, we have added an indicator that shows which zone +is currently controlling the PWM for a given fan. This is in register +00h. + +Both remote diode temperature readings may be given an offset value such +that the reported reading as well as the temperature used to determine +PWM may be offset for system calibration purposes. + +PECI Extended configuration allows for having more than two domains per +PECI address and also provides an enabling function for each PECI +address. One could use our flexible zone assignment to have a zone +assigned to up to 4 PECI addresses. This is not possible in the default +Intel configuration. This would be useful in multi-CPU systems with +individual fans on each that would benefit from individual fan control. +This is in register 0Eh. + +The tachometer measurement system is flexible and able to adapt to many +fan types. We can also support pulse-stretched PWM so that 3-wire fans +may be used. These characteristics are in registers 04h to 07h. + +Finally, we have added a tach disable function that turns off the tach +measurement system for individual tachs in order to save power. That is +in register 75h. + +-- +aSC7621 Product Description + +The aSC7621 has a two wire digital interface compatible with SMBus 2.0. +Using a 10-bit ADC, the aSC7621 measures the temperature of two remote diode +connected transistors as well as its own die. Support for Platform +Environmental Control Interface (PECI) is included. + +Using temperature information from these four zones, an automatic fan speed +control algorithm is employed to minimize acoustic impact while achieving +recommended CPU temperature under varying operational loads. + +To set fan speed, the aSC7621 has three independent pulse width modulation +(PWM) outputs that are controlled by one, or a combination of three, +temperature zones. Both high- and low-frequency PWM ranges are supported. + +The aSC7621 also includes a digital filter that can be invoked to smooth +temperature readings for better control of fan speed and minimum acoustic +impact. + +The aSC7621 has tachometer inputs to measure fan speed on up to four fans. +Limit and status registers for all measured values are included to alert +the system host that any measurements are outside of programmed limits +via status registers. + +System voltages of VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard power are +monitored efficiently with internal scaling resistors. + +Features +- Supports PECI interface and monitors internal and remote thermal diodes +- 2-wire, SMBus 2.0 compliant, serial interface +- 10-bit ADC +- Monitors VCCP, 2.5V, 3.3V, 5.0V, and 12V motherboard/processor supplies +- Programmable autonomous fan control based on temperature readings +- Noise filtering of temperature reading for fan speed control +- 0.25C digital temperature sensor resolution +- 3 PWM fan speed control outputs for 2-, 3- or 4-wire fans and up to 4 fan + tachometer inputs +- Enhanced measured temperature to Temperature Zone assignment. +- Provides high and low PWM frequency ranges +- 3 GPIO pins for custom use +- 24-Lead QSOP package + +Configuration Notes +=================== + +Except where noted below, the sysfs entries created by this driver follow +the standards defined in "sysfs-interface". + +temp1_source + 0 (default) peci_legacy = 0, Remote 1 Temperature + peci_legacy = 1, PECI Processor Temperature 0 + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp2_source + 0 (default) Internal Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp3_source + 0 (default) Remote 2 Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp4_source + 0 (default) peci_legacy = 0, PECI Processor Temperature 0 + peci_legacy = 1, Remote 1 Temperature + 1 Remote 1 Temperature + 2 Remote 2 Temperature + 3 Internal Temperature + 4 PECI Processor Temperature 0 + 5 PECI Processor Temperature 1 + 6 PECI Processor Temperature 2 + 7 PECI Processor Temperature 3 + +temp[1-4]_smoothing_enable +temp[1-4]_smoothing_time + Smooths spikes in temp readings caused by noise. + Valid values in milliseconds are: + 35000 + 17600 + 11800 + 7000 + 4400 + 3000 + 1600 + 800 + +temp[1-4]_crit + When the corresponding zone temperature reaches this value, + ALL pwm outputs will got to 100%. + +temp[5-8]_input +temp[5-8]_enable + The aSC7621 can also read temperatures provided by the processor + via the PECI bus. Usually these are "core" temps and are relative + to the point where the automatic thermal control circuit starts + throttling. This means that these are usually negative numbers. + +pwm[1-3]_enable + 0 Fan off. + 1 Fan on manual control. + 2 Fan on automatic control and will run at the minimum pwm + if the temperature for the zone is below the minimum. + 3 Fan on automatic control but will be off if the temperature + for the zone is below the minimum. + 4-254 Ignored. + 255 Fan on full. + +pwm[1-3]_auto_channels + Bitmap as described in sysctl-interface with the following + exceptions... + Only the following combination of zones (and their corresponding masks) + are valid: + 1 + 2 + 3 + 2,3 + 1,2,3 + 4 + 1,2,3,4 + + Special values: + 0 Disabled. + 16 Fan on manual control. + 31 Fan on full. + + +pwm[1-3]_invert + When set, inverts the meaning of pwm[1-3]. + i.e. when pwm = 0, the fan will be on full and + when pwm = 255 the fan will be off. + +pwm[1-3]_freq + PWM frequency in Hz + Valid values in Hz are: + + 10 + 15 + 23 + 30 (default) + 38 + 47 + 62 + 94 + 23000 + 24000 + 25000 + 26000 + 27000 + 28000 + 29000 + 30000 + + Setting any other value will be ignored. + +peci_enable + Enables or disables PECI + +peci_avg + Input filter average time. + + 0 0 Sec. (no Smoothing) (default) + 1 0.25 Sec. + 2 0.5 Sec. + 3 1.0 Sec. + 4 2.0 Sec. + 5 4.0 Sec. + 6 8.0 Sec. + 7 0.0 Sec. + +peci_legacy + + 0 Standard Mode (default) + Remote Diode 1 reading is associated with + Temperature Zone 1, PECI is associated with + Zone 4 + + 1 Legacy Mode + PECI is associated with Temperature Zone 1, + Remote Diode 1 is associated with Zone 4 + +peci_diode + Diode filter + + 0 0.25 Sec. + 1 1.1 Sec. + 2 2.4 Sec. (default) + 3 3.4 Sec. + 4 5.0 Sec. + 5 6.8 Sec. + 6 10.2 Sec. + 7 16.4 Sec. + +peci_4domain + Four domain enable + + 0 1 or 2 Domains for enabled processors (default) + 1 3 or 4 Domains for enabled processors + +peci_domain + Domain + + 0 Processor contains a single domain (0) (default) + 1 Processor contains two domains (0,1) diff --git a/MAINTAINERS b/MAINTAINERS index bb6ec71f025b..d6cbddb57326 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -966,6 +966,13 @@ W: http://www.arm.linux.org.uk/ S: Maintained F: arch/arm/vfp/ +ASC7621 HARDWARE MONITOR DRIVER +M: George Joseph +L: lm-sensors@lm-sensors.org +S: Maintained +F: Documentation/hwmon/asc7621 +F: drivers/hwmon/asc7621.c + ASUS ACPI EXTRAS DRIVER M: Corentin Chary M: Karol Kozimor diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index 77d032fb813d..b6d65aa20825 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -226,6 +226,19 @@ config SENSORS_ADT7475 This driver can also be build as a module. If so, the module will be called adt7475. +config SENSORS_ASC7621 + tristate "Andigilog aSC7621" + depends on HWMON && I2C + help + If you say yes here you get support for the aSC7621 + family of SMBus sensors chip found on most Intel X48, X38, 975, + 965 and 945 desktop boards. Currently supported chips: + aSC7621 + aSC7621a + + This driver can also be built as a module. If so, the module + will be called asc7621. + config SENSORS_K8TEMP tristate "AMD Athlon64/FX or Opteron temperature sensor" depends on X86 && PCI && EXPERIMENTAL diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index 5fe67bf961b3..865da80f2b90 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -36,6 +36,7 @@ obj-$(CONFIG_SENSORS_ADT7473) += adt7473.o obj-$(CONFIG_SENSORS_ADT7475) += adt7475.o obj-$(CONFIG_SENSORS_APPLESMC) += applesmc.o obj-$(CONFIG_SENSORS_AMS) += ams/ +obj-$(CONFIG_SENSORS_ASC7621) += asc7621.o obj-$(CONFIG_SENSORS_ATXP1) += atxp1.o obj-$(CONFIG_SENSORS_CORETEMP) += coretemp.o obj-$(CONFIG_SENSORS_DME1737) += dme1737.o diff --git a/drivers/hwmon/asc7621.c b/drivers/hwmon/asc7621.c new file mode 100644 index 000000000000..7f948105d8ad --- /dev/null +++ b/drivers/hwmon/asc7621.c @@ -0,0 +1,1255 @@ +/* + * asc7621.c - Part of lm_sensors, Linux kernel modules for hardware monitoring + * Copyright (c) 2007, 2010 George Joseph + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* Addresses to scan */ +static unsigned short normal_i2c[] = { + 0x2c, 0x2d, 0x2e, I2C_CLIENT_END +}; + +enum asc7621_type { + asc7621, + asc7621a +}; + +#define INTERVAL_HIGH (HZ + HZ / 2) +#define INTERVAL_LOW (1 * 60 * HZ) +#define PRI_NONE 0 +#define PRI_LOW 1 +#define PRI_HIGH 2 +#define FIRST_CHIP asc7621 +#define LAST_CHIP asc7621a + +struct asc7621_chip { + char *name; + enum asc7621_type chip_type; + u8 company_reg; + u8 company_id; + u8 verstep_reg; + u8 verstep_id; + unsigned short *addresses; +}; + +static struct asc7621_chip asc7621_chips[] = { + { + .name = "asc7621", + .chip_type = asc7621, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6c, + .addresses = normal_i2c, + }, + { + .name = "asc7621a", + .chip_type = asc7621a, + .company_reg = 0x3e, + .company_id = 0x61, + .verstep_reg = 0x3f, + .verstep_id = 0x6d, + .addresses = normal_i2c, + }, +}; + +/* + * Defines the highest register to be used, not the count. + * The actual count will probably be smaller because of gaps + * in the implementation (unused register locations). + * This define will safely set the array size of both the parameter + * and data arrays. + * This comes from the data sheet register description table. + */ +#define LAST_REGISTER 0xff + +struct asc7621_data { + struct i2c_client client; + struct device *class_dev; + struct mutex update_lock; + int valid; /* !=0 if following fields are valid */ + unsigned long last_high_reading; /* In jiffies */ + unsigned long last_low_reading; /* In jiffies */ + /* + * Registers we care about occupy the corresponding index + * in the array. Registers we don't care about are left + * at 0. + */ + u8 reg[LAST_REGISTER + 1]; +}; + +/* + * Macro to get the parent asc7621_param structure + * from a sensor_device_attribute passed into the + * show/store functions. + */ +#define to_asc7621_param(_sda) \ + container_of(_sda, struct asc7621_param, sda) + +/* + * Each parameter to be retrieved needs an asc7621_param structure + * allocated. It contains the sensor_device_attribute structure + * and the control info needed to retrieve the value from the register map. + */ +struct asc7621_param { + struct sensor_device_attribute sda; + u8 priority; + u8 msb[3]; + u8 lsb[3]; + u8 mask[3]; + u8 shift[3]; +}; + +/* + * This is the map that ultimately indicates whether we'll be + * retrieving a register value or not, and at what frequency. + */ +static u8 asc7621_register_priorities[255]; + +static struct asc7621_data *asc7621_update_device(struct device *dev); + +static inline u8 read_byte(struct i2c_client *client, u8 reg) +{ + int res = i2c_smbus_read_byte_data(client, reg); + if (res < 0) { + dev_err(&client->dev, + "Unable to read from register 0x%02x.\n", reg); + return 0; + }; + return res & 0xff; +} + +static inline int write_byte(struct i2c_client *client, u8 reg, u8 data) +{ + int res = i2c_smbus_write_byte_data(client, reg, data); + if (res < 0) { + dev_err(&client->dev, + "Unable to write value 0x%02x to register 0x%02x.\n", + data, reg); + }; + return res; +} + +/* + * Data Handlers + * Each function handles the formatting, storage + * and retrieval of like parameters. + */ + +#define SETUP_SHOW_data_param(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct asc7621_data *data = asc7621_update_device(d); \ + struct asc7621_param *param = to_asc7621_param(sda) + +#define SETUP_STORE_data_param(d, a) \ + struct sensor_device_attribute *sda = to_sensor_dev_attr(a); \ + struct i2c_client *client = to_i2c_client(d); \ + struct asc7621_data *data = i2c_get_clientdata(client); \ + struct asc7621_param *param = to_asc7621_param(sda) + +/* + * u8 is just what it sounds like...an unsigned byte with no + * special formatting. + */ +static ssize_t show_u8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%u\n", data->reg[param->msb[0]]); +} + +static ssize_t store_u8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, 255); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Many of the config values occupy only a few bits of a register. + */ +static ssize_t show_bitmask(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%u\n", + (data->reg[param->msb[0]] >> param-> + shift[0]) & param->mask[0]); +} + +static ssize_t store_bitmask(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, param->mask[0]); + + reqval = (reqval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + reqval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * 16 bit fan rpm values + * reported by the device as the number of 11.111us periods (90khz) + * between full fan rotations. Therefore... + * RPM = (90000 * 60) / register value + */ +static ssize_t show_fan16(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u16 regval; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] << 8) | data->reg[param->lsb[0]]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", + (regval == 0 ? -1 : (regval) == + 0xffff ? 0 : 5400000 / regval)); +} + +static ssize_t store_fan16(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = + (SENSORS_LIMIT((reqval) <= 0 ? 0 : 5400000 / (reqval), 0, 65534)); + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = (reqval >> 8) & 0xff; + data->reg[param->lsb[0]] = reqval & 0xff; + write_byte(client, param->msb[0], data->reg[param->msb[0]]); + write_byte(client, param->lsb[0], data->reg[param->lsb[0]]); + mutex_unlock(&data->update_lock); + + return count; +} + +/* + * Voltages are scaled in the device so that the nominal voltage + * is 3/4ths of the 0-255 range (i.e. 192). + * If all voltages are 'normal' then all voltage registers will + * read 0xC0. This doesn't help us if we don't have a point of refernce. + * The data sheet however provides us with the full scale value for each + * which is stored in in_scaling. The sda->index parameter value provides + * the index into in_scaling. + * + * NOTE: The chip expects the first 2 inputs be 2.5 and 2.25 volts + * respectively. That doesn't mean that's what the motherboard provides. :) + */ + +static int asc7621_in_scaling[] = { + 3320, 3000, 4380, 6640, 16000 +}; + +static ssize_t show_in10(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u16 regval; + u8 nr = sda->index; + + mutex_lock(&data->update_lock); + regval = (data->reg[param->msb[0]] * asc7621_in_scaling[nr]) / 256; + + /* The LSB value is a 2-bit scaling of the MSB's LSbit value. + * I.E. If the maximim voltage for this input is 6640 millivolts then + * a MSB register value of 0 = 0mv and 255 = 6640mv. + * A 1 step change therefore represents 25.9mv (6640 / 256). + * The extra 2-bits therefore represent increments of 6.48mv. + */ + regval += ((asc7621_in_scaling[nr] / 256) / 4) * + (data->reg[param->lsb[0]] >> 6); + + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", regval); +} + +/* 8 bit voltage values (the mins and maxs) */ +static ssize_t show_in8(struct device *dev, struct device_attribute *attr, + char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 nr = sda->index; + + return sprintf(buf, "%u\n", + ((data->reg[param->msb[0]] * + asc7621_in_scaling[nr]) / 256)); +} + +static ssize_t store_in8(struct device *dev, struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 nr = sda->index; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, 0, asc7621_in_scaling[nr]); + + reqval = (reqval * 255 + 128) / asc7621_in_scaling[nr]; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = reqval; + write_byte(client, param->msb[0], reqval); + mutex_unlock(&data->update_lock); + + return count; +} + +static ssize_t show_temp8(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + + return sprintf(buf, "%d\n", ((s8) data->reg[param->msb[0]]) * 1000); +} + +static ssize_t store_temp8(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + s8 temp; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, -127000, 127000); + + temp = reqval / 1000; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * Temperatures that occupy 2 bytes always have the whole + * number of degrees in the MSB with some part of the LSB + * indicating fractional degrees. + */ + +/* mmmmmmmm.llxxxxxx */ +static ssize_t show_temp10(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 msb, lsb; + int temp; + + mutex_lock(&data->update_lock); + msb = data->reg[param->msb[0]]; + lsb = (data->reg[param->lsb[0]] >> 6) & 0x03; + temp = (((s8) msb) * 1000) + (lsb * 250); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); +} + +/* mmmmmm.ll */ +static ssize_t show_temp62(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = data->reg[param->msb[0]]; + int temp = ((s8) (regval & 0xfc) * 1000) + ((regval & 0x03) * 250); + + return sprintf(buf, "%d\n", temp); +} + +static ssize_t store_temp62(struct device *dev, + struct device_attribute *attr, const char *buf, + size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval, i, f; + s8 temp; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + reqval = SENSORS_LIMIT(reqval, -32000, 31750); + i = reqval / 1000; + f = reqval - (i * 1000); + temp = i << 2; + temp |= f / 250; + + mutex_lock(&data->update_lock); + data->reg[param->msb[0]] = temp; + write_byte(client, param->msb[0], temp); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * The aSC7621 doesn't provide an "auto_point2". Instead, you + * specify the auto_point1 and a range. To keep with the sysfs + * hwmon specs, we synthesize the auto_point_2 from them. + */ + +static u32 asc7621_range_map[] = { + 2000, 2500, 3330, 4000, 5000, 6670, 8000, 10000, + 13330, 16000, 20000, 26670, 32000, 40000, 53330, 80000, +}; + +static ssize_t show_ap2_temp(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + long auto_point1; + u8 regval; + int temp; + + mutex_lock(&data->update_lock); + auto_point1 = ((s8) data->reg[param->msb[1]]) * 1000; + regval = + ((data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]); + temp = auto_point1 + asc7621_range_map[SENSORS_LIMIT(regval, 0, 15)]; + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%d\n", temp); + +} + +static ssize_t store_ap2_temp(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval, auto_point1; + int i; + u8 currval, newval = 0; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + mutex_lock(&data->update_lock); + auto_point1 = data->reg[param->msb[1]] * 1000; + reqval = SENSORS_LIMIT(reqval, auto_point1 + 2000, auto_point1 + 80000); + + for (i = ARRAY_SIZE(asc7621_range_map) - 1; i >= 0; i--) { + if (reqval >= auto_point1 + asc7621_range_map[i]) { + newval = i; + break; + } + } + + newval = (newval & param->mask[0]) << param->shift[0]; + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_ac(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 config, altbit, regval; + u8 map[] = { + 0x01, 0x02, 0x04, 0x1f, 0x00, 0x06, 0x07, 0x10, + 0x08, 0x0f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f, 0x1f + }; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + regval = config | (altbit << 3); + mutex_unlock(&data->update_lock); + + return sprintf(buf, "%u\n", map[SENSORS_LIMIT(regval, 0, 15)]); +} + +static ssize_t store_pwm_ac(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + unsigned long reqval; + u8 currval, config, altbit, newval; + u16 map[] = { + 0x04, 0x00, 0x01, 0xff, 0x02, 0xff, 0x05, 0x06, + 0x08, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x0f, + 0x07, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x03, + }; + + if (strict_strtoul(buf, 10, &reqval)) + return -EINVAL; + + if (reqval > 31) + return -EINVAL; + + reqval = map[reqval]; + if (reqval == 0xff) + return -EINVAL; + + config = reqval & 0x07; + altbit = (reqval >> 3) & 0x01; + + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static ssize_t show_pwm_enable(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 config, altbit, minoff, val, newval; + + mutex_lock(&data->update_lock); + config = (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + altbit = (data->reg[param->msb[1]] >> param->shift[1]) & param->mask[1]; + minoff = (data->reg[param->msb[2]] >> param->shift[2]) & param->mask[2]; + mutex_unlock(&data->update_lock); + + val = config | (altbit << 3); + newval = 0; + + if (val == 3 || val >= 10) + newval = 255; + else if (val == 4) + newval = 0; + else if (val == 7) + newval = 1; + else if (minoff == 1) + newval = 2; + else + newval = 3; + + return sprintf(buf, "%u\n", newval); +} + +static ssize_t store_pwm_enable(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, config, altbit, newval, minoff = 255; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + switch (reqval) { + case 0: + newval = 0x04; + break; + case 1: + newval = 0x07; + break; + case 2: + newval = 0x00; + minoff = 1; + break; + case 3: + newval = 0x00; + minoff = 0; + break; + case 255: + newval = 0x03; + break; + default: + return -EINVAL; + } + + config = newval & 0x07; + altbit = (newval >> 3) & 0x01; + + mutex_lock(&data->update_lock); + config = (config & param->mask[0]) << param->shift[0]; + altbit = (altbit & param->mask[1]) << param->shift[1]; + currval = read_byte(client, param->msb[0]); + newval = config | (currval & ~(param->mask[0] << param->shift[0])); + newval = altbit | (newval & ~(param->mask[1] << param->shift[1])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + if (minoff < 255) { + minoff = (minoff & param->mask[2]) << param->shift[2]; + currval = read_byte(client, param->msb[2]); + newval = + minoff | (currval & ~(param->mask[2] << param->shift[2])); + data->reg[param->msb[2]] = newval; + write_byte(client, param->msb[2], newval); + } + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_pwm_freq_map[] = { + 10, 15, 23, 30, 38, 47, 62, 94, + 23000, 24000, 25000, 26000, 27000, 28000, 29000, 30000 +}; + +static ssize_t show_pwm_freq(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = SENSORS_LIMIT(regval, 0, 15); + + return sprintf(buf, "%u\n", asc7621_pwm_freq_map[regval]); +} + +static ssize_t store_pwm_freq(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + unsigned long reqval; + u8 currval, newval = 255; + int i; + + if (strict_strtoul(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_freq_map); i++) { + if (reqval == asc7621_pwm_freq_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_pwm_auto_spinup_map[] = { + 0, 100, 250, 400, 700, 1000, 2000, 4000 +}; + +static ssize_t show_pwm_ast(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + + regval = SENSORS_LIMIT(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_pwm_auto_spinup_map[regval]); + +} + +static ssize_t store_pwm_ast(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_pwm_auto_spinup_map); i++) { + if (reqval == asc7621_pwm_auto_spinup_map[i]) { + newval = i; + break; + } + } + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +static u32 asc7621_temp_smoothing_time_map[] = { + 35000, 17600, 11800, 7000, 4400, 3000, 1600, 800 +}; + +static ssize_t show_temp_st(struct device *dev, + struct device_attribute *attr, char *buf) +{ + SETUP_SHOW_data_param(dev, attr); + u8 regval = + (data->reg[param->msb[0]] >> param->shift[0]) & param->mask[0]; + regval = SENSORS_LIMIT(regval, 0, 7); + + return sprintf(buf, "%u\n", asc7621_temp_smoothing_time_map[regval]); +} + +static ssize_t store_temp_st(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + SETUP_STORE_data_param(dev, attr); + long reqval; + u8 currval, newval = 255; + u32 i; + + if (strict_strtol(buf, 10, &reqval)) + return -EINVAL; + + for (i = 0; i < ARRAY_SIZE(asc7621_temp_smoothing_time_map); i++) { + if (reqval == asc7621_temp_smoothing_time_map[i]) { + newval = i; + break; + } + } + + if (newval == 255) + return -EINVAL; + + newval = (newval & param->mask[0]) << param->shift[0]; + + mutex_lock(&data->update_lock); + currval = read_byte(client, param->msb[0]); + newval |= (currval & ~(param->mask[0] << param->shift[0])); + data->reg[param->msb[0]] = newval; + write_byte(client, param->msb[0], newval); + mutex_unlock(&data->update_lock); + return count; +} + +/* + * End of data handlers + * + * These defines do nothing more than make the table easier + * to read when wrapped at column 80. + */ + +/* + * Creates a variable length array inititalizer. + * VAA(1,3,5,7) would produce {1,3,5,7} + */ +#define VAA(args...) {args} + +#define PREAD(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO, show_##r, NULL, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +#define PWRITE(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb[0] = rm, .lsb[0] = rl, .mask[0] = m, \ + .shift[0] = s,} + +/* + * PWRITEM assumes that the initializers for the .msb, .lsb, .mask and .shift + * were created using the VAA macro. + */ +#define PWRITEM(name, n, pri, rm, rl, m, s, r) \ + {.sda = SENSOR_ATTR(name, S_IRUGO | S_IWUSR, show_##r, store_##r, n), \ + .priority = pri, .msb = rm, .lsb = rl, .mask = m, .shift = s,} + +static struct asc7621_param asc7621_params[] = { + PREAD(in0_input, 0, PRI_HIGH, 0x20, 0x13, 0, 0, in10), + PREAD(in1_input, 1, PRI_HIGH, 0x21, 0x18, 0, 0, in10), + PREAD(in2_input, 2, PRI_HIGH, 0x22, 0x11, 0, 0, in10), + PREAD(in3_input, 3, PRI_HIGH, 0x23, 0x12, 0, 0, in10), + PREAD(in4_input, 4, PRI_HIGH, 0x24, 0x14, 0, 0, in10), + + PWRITE(in0_min, 0, PRI_LOW, 0x44, 0, 0, 0, in8), + PWRITE(in1_min, 1, PRI_LOW, 0x46, 0, 0, 0, in8), + PWRITE(in2_min, 2, PRI_LOW, 0x48, 0, 0, 0, in8), + PWRITE(in3_min, 3, PRI_LOW, 0x4a, 0, 0, 0, in8), + PWRITE(in4_min, 4, PRI_LOW, 0x4c, 0, 0, 0, in8), + + PWRITE(in0_max, 0, PRI_LOW, 0x45, 0, 0, 0, in8), + PWRITE(in1_max, 1, PRI_LOW, 0x47, 0, 0, 0, in8), + PWRITE(in2_max, 2, PRI_LOW, 0x49, 0, 0, 0, in8), + PWRITE(in3_max, 3, PRI_LOW, 0x4b, 0, 0, 0, in8), + PWRITE(in4_max, 4, PRI_LOW, 0x4d, 0, 0, 0, in8), + + PREAD(in0_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 0, bitmask), + PREAD(in1_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 1, bitmask), + PREAD(in2_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 2, bitmask), + PREAD(in3_alarm, 3, PRI_LOW, 0x41, 0, 0x01, 3, bitmask), + PREAD(in4_alarm, 4, PRI_LOW, 0x42, 0, 0x01, 0, bitmask), + + PREAD(fan1_input, 0, PRI_HIGH, 0x29, 0x28, 0, 0, fan16), + PREAD(fan2_input, 1, PRI_HIGH, 0x2b, 0x2a, 0, 0, fan16), + PREAD(fan3_input, 2, PRI_HIGH, 0x2d, 0x2c, 0, 0, fan16), + PREAD(fan4_input, 3, PRI_HIGH, 0x2f, 0x2e, 0, 0, fan16), + + PWRITE(fan1_min, 0, PRI_LOW, 0x55, 0x54, 0, 0, fan16), + PWRITE(fan2_min, 1, PRI_LOW, 0x57, 0x56, 0, 0, fan16), + PWRITE(fan3_min, 2, PRI_LOW, 0x59, 0x58, 0, 0, fan16), + PWRITE(fan4_min, 3, PRI_LOW, 0x5b, 0x5a, 0, 0, fan16), + + PREAD(fan1_alarm, 0, PRI_LOW, 0x42, 0, 0x01, 0, bitmask), + PREAD(fan2_alarm, 1, PRI_LOW, 0x42, 0, 0x01, 1, bitmask), + PREAD(fan3_alarm, 2, PRI_LOW, 0x42, 0, 0x01, 2, bitmask), + PREAD(fan4_alarm, 3, PRI_LOW, 0x42, 0, 0x01, 3, bitmask), + + PREAD(temp1_input, 0, PRI_HIGH, 0x25, 0x10, 0, 0, temp10), + PREAD(temp2_input, 1, PRI_HIGH, 0x26, 0x15, 0, 0, temp10), + PREAD(temp3_input, 2, PRI_HIGH, 0x27, 0x16, 0, 0, temp10), + PREAD(temp4_input, 3, PRI_HIGH, 0x33, 0x17, 0, 0, temp10), + PREAD(temp5_input, 4, PRI_HIGH, 0xf7, 0xf6, 0, 0, temp10), + PREAD(temp6_input, 5, PRI_HIGH, 0xf9, 0xf8, 0, 0, temp10), + PREAD(temp7_input, 6, PRI_HIGH, 0xfb, 0xfa, 0, 0, temp10), + PREAD(temp8_input, 7, PRI_HIGH, 0xfd, 0xfc, 0, 0, temp10), + + PWRITE(temp1_min, 0, PRI_LOW, 0x4e, 0, 0, 0, temp8), + PWRITE(temp2_min, 1, PRI_LOW, 0x50, 0, 0, 0, temp8), + PWRITE(temp3_min, 2, PRI_LOW, 0x52, 0, 0, 0, temp8), + PWRITE(temp4_min, 3, PRI_LOW, 0x34, 0, 0, 0, temp8), + + PWRITE(temp1_max, 0, PRI_LOW, 0x4f, 0, 0, 0, temp8), + PWRITE(temp2_max, 1, PRI_LOW, 0x51, 0, 0, 0, temp8), + PWRITE(temp3_max, 2, PRI_LOW, 0x53, 0, 0, 0, temp8), + PWRITE(temp4_max, 3, PRI_LOW, 0x35, 0, 0, 0, temp8), + + PREAD(temp1_alarm, 0, PRI_LOW, 0x41, 0, 0x01, 4, bitmask), + PREAD(temp2_alarm, 1, PRI_LOW, 0x41, 0, 0x01, 5, bitmask), + PREAD(temp3_alarm, 2, PRI_LOW, 0x41, 0, 0x01, 6, bitmask), + PREAD(temp4_alarm, 3, PRI_LOW, 0x43, 0, 0x01, 0, bitmask), + + PWRITE(temp1_source, 0, PRI_LOW, 0x02, 0, 0x07, 4, bitmask), + PWRITE(temp2_source, 1, PRI_LOW, 0x02, 0, 0x07, 0, bitmask), + PWRITE(temp3_source, 2, PRI_LOW, 0x03, 0, 0x07, 4, bitmask), + PWRITE(temp4_source, 3, PRI_LOW, 0x03, 0, 0x07, 0, bitmask), + + PWRITE(temp1_smoothing_enable, 0, PRI_LOW, 0x62, 0, 0x01, 3, bitmask), + PWRITE(temp2_smoothing_enable, 1, PRI_LOW, 0x63, 0, 0x01, 7, bitmask), + PWRITE(temp3_smoothing_enable, 2, PRI_LOW, 0x64, 0, 0x01, 3, bitmask), + PWRITE(temp4_smoothing_enable, 3, PRI_LOW, 0x3c, 0, 0x01, 3, bitmask), + + PWRITE(temp1_smoothing_time, 0, PRI_LOW, 0x62, 0, 0x07, 0, temp_st), + PWRITE(temp2_smoothing_time, 1, PRI_LOW, 0x63, 0, 0x07, 4, temp_st), + PWRITE(temp3_smoothing_time, 2, PRI_LOW, 0x63, 0, 0x07, 0, temp_st), + PWRITE(temp4_smoothing_time, 3, PRI_LOW, 0x3c, 0, 0x07, 0, temp_st), + + PWRITE(temp1_auto_point1_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PWRITE(temp2_auto_point1_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PWRITE(temp3_auto_point1_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PWRITE(temp4_auto_point1_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PREAD(temp1_auto_point2_temp_hyst, 0, PRI_LOW, 0x6d, 0, 0x0f, 4, + bitmask), + PREAD(temp2_auto_point2_temp_hyst, 1, PRI_LOW, 0x6d, 0, 0x0f, 0, + bitmask), + PREAD(temp3_auto_point2_temp_hyst, 2, PRI_LOW, 0x6e, 0, 0x0f, 4, + bitmask), + PREAD(temp4_auto_point2_temp_hyst, 3, PRI_LOW, 0x6e, 0, 0x0f, 0, + bitmask), + + PWRITE(temp1_auto_point1_temp, 0, PRI_LOW, 0x67, 0, 0, 0, temp8), + PWRITE(temp2_auto_point1_temp, 1, PRI_LOW, 0x68, 0, 0, 0, temp8), + PWRITE(temp3_auto_point1_temp, 2, PRI_LOW, 0x69, 0, 0, 0, temp8), + PWRITE(temp4_auto_point1_temp, 3, PRI_LOW, 0x3b, 0, 0, 0, temp8), + + PWRITEM(temp1_auto_point2_temp, 0, PRI_LOW, VAA(0x5f, 0x67), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp2_auto_point2_temp, 1, PRI_LOW, VAA(0x60, 0x68), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp3_auto_point2_temp, 2, PRI_LOW, VAA(0x61, 0x69), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + PWRITEM(temp4_auto_point2_temp, 3, PRI_LOW, VAA(0x3c, 0x3b), VAA(0), + VAA(0x0f), VAA(4), ap2_temp), + + PWRITE(temp1_crit, 0, PRI_LOW, 0x6a, 0, 0, 0, temp8), + PWRITE(temp2_crit, 1, PRI_LOW, 0x6b, 0, 0, 0, temp8), + PWRITE(temp3_crit, 2, PRI_LOW, 0x6c, 0, 0, 0, temp8), + PWRITE(temp4_crit, 3, PRI_LOW, 0x3d, 0, 0, 0, temp8), + + PWRITE(temp5_enable, 4, PRI_LOW, 0x0e, 0, 0x01, 0, bitmask), + PWRITE(temp6_enable, 5, PRI_LOW, 0x0e, 0, 0x01, 1, bitmask), + PWRITE(temp7_enable, 6, PRI_LOW, 0x0e, 0, 0x01, 2, bitmask), + PWRITE(temp8_enable, 7, PRI_LOW, 0x0e, 0, 0x01, 3, bitmask), + + PWRITE(remote1_offset, 0, PRI_LOW, 0x1c, 0, 0, 0, temp62), + PWRITE(remote2_offset, 1, PRI_LOW, 0x1d, 0, 0, 0, temp62), + + PWRITE(pwm1, 0, PRI_HIGH, 0x30, 0, 0, 0, u8), + PWRITE(pwm2, 1, PRI_HIGH, 0x31, 0, 0, 0, u8), + PWRITE(pwm3, 2, PRI_HIGH, 0x32, 0, 0, 0, u8), + + PWRITE(pwm1_invert, 0, PRI_LOW, 0x5c, 0, 0x01, 4, bitmask), + PWRITE(pwm2_invert, 1, PRI_LOW, 0x5d, 0, 0x01, 4, bitmask), + PWRITE(pwm3_invert, 2, PRI_LOW, 0x5e, 0, 0x01, 4, bitmask), + + PWRITEM(pwm1_enable, 0, PRI_LOW, VAA(0x5c, 0x5c, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 5), pwm_enable), + PWRITEM(pwm2_enable, 1, PRI_LOW, VAA(0x5d, 0x5d, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 6), pwm_enable), + PWRITEM(pwm3_enable, 2, PRI_LOW, VAA(0x5e, 0x5e, 0x62), VAA(0, 0, 0), + VAA(0x07, 0x01, 0x01), VAA(5, 3, 7), pwm_enable), + + PWRITEM(pwm1_auto_channels, 0, PRI_LOW, VAA(0x5c, 0x5c), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm2_auto_channels, 1, PRI_LOW, VAA(0x5d, 0x5d), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + PWRITEM(pwm3_auto_channels, 2, PRI_LOW, VAA(0x5e, 0x5e), VAA(0, 0), + VAA(0x07, 0x01), VAA(5, 3), pwm_ac), + + PWRITE(pwm1_auto_point1_pwm, 0, PRI_LOW, 0x64, 0, 0, 0, u8), + PWRITE(pwm2_auto_point1_pwm, 1, PRI_LOW, 0x65, 0, 0, 0, u8), + PWRITE(pwm3_auto_point1_pwm, 2, PRI_LOW, 0x66, 0, 0, 0, u8), + + PWRITE(pwm1_auto_point2_pwm, 0, PRI_LOW, 0x38, 0, 0, 0, u8), + PWRITE(pwm2_auto_point2_pwm, 1, PRI_LOW, 0x39, 0, 0, 0, u8), + PWRITE(pwm3_auto_point2_pwm, 2, PRI_LOW, 0x3a, 0, 0, 0, u8), + + PWRITE(pwm1_freq, 0, PRI_LOW, 0x5f, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm2_freq, 1, PRI_LOW, 0x60, 0, 0x0f, 0, pwm_freq), + PWRITE(pwm3_freq, 2, PRI_LOW, 0x61, 0, 0x0f, 0, pwm_freq), + + PREAD(pwm1_auto_zone_assigned, 0, PRI_LOW, 0, 0, 0x03, 2, bitmask), + PREAD(pwm2_auto_zone_assigned, 1, PRI_LOW, 0, 0, 0x03, 4, bitmask), + PREAD(pwm3_auto_zone_assigned, 2, PRI_LOW, 0, 0, 0x03, 6, bitmask), + + PWRITE(pwm1_auto_spinup_time, 0, PRI_LOW, 0x5c, 0, 0x07, 0, pwm_ast), + PWRITE(pwm2_auto_spinup_time, 1, PRI_LOW, 0x5d, 0, 0x07, 0, pwm_ast), + PWRITE(pwm3_auto_spinup_time, 2, PRI_LOW, 0x5e, 0, 0x07, 0, pwm_ast), + + PWRITE(peci_enable, 0, PRI_LOW, 0x40, 0, 0x01, 4, bitmask), + PWRITE(peci_avg, 0, PRI_LOW, 0x36, 0, 0x07, 0, bitmask), + PWRITE(peci_domain, 0, PRI_LOW, 0x36, 0, 0x01, 3, bitmask), + PWRITE(peci_legacy, 0, PRI_LOW, 0x36, 0, 0x01, 4, bitmask), + PWRITE(peci_diode, 0, PRI_LOW, 0x0e, 0, 0x07, 4, bitmask), + PWRITE(peci_4domain, 0, PRI_LOW, 0x0e, 0, 0x01, 4, bitmask), + +}; + +static struct asc7621_data *asc7621_update_device(struct device *dev) +{ + struct i2c_client *client = to_i2c_client(dev); + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + +/* + * The asc7621 chips guarantee consistent reads of multi-byte values + * regardless of the order of the reads. No special logic is needed + * so we can just read the registers in whatever order they appear + * in the asc7621_params array. + */ + + mutex_lock(&data->update_lock); + + /* Read all the high priority registers */ + + if (!data->valid || + time_after(jiffies, data->last_high_reading + INTERVAL_HIGH)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_register_priorities); i++) { + if (asc7621_register_priorities[i] == PRI_HIGH) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_high_reading = jiffies; + }; /* last_reading */ + + /* Read all the low priority registers. */ + + if (!data->valid || + time_after(jiffies, data->last_low_reading + INTERVAL_LOW)) { + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + if (asc7621_register_priorities[i] == PRI_LOW) { + data->reg[i] = + i2c_smbus_read_byte_data(client, i) & 0xff; + } + } + data->last_low_reading = jiffies; + }; /* last_reading */ + + data->valid = 1; + + mutex_unlock(&data->update_lock); + + return data; +} + +/* + * Standard detection and initialization below + * + * Helper function that checks if an address is valid + * for a particular chip. + */ + +static inline int valid_address_for_chip(int chip_type, int address) +{ + int i; + + for (i = 0; asc7621_chips[chip_type].addresses[i] != I2C_CLIENT_END; + i++) { + if (asc7621_chips[chip_type].addresses[i] == address) + return 1; + } + return 0; +} + +static void asc7621_init_client(struct i2c_client *client) +{ + int value; + + /* Warn if part was not "READY" */ + + value = read_byte(client, 0x40); + + if (value & 0x02) { + dev_err(&client->dev, + "Client (%d,0x%02x) config is locked.\n", + i2c_adapter_id(client->adapter), client->addr); + }; + if (!(value & 0x04)) { + dev_err(&client->dev, "Client (%d,0x%02x) is not ready.\n", + i2c_adapter_id(client->adapter), client->addr); + }; + +/* + * Start monitoring + * + * Try to clear LOCK, Set START, save everything else + */ + value = (value & ~0x02) | 0x01; + write_byte(client, 0x40, value & 0xff); + +} + +static int +asc7621_probe(struct i2c_client *client, const struct i2c_device_id *id) +{ + struct asc7621_data *data; + int i, err; + + if (!i2c_check_functionality(client->adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -EIO; + + data = kzalloc(sizeof(struct asc7621_data), GFP_KERNEL); + if (data == NULL) + return -ENOMEM; + + i2c_set_clientdata(client, data); + data->valid = 0; + mutex_init(&data->update_lock); + + /* Initialize the asc7621 chip */ + asc7621_init_client(client); + + /* Create the sysfs entries */ + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + err = + device_create_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + if (err) + goto exit_remove; + } + + data->class_dev = hwmon_device_register(&client->dev); + if (IS_ERR(data->class_dev)) { + err = PTR_ERR(data->class_dev); + goto exit_remove; + } + + return 0; + +exit_remove: + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + i2c_set_clientdata(client, NULL); + kfree(data); + return err; +} + +static int asc7621_detect(struct i2c_client *client, + struct i2c_board_info *info) +{ + struct i2c_adapter *adapter = client->adapter; + int company, verstep, chip_index; + struct device *dev; + + dev = &client->dev; + + if (!i2c_check_functionality(adapter, I2C_FUNC_SMBUS_BYTE_DATA)) + return -ENODEV; + + for (chip_index = FIRST_CHIP; chip_index <= LAST_CHIP; chip_index++) { + + if (!valid_address_for_chip(chip_index, client->addr)) + continue; + + company = read_byte(client, + asc7621_chips[chip_index].company_reg); + verstep = read_byte(client, + asc7621_chips[chip_index].verstep_reg); + + if (company == asc7621_chips[chip_index].company_id && + verstep == asc7621_chips[chip_index].verstep_id) { + strlcpy(client->name, asc7621_chips[chip_index].name, + I2C_NAME_SIZE); + strlcpy(info->type, asc7621_chips[chip_index].name, + I2C_NAME_SIZE); + + dev_info(&adapter->dev, "Matched %s\n", + asc7621_chips[chip_index].name); + return 0; + } + } + + return -ENODEV; +} + +static int asc7621_remove(struct i2c_client *client) +{ + struct asc7621_data *data = i2c_get_clientdata(client); + int i; + + hwmon_device_unregister(data->class_dev); + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + device_remove_file(&client->dev, + &(asc7621_params[i].sda.dev_attr)); + } + + i2c_set_clientdata(client, NULL); + kfree(data); + return 0; +} + +static const struct i2c_device_id asc7621_id[] = { + {"asc7621", asc7621}, + {"asc7621a", asc7621a}, + {}, +}; + +MODULE_DEVICE_TABLE(i2c, asc7621_id); + +static struct i2c_driver asc7621_driver = { + .class = I2C_CLASS_HWMON, + .driver = { + .name = "asc7621", + }, + .probe = asc7621_probe, + .remove = asc7621_remove, + .id_table = asc7621_id, + .detect = asc7621_detect, + .address_list = normal_i2c, +}; + +static int __init sm_asc7621_init(void) +{ + int i, j; +/* + * Collect all the registers needed into a single array. + * This way, if a register isn't actually used for anything, + * we don't retrieve it. + */ + + for (i = 0; i < ARRAY_SIZE(asc7621_params); i++) { + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].msb); j++) + asc7621_register_priorities[asc7621_params[i].msb[j]] = + asc7621_params[i].priority; + for (j = 0; j < ARRAY_SIZE(asc7621_params[i].lsb); j++) + asc7621_register_priorities[asc7621_params[i].lsb[j]] = + asc7621_params[i].priority; + } + return i2c_add_driver(&asc7621_driver); +} + +static void __exit sm_asc7621_exit(void) +{ + i2c_del_driver(&asc7621_driver); +} + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("George Joseph"); +MODULE_DESCRIPTION("Andigilog aSC7621 and aSC7621a driver"); + +module_init(sm_asc7621_init); +module_exit(sm_asc7621_exit); -- cgit v1.2.3 From 82cc83473559e0e6da6278053c2d14448b189e0e Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Fri, 5 Mar 2010 13:43:08 -0800 Subject: MAINTAINERS: remove AMD GEODE F: arch/x86/kernel/geode_32.c Commit c95d1e53ed89b75a4d7b68d1cbae4607b1479243 ("cs5535: drop the Geode-specific MFGPT/GPIO code") removed it. Signed-off-by: Joe Perches Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 1 - 1 file changed, 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 51d8b5221dd8..347afcd2a6e6 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -428,7 +428,6 @@ P: Jordan Crouse L: linux-geode@lists.infradead.org (moderated for non-subscribers) W: http://www.amd.com/us-en/ConnectivitySolutions/TechnicalResources/0,,50_2334_2452_11363,00.html S: Supported -F: arch/x86/kernel/geode_32.c F: drivers/char/hw_random/geode-rng.c F: drivers/crypto/geode* F: drivers/video/geode/ -- cgit v1.2.3 From a9582206c526be81054a8be2227e92a4403d2452 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Fri, 5 Mar 2010 13:43:08 -0800 Subject: MAINTAINERS: remove HAYES ESP SERIAL DRIVER Commit f53a2ade0bb9f2a81f473e6469155172a96b7c38 ("tty: esp: remove broken driver") removed it Signed-off-by: Joe Perches Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 7 ------- 1 file changed, 7 deletions(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 347afcd2a6e6..598d4a2e78e1 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -2501,13 +2501,6 @@ L: linux-parisc@vger.kernel.org S: Maintained F: sound/parisc/harmony.* -HAYES ESP SERIAL DRIVER -M: "Andrew J. Robinson" -W: http://www.nyx.net/~arobinso -S: Maintained -F: Documentation/serial/hayes-esp.txt -F: drivers/char/esp.c - HEWLETT-PACKARD SMART2 RAID DRIVER M: Chirag Kantharia L: iss_storagedev@hp.com -- cgit v1.2.3 From dc95ec6fbd9e2426df76b8cf62063d2ee8e32dc2 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Fri, 5 Mar 2010 13:43:09 -0800 Subject: MAINTAINERS: update PERFORMANCE EVENTS F: patterns To match arch/*/kernel perf_event location changes Signed-off-by: Joe Perches Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 598d4a2e78e1..1d2d4caf497f 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -4257,7 +4257,9 @@ M: Ingo Molnar S: Supported F: kernel/perf_event.c F: include/linux/perf_event.h -F: arch/*/*/kernel/perf_event.c +F: arch/*/kernel/perf_event.c +F: arch/*/kernel/*/perf_event.c +F: arch/*/kernel/*/*/perf_event.c F: arch/*/include/asm/perf_event.h F: arch/*/lib/perf_event.c F: arch/*/kernel/perf_callchain.c -- cgit v1.2.3 From 931812cb1b40650e44f6bbb2acb84a129498202f Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Fri, 5 Mar 2010 13:43:10 -0800 Subject: MAINTAINERS: STARMODE RADIO IP (STRIP) moved to staging by commit 955015bb0b42167d14f776ff5947ae2463a974dc Signed-off-by: Joe Perches Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 1d2d4caf497f..0e71fbfec49d 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5195,7 +5195,7 @@ F: drivers/net/starfire* STARMODE RADIO IP (STRIP) PROTOCOL DRIVER S: Orphan -F: drivers/net/wireless/strip.c +F: drivers/staging/strip/strip.c F: include/linux/if_strip.h STRADIS MPEG-2 DECODER DRIVER -- cgit v1.2.3 From e200e0ec9148ad2941e30d59b9552973d49a82b6 Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Fri, 5 Mar 2010 13:43:10 -0800 Subject: MAINTAINERS: WAVELAN moved to staging by commit 0234f84ebb00d36c48062befa5436eef36b71ccd Update patterns Signed-off-by: Joe Perches Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 0e71fbfec49d..43ce11f7bf48 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -5983,7 +5983,7 @@ L: linux-wireless@vger.kernel.org W: http://www.hpl.hp.com/personal/Jean_Tourrilhes/Linux/ S: Maintained F: Documentation/networking/wavelan.txt -F: drivers/net/wireless/wavelan* +F: drivers/staging/wavelan/ WD7000 SCSI DRIVER M: Miroslav Zagorac -- cgit v1.2.3 From 8a6e25357d51d6ecf4ee21e9048f0416a085a79c Mon Sep 17 00:00:00 2001 From: Joe Perches Date: Fri, 5 Mar 2010 13:43:11 -0800 Subject: MAINTAINERS: document and add "Q" patchwork queue entries Patchwork queues show the acceptance/rejection state of submitted patches for various MAINTAINER trees. Document their existence. Signed-off-by: Joe Perches Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- MAINTAINERS | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) (limited to 'MAINTAINERS') diff --git a/MAINTAINERS b/MAINTAINERS index 43ce11f7bf48..e0d5c1a949d9 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -71,6 +71,7 @@ Descriptions of section entries: M: Mail patches to: FullName L: Mailing list that is relevant to this area W: Web-page with status/info + Q: Patchwork web based patch tracking system site T: SCM tree type and location. Type is one of: git, hg, quilt, stgit. S: Status, one of the following: Supported: Someone is actually paid to look after this. @@ -182,6 +183,7 @@ M: Ron Minnich M: Latchesar Ionkov L: v9fs-developer@lists.sourceforge.net W: http://swik.net/v9fs +Q: http://patchwork.kernel.org/project/v9fs-devel/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/ericvh/v9fs.git S: Maintained F: Documentation/filesystems/9p.txt @@ -238,6 +240,7 @@ ACPI M: Len Brown L: linux-acpi@vger.kernel.org W: http://www.lesswatts.org/projects/acpi/ +Q: http://patchwork.kernel.org/project/linux-acpi/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lenb/linux-acpi-2.6.git S: Supported F: drivers/acpi/ @@ -1331,6 +1334,7 @@ BTRFS FILE SYSTEM M: Chris Mason L: linux-btrfs@vger.kernel.org W: http://btrfs.wiki.kernel.org/ +Q: http://patchwork.kernel.org/project/linux-btrfs/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mason/btrfs-unstable.git S: Maintained F: Documentation/filesystems/btrfs.txt @@ -1495,6 +1499,7 @@ M: Steve French L: linux-cifs-client@lists.samba.org (moderated for non-subscribers) L: samba-technical@lists.samba.org (moderated for non-subscribers) W: http://linux-cifs.samba.org/ +Q: http://patchwork.ozlabs.org/project/linux-cifs-client/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/sfrench/cifs-2.6.git S: Supported F: Documentation/filesystems/cifs.txt @@ -1781,6 +1786,7 @@ DEVICE-MAPPER (LVM) P: Alasdair Kergon L: dm-devel@redhat.com W: http://sources.redhat.com/dm +Q: http://patchwork.kernel.org/project/dm-devel/list/ S: Maintained F: Documentation/device-mapper/ F: drivers/md/dm* @@ -2125,6 +2131,7 @@ M: "Theodore Ts'o" M: Andreas Dilger L: linux-ext4@vger.kernel.org W: http://ext4.wiki.kernel.org +Q: http://patchwork.ozlabs.org/project/linux-ext4/list/ S: Maintained F: Documentation/filesystems/ext4.txt F: fs/ext4/ @@ -2709,6 +2716,7 @@ F: drivers/scsi/ips.* IDE SUBSYSTEM M: "David S. Miller" L: linux-ide@vger.kernel.org +Q: http://patchwork.ozlabs.org/project/linux-ide/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/davem/ide-2.6.git S: Maintained F: Documentation/ide/ @@ -2763,6 +2771,7 @@ M: Sean Hefty M: Hal Rosenstock L: linux-rdma@vger.kernel.org W: http://www.openib.org/ +Q: http://patchwork.kernel.org/project/linux-rdma/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/roland/infiniband.git S: Supported F: Documentation/infiniband/ @@ -2782,6 +2791,7 @@ INPUT (KEYBOARD, MOUSE, JOYSTICK, TOUCHSCREEN) DRIVERS M: Dmitry Torokhov M: Dmitry Torokhov L: linux-input@vger.kernel.org +Q: http://patchwork.kernel.org/project/linux-input/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/dtor/input.git S: Maintained F: drivers/input/ @@ -3091,6 +3101,7 @@ F: drivers/hwmon/k8temp.c KCONFIG M: Roman Zippel L: linux-kbuild@vger.kernel.org +Q: http://patchwork.kernel.org/project/linux-kbuild/list/ S: Maintained F: Documentation/kbuild/kconfig-language.txt F: scripts/kconfig/ @@ -3304,6 +3315,7 @@ M: Benjamin Herrenschmidt M: Paul Mackerras W: http://www.penguinppc.org/ L: linuxppc-dev@ozlabs.org +Q: http://patchwork.ozlabs.org/project/linuxppc-dev/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/benh/powerpc.git S: Supported F: Documentation/powerpc/ @@ -3560,6 +3572,7 @@ M: Mauro Carvalho Chehab P: LinuxTV.org Project L: linux-media@vger.kernel.org W: http://linuxtv.org +Q: http://patchwork.kernel.org/project/linux-media/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-2.6.git S: Maintained F: Documentation/dvb/ @@ -3595,8 +3608,9 @@ F: mm/memcontrol.c MEMORY TECHNOLOGY DEVICES (MTD) M: David Woodhouse -W: http://www.linux-mtd.infradead.org/ L: linux-mtd@lists.infradead.org +W: http://www.linux-mtd.infradead.org/ +Q: http://patchwork.ozlabs.org/project/linux-mtd/list/ T: git git://git.infradead.org/mtd-2.6.git S: Maintained F: drivers/mtd/ @@ -3856,6 +3870,7 @@ S: Maintained NETWORKING [WIRELESS] M: "John W. Linville" L: linux-wireless@vger.kernel.org +Q: http://patchwork.kernel.org/project/linux-wireless/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-2.6.git S: Maintained F: net/mac80211/ @@ -3948,6 +3963,7 @@ M: Tony Lindgren L: linux-omap@vger.kernel.org W: http://www.muru.com/linux/omap/ W: http://linux.omap.com/ +Q: http://patchwork.kernel.org/project/linux-omap/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/tmlind/linux-omap-2.6.git S: Maintained F: arch/arm/*omap*/ @@ -4174,6 +4190,7 @@ M: Helge Deller M: "James E.J. Bottomley" L: linux-parisc@vger.kernel.org W: http://www.parisc-linux.org/ +Q: http://patchwork.kernel.org/project/linux-parisc/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/kyle/parisc-2.6.git S: Maintained F: arch/parisc/ @@ -4216,6 +4233,7 @@ F: Documentation/powerpc/eeh-pci-error-recovery.txt PCI SUBSYSTEM M: Jesse Barnes L: linux-pci@vger.kernel.org +Q: http://patchwork.kernel.org/project/linux-pci/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/jbarnes/pci-2.6.git S: Supported F: Documentation/PCI/ @@ -4593,6 +4611,7 @@ F: include/linux/rtc.h REAL TIME CLOCK (RTC) SUBSYSTEM M: Alessandro Zummo L: rtc-linux@googlegroups.com +Q: http://patchwork.ozlabs.org/project/rtc-linux/list/ S: Maintained F: Documentation/rtc.txt F: drivers/rtc/ @@ -4960,6 +4979,7 @@ F: drivers/*/*/*s3c2410* TI DAVINCI MACHINE SUPPORT P: Kevin Hilman M: davinci-linux-open-source@linux.davincidsp.com +Q: http://patchwork.kernel.org/project/linux-davinci/list/ S: Supported F: arch/arm/mach-davinci @@ -5125,6 +5145,7 @@ F: include/sound/soc* SPARC + UltraSPARC (sparc/sparc64) M: "David S. Miller" L: sparclinux@vger.kernel.org +Q: http://patchwork.ozlabs.org/project/sparclinux/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/davem/sparc-2.6.git T: git git://git.kernel.org/pub/scm/linux/kernel/git/davem/sparc-next-2.6.git S: Maintained @@ -5140,6 +5161,7 @@ SPI SUBSYSTEM M: David Brownell M: Grant Likely L: spi-devel-general@lists.sourceforge.net +Q: http://patchwork.kernel.org/project/spi-devel-general/list/ S: Maintained F: Documentation/spi/ F: drivers/spi/ @@ -5216,6 +5238,7 @@ SUPERH M: Paul Mundt L: linux-sh@vger.kernel.org W: http://www.linux-sh.org +Q: http://patchwork.kernel.org/project/linux-sh/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/lethal/sh-2.6.git S: Supported F: Documentation/sh/ @@ -6179,6 +6202,7 @@ F: drivers/serial/zs.* THE REST M: Linus Torvalds L: linux-kernel@vger.kernel.org +Q: http://patchwork.kernel.org/project/LKML/list/ T: git git://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux-2.6.git S: Buried alive in reporters F: * -- cgit v1.2.3