From 7b76bfc86757ca5e7b0a62f50003752da77c115b Mon Sep 17 00:00:00 2001 From: Dick Streefland Date: Tue, 6 Oct 2009 22:35:40 +0200 Subject: kconfig: simplification of scripts/extract-ikconfig I've rewritten the extract-ikconfig script to extract the kernel configuration from a kernel compiled with CONFIG_IKCONFIG. The main motivation for the rewrite was to remove the dependency on the external C program binoffset.c, which is compiled on the initial run. The binoffset executable is invoked with a relative path, which means that the old script can only be run from the top of the kernel tree, and only when you have write permission in the scripts directory. The new script uses tr/grep/tail/zcat only, and can be invoked from anywhere. The binoffset.c program has been removed. This script requires GNU grep 2.5 (released 2002-03-13) or higher, because the -o option was introduced in that version. Signed-off-by: Dick Streefland LKML-Reference: <20091006203540.GA14634@streefland.net> Tested-by: Steven Rostedt Cc: Sam Ravnborg Signed-off-by: Steven Rostedt --- Documentation/dontdiff | 1 - 1 file changed, 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/dontdiff b/Documentation/dontdiff index e1efc400bed..bc01a5a9d66 100644 --- a/Documentation/dontdiff +++ b/Documentation/dontdiff @@ -68,7 +68,6 @@ autoconf.h* bbootsect bin2c binkernel.spec -binoffset bootsect bounds.h bsetup -- cgit v1.2.3-70-g09d2 From 63978ab3e3e963db28093b53bb4598f2702e1ad7 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Mon, 14 Dec 2009 12:48:35 +0100 Subject: sound: add Edirol UA-101 support Add experimental support for the Edirol UA-101 audio/MIDI interface. Signed-off-by: Clemens Ladisch Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 7 + sound/usb/Kconfig | 12 + sound/usb/Makefile | 2 + sound/usb/ua101.c | 1457 +++++++++++++++++++++++ sound/usb/usbaudio.c | 54 - sound/usb/usbaudio.h | 1 - sound/usb/usbquirks.h | 31 - 7 files changed, 1478 insertions(+), 86 deletions(-) create mode 100644 sound/usb/ua101.c (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 8923597bd2b..7a0a4a9dc18 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1791,6 +1791,13 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. The power-management is supported. + Module snd-ua101 + ---------------- + + Module for the Edirol UA-101 audio/MIDI interface. + + This module supports multiple devices, autoprobe and hotplugging. + Module snd-usb-audio -------------------- diff --git a/sound/usb/Kconfig b/sound/usb/Kconfig index 73525c048e7..8c2925814ce 100644 --- a/sound/usb/Kconfig +++ b/sound/usb/Kconfig @@ -21,6 +21,18 @@ config SND_USB_AUDIO To compile this driver as a module, choose M here: the module will be called snd-usb-audio. +config SND_USB_UA101 + tristate "Edirol UA-101 driver (EXPERIMENTAL)" + depends on EXPERIMENTAL + select SND_PCM + select SND_RAWMIDI + help + Say Y here to include support for the Edirol UA-101 audio/MIDI + interface. + + To compile this driver as a module, choose M here: the module + will be called snd-ua101. + config SND_USB_USX2Y tristate "Tascam US-122, US-224 and US-428 USB driver" depends on X86 || PPC || ALPHA diff --git a/sound/usb/Makefile b/sound/usb/Makefile index abb288bfe35..5bf64aef955 100644 --- a/sound/usb/Makefile +++ b/sound/usb/Makefile @@ -4,9 +4,11 @@ snd-usb-audio-objs := usbaudio.o usbmixer.o snd-usb-lib-objs := usbmidi.o +snd-ua101-objs := ua101.o # Toplevel Module Dependency obj-$(CONFIG_SND_USB_AUDIO) += snd-usb-audio.o snd-usb-lib.o +obj-$(CONFIG_SND_USB_UA101) += snd-ua101.o snd-usb-lib.o obj-$(CONFIG_SND_USB_USX2Y) += snd-usb-lib.o obj-$(CONFIG_SND_USB_US122L) += snd-usb-lib.o diff --git a/sound/usb/ua101.c b/sound/usb/ua101.c new file mode 100644 index 00000000000..ab9f8a2e193 --- /dev/null +++ b/sound/usb/ua101.c @@ -0,0 +1,1457 @@ +/* + * Edirol UA-101 driver + * Copyright (c) Clemens Ladisch + * + * This driver is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver. If not, see . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "usbaudio.h" + +MODULE_DESCRIPTION("Edirol UA-101 driver"); +MODULE_AUTHOR("Clemens Ladisch "); +MODULE_LICENSE("GPL v2"); +MODULE_SUPPORTED_DEVICE("{{Edirol,UA-101}}"); + +/* I use my UA-1A for testing because I don't have a UA-101 ... */ +#define UA1A_HACK + +/* + * Should not be lower than the minimum scheduling delay of the host + * controller. Some Intel controllers need more than one frame; as long as + * that driver doesn't tell us about this, use 1.5 frames just to be sure. + */ +#define MIN_QUEUE_LENGTH 12 +/* Somewhat random. */ +#define MAX_QUEUE_LENGTH 30 +/* + * This magic value optimizes memory usage efficiency for the UA-101's packet + * sizes at all sample rates, taking into account the stupid cache pool sizes + * that usb_buffer_alloc() uses. + */ +#define DEFAULT_QUEUE_LENGTH 21 + +#define MAX_PACKET_SIZE 672 /* hardware specific */ +#define MAX_MEMORY_BUFFERS DIV_ROUND_UP(MAX_QUEUE_LENGTH, \ + PAGE_SIZE / MAX_PACKET_SIZE) + +static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; +static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; +static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static unsigned int queue_length = 21; + +module_param_array(index, int, NULL, 0444); +MODULE_PARM_DESC(index, "card index"); +module_param_array(id, charp, NULL, 0444); +MODULE_PARM_DESC(id, "ID string"); +module_param_array(enable, bool, NULL, 0444); +MODULE_PARM_DESC(enable, "enable card"); +module_param(queue_length, uint, 0644); +MODULE_PARM_DESC(queue_length, "USB queue length in microframes, " + __stringify(MIN_QUEUE_LENGTH)"-"__stringify(MAX_QUEUE_LENGTH)); + +enum { + INTF_PLAYBACK, + INTF_CAPTURE, + INTF_MIDI, + + INTF_COUNT +}; + +/* bits in struct ua101::states */ +enum { + USB_CAPTURE_RUNNING, + USB_PLAYBACK_RUNNING, + ALSA_CAPTURE_OPEN, + ALSA_PLAYBACK_OPEN, + ALSA_CAPTURE_RUNNING, + ALSA_PLAYBACK_RUNNING, + CAPTURE_URB_COMPLETED, + PLAYBACK_URB_COMPLETED, + DISCONNECTED, +}; + +struct ua101 { + struct usb_device *dev; + struct snd_card *card; + struct usb_interface *intf[INTF_COUNT]; + int card_index; + struct snd_pcm *pcm; + struct list_head midi_list; + u64 format_bit; + unsigned int rate; + unsigned int packets_per_second; + spinlock_t lock; + struct mutex mutex; + unsigned long states; + + /* FIFO to synchronize playback rate to capture rate */ + unsigned int rate_feedback_start; + unsigned int rate_feedback_count; + u8 rate_feedback[MAX_QUEUE_LENGTH]; + + struct list_head ready_playback_urbs; + struct tasklet_struct playback_tasklet; + wait_queue_head_t alsa_capture_wait; + wait_queue_head_t rate_feedback_wait; + wait_queue_head_t alsa_playback_wait; + struct ua101_stream { + struct snd_pcm_substream *substream; + unsigned int usb_pipe; + unsigned int channels; + unsigned int frame_bytes; + unsigned int max_packet_bytes; + unsigned int period_pos; + unsigned int buffer_pos; + unsigned int queue_length; + struct ua101_urb { + struct urb urb; + struct usb_iso_packet_descriptor iso_frame_desc[1]; + struct list_head ready_list; + } *urbs[MAX_QUEUE_LENGTH]; + struct { + unsigned int size; + void *addr; + dma_addr_t dma; + } buffers[MAX_MEMORY_BUFFERS]; + } capture, playback; + + unsigned int fps[10]; + unsigned int frame_counter; +}; + +static DEFINE_MUTEX(devices_mutex); +static unsigned int devices_used; +static struct usb_driver ua101_driver; + +static void abort_alsa_playback(struct ua101 *ua); +static void abort_alsa_capture(struct ua101 *ua); + +/* allocate virtual buffer; may be called more than once */ +static int snd_pcm_alloc_vmalloc_buffer(struct snd_pcm_substream *subs, + size_t size) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + + if (runtime->dma_area) { + if (runtime->dma_bytes >= size) + return 0; /* already large enough */ + vfree(runtime->dma_area); + } + runtime->dma_area = vmalloc_user(size); + if (!runtime->dma_area) + return -ENOMEM; + runtime->dma_bytes = size; + return 0; +} + +/* free virtual buffer; may be called more than once */ +static int snd_pcm_free_vmalloc_buffer(struct snd_pcm_substream *subs) +{ + struct snd_pcm_runtime *runtime = subs->runtime; + + vfree(runtime->dma_area); + runtime->dma_area = NULL; + return 0; +} + +/* get the physical page pointer at the given offset */ +static struct page *snd_pcm_get_vmalloc_page(struct snd_pcm_substream *subs, + unsigned long offset) +{ + void *pageptr = subs->runtime->dma_area + offset; + return vmalloc_to_page(pageptr); +} + +static const char *usb_error_string(int err) +{ + switch (err) { + case -ENODEV: + return "no device"; + case -ENOENT: + return "endpoint not enabled"; + case -EPIPE: + return "endpoint stalled"; + case -ENOSPC: + return "not enough bandwidth"; + case -ESHUTDOWN: + return "device disabled"; + case -EHOSTUNREACH: + return "device suspended"; + case -EINVAL: + case -EAGAIN: + case -EFBIG: + case -EMSGSIZE: + return "internal error"; + default: + return "unknown error"; + } +} + +static void abort_usb_capture(struct ua101 *ua) +{ + if (test_and_clear_bit(USB_CAPTURE_RUNNING, &ua->states)) { + wake_up(&ua->alsa_capture_wait); + wake_up(&ua->rate_feedback_wait); + } +} + +static void abort_usb_playback(struct ua101 *ua) +{ + if (test_and_clear_bit(USB_PLAYBACK_RUNNING, &ua->states)) + wake_up(&ua->alsa_playback_wait); +} + +static void playback_urb_complete(struct urb *usb_urb) +{ + struct ua101_urb *urb = (struct ua101_urb *)usb_urb; + struct ua101 *ua = urb->urb.context; + unsigned long flags; + + if (unlikely(urb->urb.status == -ENOENT || /* unlinked */ + urb->urb.status == -ENODEV || /* device removed */ + urb->urb.status == -ECONNRESET || /* unlinked */ + urb->urb.status == -ESHUTDOWN)) { /* device disabled */ + abort_usb_playback(ua); + abort_alsa_playback(ua); + return; + } + + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) { + /* append URB to FIFO */ + spin_lock_irqsave(&ua->lock, flags); + list_add_tail(&urb->ready_list, &ua->ready_playback_urbs); + if (ua->rate_feedback_count > 0) + tasklet_schedule(&ua->playback_tasklet); + ua->playback.substream->runtime->delay -= + urb->urb.iso_frame_desc[0].length / + ua->playback.frame_bytes; + spin_unlock_irqrestore(&ua->lock, flags); + } +} + +static void first_playback_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + + urb->complete = playback_urb_complete; + playback_urb_complete(urb); + + set_bit(PLAYBACK_URB_COMPLETED, &ua->states); + wake_up(&ua->alsa_playback_wait); +} + +/* copy data from the ALSA ring buffer into the URB buffer */ +static bool copy_playback_data(struct ua101_stream *stream, struct urb *urb, + unsigned int frames) +{ + struct snd_pcm_runtime *runtime; + unsigned int frame_bytes, frames1; + const u8 *source; + + runtime = stream->substream->runtime; + frame_bytes = stream->frame_bytes; + source = runtime->dma_area + stream->buffer_pos * frame_bytes; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(urb->transfer_buffer, source, frames * frame_bytes); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(urb->transfer_buffer, source, frames1 * frame_bytes); + memcpy(urb->transfer_buffer + frames1 * frame_bytes, + runtime->dma_area, (frames - frames1) * frame_bytes); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + return true; + } + return false; +} + +static inline void add_with_wraparound(struct ua101 *ua, + unsigned int *value, unsigned int add) +{ + *value += add; + if (*value >= ua->playback.queue_length) + *value -= ua->playback.queue_length; +} + +static void playback_tasklet(unsigned long data) +{ + struct ua101 *ua = (void *)data; + unsigned long flags; + unsigned int frames; + struct ua101_urb *urb; + bool do_period_elapsed = false; + int err; + + if (unlikely(!test_bit(USB_PLAYBACK_RUNNING, &ua->states))) + return; + + /* + * Synchronizing the playback rate to the capture rate is done by using + * the same sequence of packet sizes for both streams. + * Submitting a playback URB therefore requires both a ready URB and + * the size of the corresponding capture packet, i.e., both playback + * and capture URBs must have been completed. Since the USB core does + * not guarantee that playback and capture complete callbacks are + * called alternately, we use two FIFOs for packet sizes and read URBs; + * submitting playback URBs is possible as long as both FIFOs are + * nonempty. + */ + spin_lock_irqsave(&ua->lock, flags); + while (ua->rate_feedback_count > 0 && + !list_empty(&ua->ready_playback_urbs)) { + /* take packet size out of FIFO */ + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + + /* take URB out of FIFO */ + urb = list_first_entry(&ua->ready_playback_urbs, + struct ua101_urb, ready_list); + list_del(&urb->ready_list); + + /* fill packet with data or silence */ + urb->urb.iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + do_period_elapsed |= copy_playback_data(&ua->playback, + &urb->urb, + frames); + else + memset(urb->urb.transfer_buffer, 0, + urb->urb.iso_frame_desc[0].length); + + /* and off you go ... */ + err = usb_submit_urb(&urb->urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + spin_unlock_irqrestore(&ua->lock, flags); + abort_usb_playback(ua); + abort_alsa_playback(ua); + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + return; + } + ua->playback.substream->runtime->delay += frames; + } + spin_unlock_irqrestore(&ua->lock, flags); + if (do_period_elapsed) + snd_pcm_period_elapsed(ua->playback.substream); +} + +/* copy data from the URB buffer into the ALSA ring buffer */ +static bool copy_capture_data(struct ua101_stream *stream, struct urb *urb, + unsigned int frames) +{ + struct snd_pcm_runtime *runtime; + unsigned int frame_bytes, frames1; + u8 *dest; + + runtime = stream->substream->runtime; + frame_bytes = stream->frame_bytes; + dest = runtime->dma_area + stream->buffer_pos * frame_bytes; + if (stream->buffer_pos + frames <= runtime->buffer_size) { + memcpy(dest, urb->transfer_buffer, frames * frame_bytes); + } else { + /* wrap around at end of ring buffer */ + frames1 = runtime->buffer_size - stream->buffer_pos; + memcpy(dest, urb->transfer_buffer, frames1 * frame_bytes); + memcpy(runtime->dma_area, + urb->transfer_buffer + frames1 * frame_bytes, + (frames - frames1) * frame_bytes); + } + + stream->buffer_pos += frames; + if (stream->buffer_pos >= runtime->buffer_size) + stream->buffer_pos -= runtime->buffer_size; + stream->period_pos += frames; + if (stream->period_pos >= runtime->period_size) { + stream->period_pos -= runtime->period_size; + return true; + } + return false; +} + +static void capture_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + struct ua101_stream *stream = &ua->capture; + unsigned long flags; + unsigned int frames, write_ptr; + bool do_period_elapsed; + int err; + + if (unlikely(urb->status == -ENOENT || /* unlinked */ + urb->status == -ENODEV || /* device removed */ + urb->status == -ECONNRESET || /* unlinked */ + urb->status == -ESHUTDOWN)) /* device disabled */ + goto stream_stopped; + + if (urb->status >= 0 && urb->iso_frame_desc[0].status >= 0) + frames = urb->iso_frame_desc[0].actual_length / + stream->frame_bytes; + else + frames = 0; + + spin_lock_irqsave(&ua->lock, flags); + + if (frames > 0 && test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) + do_period_elapsed = copy_capture_data(stream, urb, frames); + else + do_period_elapsed = false; + + if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) { + err = usb_submit_urb(urb, GFP_ATOMIC); + if (unlikely(err < 0)) { + spin_unlock_irqrestore(&ua->lock, flags); + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + goto stream_stopped; + } + + /* append packet size to FIFO */ + write_ptr = ua->rate_feedback_start; + add_with_wraparound(ua, &write_ptr, ua->rate_feedback_count); + ua->rate_feedback[write_ptr] = frames; + if (ua->rate_feedback_count < ua->playback.queue_length) { + ua->rate_feedback_count++; + if (ua->rate_feedback_count == + ua->playback.queue_length) + wake_up(&ua->rate_feedback_wait); + } else { + /* + * Ring buffer overflow; this happens when the playback + * stream is not running. Throw away the oldest entry, + * so that the playback stream, when it starts, sees + * the most recent packet sizes. + */ + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + } + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states) && + !list_empty(&ua->ready_playback_urbs)) + tasklet_schedule(&ua->playback_tasklet); + } + + spin_unlock_irqrestore(&ua->lock, flags); + + if (do_period_elapsed) + snd_pcm_period_elapsed(stream->substream); + + /* for debugging: measure the sample rate relative to the USB clock */ + ua->fps[ua->frame_counter++ / ua->packets_per_second] += frames; + if (ua->frame_counter >= ARRAY_SIZE(ua->fps) * ua->packets_per_second) { + printk(KERN_DEBUG "capture rate:"); + for (frames = 0; frames < ARRAY_SIZE(ua->fps); ++frames) + printk(KERN_CONT " %u", ua->fps[frames]); + printk(KERN_CONT "\n"); + memset(ua->fps, 0, sizeof(ua->fps)); + ua->frame_counter = 0; + } + return; + +stream_stopped: + abort_usb_playback(ua); + abort_usb_capture(ua); + abort_alsa_playback(ua); + abort_alsa_capture(ua); +} + +static void first_capture_urb_complete(struct urb *urb) +{ + struct ua101 *ua = urb->context; + + urb->complete = capture_urb_complete; + capture_urb_complete(urb); + + set_bit(CAPTURE_URB_COMPLETED, &ua->states); + wake_up(&ua->alsa_capture_wait); +} + +static int submit_stream_urbs(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) { + int err = usb_submit_urb(&stream->urbs[i]->urb, GFP_KERNEL); + if (err < 0) { + dev_err(&ua->dev->dev, "USB request error %d: %s\n", + err, usb_error_string(err)); + return err; + } + } + return 0; +} + +static void kill_stream_urbs(struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) + usb_kill_urb(&stream->urbs[i]->urb); +} + +static int enable_iso_interface(struct ua101 *ua, unsigned int intf_index) +{ + struct usb_host_interface *alts; + + alts = ua->intf[intf_index]->cur_altsetting; + if (alts->desc.bAlternateSetting != 1) { + int err = usb_set_interface(ua->dev, + alts->desc.bInterfaceNumber, 1); + if (err < 0) { + dev_err(&ua->dev->dev, + "cannot initialize interface; error %d: %s\n", + err, usb_error_string(err)); + return err; + } + } + return 0; +} + +static void disable_iso_interface(struct ua101 *ua, unsigned int intf_index) +{ + struct usb_host_interface *alts; + + alts = ua->intf[intf_index]->cur_altsetting; + if (alts->desc.bAlternateSetting != 0) { + int err = usb_set_interface(ua->dev, + alts->desc.bInterfaceNumber, 0); + if (err < 0 && !test_bit(DISCONNECTED, &ua->states)) + dev_warn(&ua->dev->dev, + "interface reset failed; error %d: %s\n", + err, usb_error_string(err)); + } +} + +static void stop_usb_capture(struct ua101 *ua) +{ + clear_bit(USB_CAPTURE_RUNNING, &ua->states); + + kill_stream_urbs(&ua->capture); + + disable_iso_interface(ua, INTF_CAPTURE); +} + +static int start_usb_capture(struct ua101 *ua) +{ + int err; + + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + + if (test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return 0; + + kill_stream_urbs(&ua->capture); + + err = enable_iso_interface(ua, INTF_CAPTURE); + if (err < 0) + return err; + + clear_bit(CAPTURE_URB_COMPLETED, &ua->states); + ua->capture.urbs[0]->urb.complete = first_capture_urb_complete; + ua->rate_feedback_start = 0; + ua->rate_feedback_count = 0; + + set_bit(USB_CAPTURE_RUNNING, &ua->states); + err = submit_stream_urbs(ua, &ua->capture); + if (err < 0) + stop_usb_capture(ua); + return err; +} + +static void stop_usb_playback(struct ua101 *ua) +{ + clear_bit(USB_PLAYBACK_RUNNING, &ua->states); + + kill_stream_urbs(&ua->playback); + + tasklet_kill(&ua->playback_tasklet); + + disable_iso_interface(ua, INTF_PLAYBACK); +} + +static int start_usb_playback(struct ua101 *ua) +{ + unsigned int i, frames; + struct urb *urb; + int err = 0; + + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + + if (test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return 0; + + kill_stream_urbs(&ua->playback); + tasklet_kill(&ua->playback_tasklet); + + err = enable_iso_interface(ua, INTF_PLAYBACK); + if (err < 0) + return err; + + clear_bit(PLAYBACK_URB_COMPLETED, &ua->states); + ua->playback.urbs[0]->urb.complete = + first_playback_urb_complete; + spin_lock_irq(&ua->lock); + INIT_LIST_HEAD(&ua->ready_playback_urbs); + spin_unlock_irq(&ua->lock); + + /* + * We submit the initial URBs all at once, so we have to wait for the + * packet size FIFO to be full. + */ + wait_event(ua->rate_feedback_wait, + ua->rate_feedback_count >= ua->playback.queue_length || + !test_bit(USB_CAPTURE_RUNNING, &ua->states) || + test_bit(DISCONNECTED, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) { + stop_usb_playback(ua); + return -ENODEV; + } + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) { + stop_usb_playback(ua); + return -EIO; + } + + for (i = 0; i < ua->playback.queue_length; ++i) { + /* all initial URBs contain silence */ + spin_lock_irq(&ua->lock); + frames = ua->rate_feedback[ua->rate_feedback_start]; + add_with_wraparound(ua, &ua->rate_feedback_start, 1); + ua->rate_feedback_count--; + spin_unlock_irq(&ua->lock); + urb = &ua->playback.urbs[i]->urb; + urb->iso_frame_desc[0].length = + frames * ua->playback.frame_bytes; + memset(urb->transfer_buffer, 0, + urb->iso_frame_desc[0].length); + } + + set_bit(USB_PLAYBACK_RUNNING, &ua->states); + err = submit_stream_urbs(ua, &ua->playback); + if (err < 0) + stop_usb_playback(ua); + return err; +} + +static void abort_alsa_capture(struct ua101 *ua) +{ + if (test_bit(ALSA_CAPTURE_RUNNING, &ua->states)) + snd_pcm_stop(ua->capture.substream, SNDRV_PCM_STATE_XRUN); +} + +static void abort_alsa_playback(struct ua101 *ua) +{ + if (test_bit(ALSA_PLAYBACK_RUNNING, &ua->states)) + snd_pcm_stop(ua->playback.substream, SNDRV_PCM_STATE_XRUN); +} + +static int set_stream_hw(struct ua101 *ua, struct snd_pcm_substream *substream, + unsigned int channels) +{ + int err; + + substream->runtime->hw.info = + SNDRV_PCM_INFO_MMAP | + SNDRV_PCM_INFO_MMAP_VALID | + SNDRV_PCM_INFO_BATCH | + SNDRV_PCM_INFO_INTERLEAVED | + SNDRV_PCM_INFO_BLOCK_TRANSFER | + SNDRV_PCM_INFO_FIFO_IN_FRAMES; + substream->runtime->hw.formats = ua->format_bit; + substream->runtime->hw.rates = snd_pcm_rate_to_rate_bit(ua->rate); + substream->runtime->hw.rate_min = ua->rate; + substream->runtime->hw.rate_max = ua->rate; + substream->runtime->hw.channels_min = channels; + substream->runtime->hw.channels_max = channels; + substream->runtime->hw.buffer_bytes_max = 45000 * 1024; + substream->runtime->hw.period_bytes_min = 1; + substream->runtime->hw.period_bytes_max = UINT_MAX; + substream->runtime->hw.periods_min = 2; + substream->runtime->hw.periods_max = UINT_MAX; + err = snd_pcm_hw_constraint_minmax(substream->runtime, + SNDRV_PCM_HW_PARAM_PERIOD_TIME, + 1500000 / ua->packets_per_second, + 8192000); + if (err < 0) + return err; + err = snd_pcm_hw_constraint_msbits(substream->runtime, 0, 32, 24); + return err; +} + +static int capture_pcm_open(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + ua->capture.substream = substream; + err = set_stream_hw(ua, substream, ua->capture.channels); + if (err < 0) + return err; + substream->runtime->hw.fifo_size = + DIV_ROUND_CLOSEST(ua->rate, ua->packets_per_second); + substream->runtime->delay = substream->runtime->hw.fifo_size; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + set_bit(ALSA_CAPTURE_OPEN, &ua->states); + mutex_unlock(&ua->mutex); + return err; +} + +static int playback_pcm_open(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + ua->playback.substream = substream; + err = set_stream_hw(ua, substream, ua->playback.channels); + if (err < 0) + return err; + substream->runtime->hw.fifo_size = + DIV_ROUND_CLOSEST(ua->rate * ua->playback.queue_length, + ua->packets_per_second); + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err < 0) + goto error; + err = start_usb_playback(ua); + if (err < 0) { + if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) + stop_usb_capture(ua); + goto error; + } + set_bit(ALSA_PLAYBACK_OPEN, &ua->states); +error: + mutex_unlock(&ua->mutex); + return err; +} + +static int capture_pcm_close(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + + mutex_lock(&ua->mutex); + clear_bit(ALSA_CAPTURE_OPEN, &ua->states); + if (!test_bit(ALSA_PLAYBACK_OPEN, &ua->states)) + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + return 0; +} + +static int playback_pcm_close(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + + mutex_lock(&ua->mutex); + stop_usb_playback(ua); + clear_bit(ALSA_PLAYBACK_OPEN, &ua->states); + if (!test_bit(ALSA_CAPTURE_OPEN, &ua->states)) + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + return 0; +} + +static int capture_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + return snd_pcm_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int playback_pcm_hw_params(struct snd_pcm_substream *substream, + struct snd_pcm_hw_params *hw_params) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + err = start_usb_playback(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + return snd_pcm_alloc_vmalloc_buffer(substream, + params_buffer_bytes(hw_params)); +} + +static int ua101_pcm_hw_free(struct snd_pcm_substream *substream) +{ + snd_pcm_free_vmalloc_buffer(substream); + return 0; +} + +static int capture_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + /* + * The EHCI driver schedules the first packet of an iso stream at 10 ms + * in the future, i.e., no data is actually captured for that long. + * Take the wait here so that the stream is known to be actually + * running when the start trigger has been called. + */ + wait_event(ua->alsa_capture_wait, + test_bit(CAPTURE_URB_COMPLETED, &ua->states) || + !test_bit(USB_CAPTURE_RUNNING, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return -EIO; + + ua->capture.period_pos = 0; + ua->capture.buffer_pos = 0; + return 0; +} + +static int playback_pcm_prepare(struct snd_pcm_substream *substream) +{ + struct ua101 *ua = substream->private_data; + int err; + + mutex_lock(&ua->mutex); + err = start_usb_capture(ua); + if (err >= 0) + err = start_usb_playback(ua); + mutex_unlock(&ua->mutex); + if (err < 0) + return err; + + /* see the comment in capture_pcm_prepare() */ + wait_event(ua->alsa_playback_wait, + test_bit(PLAYBACK_URB_COMPLETED, &ua->states) || + !test_bit(USB_PLAYBACK_RUNNING, &ua->states)); + if (test_bit(DISCONNECTED, &ua->states)) + return -ENODEV; + if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return -EIO; + + substream->runtime->delay = 0; + ua->playback.period_pos = 0; + ua->playback.buffer_pos = 0; + return 0; +} + +static int capture_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua101 *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!test_bit(USB_CAPTURE_RUNNING, &ua->states)) + return -EIO; + set_bit(ALSA_CAPTURE_RUNNING, &ua->states); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + clear_bit(ALSA_CAPTURE_RUNNING, &ua->states); + return 0; + default: + return -EINVAL; + } +} + +static int playback_pcm_trigger(struct snd_pcm_substream *substream, int cmd) +{ + struct ua101 *ua = substream->private_data; + + switch (cmd) { + case SNDRV_PCM_TRIGGER_START: + if (!test_bit(USB_PLAYBACK_RUNNING, &ua->states)) + return -EIO; + set_bit(ALSA_PLAYBACK_RUNNING, &ua->states); + return 0; + case SNDRV_PCM_TRIGGER_STOP: + clear_bit(ALSA_PLAYBACK_RUNNING, &ua->states); + return 0; + default: + return -EINVAL; + } +} + +static inline snd_pcm_uframes_t ua101_pcm_pointer(struct ua101 *ua, + struct ua101_stream *stream) +{ + unsigned long flags; + unsigned int pos; + + spin_lock_irqsave(&ua->lock, flags); + pos = stream->buffer_pos; + spin_unlock_irqrestore(&ua->lock, flags); + return pos; +} + +static snd_pcm_uframes_t capture_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct ua101 *ua = subs->private_data; + + return ua101_pcm_pointer(ua, &ua->capture); +} + +static snd_pcm_uframes_t playback_pcm_pointer(struct snd_pcm_substream *subs) +{ + struct ua101 *ua = subs->private_data; + + return ua101_pcm_pointer(ua, &ua->playback); +} + +static struct snd_pcm_ops capture_pcm_ops = { + .open = capture_pcm_open, + .close = capture_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = capture_pcm_hw_params, + .hw_free = ua101_pcm_hw_free, + .prepare = capture_pcm_prepare, + .trigger = capture_pcm_trigger, + .pointer = capture_pcm_pointer, + .page = snd_pcm_get_vmalloc_page, +}; + +static struct snd_pcm_ops playback_pcm_ops = { + .open = playback_pcm_open, + .close = playback_pcm_close, + .ioctl = snd_pcm_lib_ioctl, + .hw_params = playback_pcm_hw_params, + .hw_free = ua101_pcm_hw_free, + .prepare = playback_pcm_prepare, + .trigger = playback_pcm_trigger, + .pointer = playback_pcm_pointer, + .page = snd_pcm_get_vmalloc_page, +}; + +static const struct uac_format_type_i_discrete_descriptor * +find_format_descriptor(struct usb_interface *interface) +{ + struct usb_host_interface *alt; + u8 *extra; + int extralen; + + if (interface->num_altsetting != 2) { + dev_err(&interface->dev, "invalid num_altsetting\n"); + return NULL; + } + + alt = &interface->altsetting[0]; + if (alt->desc.bNumEndpoints != 0) { + dev_err(&interface->dev, "invalid bNumEndpoints\n"); + return NULL; + } + + alt = &interface->altsetting[1]; + if (alt->desc.bNumEndpoints != 1) { + dev_err(&interface->dev, "invalid bNumEndpoints\n"); + return NULL; + } + + extra = alt->extra; + extralen = alt->extralen; + while (extralen >= sizeof(struct usb_descriptor_header)) { + struct uac_format_type_i_discrete_descriptor *desc; + + desc = (struct uac_format_type_i_discrete_descriptor *)extra; + if (desc->bLength > extralen) { + dev_err(&interface->dev, "descriptor overflow\n"); + return NULL; + } + if (desc->bLength == UAC_FORMAT_TYPE_I_DISCRETE_DESC_SIZE(1) && + desc->bDescriptorType == USB_DT_CS_INTERFACE && + desc->bDescriptorSubtype == UAC_FORMAT_TYPE) { + if (desc->bFormatType != UAC_FORMAT_TYPE_I_PCM || + desc->bSamFreqType != 1) { + dev_err(&interface->dev, + "invalid format type\n"); + return NULL; + } + return desc; + } + extralen -= desc->bLength; + extra += desc->bLength; + } + dev_err(&interface->dev, "sample format descriptor not found\n"); + return NULL; +} + +static int detect_usb_format(struct ua101 *ua) +{ + const struct uac_format_type_i_discrete_descriptor *fmt_capture; + const struct uac_format_type_i_discrete_descriptor *fmt_playback; + const struct usb_endpoint_descriptor *epd; + unsigned int rate2; + + fmt_capture = find_format_descriptor(ua->intf[INTF_CAPTURE]); + fmt_playback = find_format_descriptor(ua->intf[INTF_PLAYBACK]); + if (!fmt_capture || !fmt_playback) + return -ENXIO; + + switch (fmt_capture->bSubframeSize) { + case 3: + ua->format_bit = SNDRV_PCM_FMTBIT_S24_3LE; + break; + case 4: + ua->format_bit = SNDRV_PCM_FMTBIT_S32_LE; + break; + default: + dev_err(&ua->dev->dev, "sample width is not 24 or 32 bits\n"); + return -ENXIO; + } + if (fmt_capture->bSubframeSize != fmt_playback->bSubframeSize) { + dev_err(&ua->dev->dev, + "playback/capture sample widths do not match\n"); + return -ENXIO; + } + + if (fmt_capture->bBitResolution != 24 || + fmt_playback->bBitResolution != 24) { + dev_err(&ua->dev->dev, "sample width is not 24 bits\n"); + return -ENXIO; + } + + ua->rate = combine_triple(fmt_capture->tSamFreq[0]); + rate2 = combine_triple(fmt_playback->tSamFreq[0]); + if (ua->rate != rate2) { + dev_err(&ua->dev->dev, + "playback/capture rates do not match: %u/%u\n", + rate2, ua->rate); + return -ENXIO; + } + + switch (ua->dev->speed) { + case USB_SPEED_FULL: + ua->packets_per_second = 1000; + break; + case USB_SPEED_HIGH: + ua->packets_per_second = 8000; + break; + default: + dev_err(&ua->dev->dev, "unknown device speed\n"); + return -ENXIO; + } + + ua->capture.channels = fmt_capture->bNrChannels; + ua->playback.channels = fmt_playback->bNrChannels; + ua->capture.frame_bytes = + fmt_capture->bSubframeSize * ua->capture.channels; + ua->playback.frame_bytes = + fmt_playback->bSubframeSize * ua->playback.channels; + + epd = &ua->intf[INTF_CAPTURE]->altsetting[1].endpoint[0].desc; + if (!usb_endpoint_is_isoc_in(epd)) { + dev_err(&ua->dev->dev, "invalid capture endpoint\n"); + return -ENXIO; + } + ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->capture.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize); + + epd = &ua->intf[INTF_PLAYBACK]->altsetting[1].endpoint[0].desc; + if (!usb_endpoint_is_isoc_out(epd)) { + dev_err(&ua->dev->dev, "invalid playback endpoint\n"); + return -ENXIO; + } + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, usb_endpoint_num(epd)); + ua->playback.max_packet_bytes = le16_to_cpu(epd->wMaxPacketSize); + return 0; +} + +static int alloc_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int remaining_packets, packets, packets_per_page, i; + size_t size; + + stream->queue_length = queue_length; + stream->queue_length = max(stream->queue_length, + (unsigned int)MIN_QUEUE_LENGTH); + stream->queue_length = min(stream->queue_length, + (unsigned int)MAX_QUEUE_LENGTH); + + /* + * The cache pool sizes used by usb_buffer_alloc() (128, 512, 2048) are + * quite bad when used with the packet sizes of this device (e.g. 280, + * 520, 624). Therefore, we allocate and subdivide entire pages, using + * a smaller buffer only for the last chunk. + */ + remaining_packets = stream->queue_length; + packets_per_page = PAGE_SIZE / stream->max_packet_bytes; + for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) { + packets = min(remaining_packets, packets_per_page); + size = packets * stream->max_packet_bytes; + stream->buffers[i].addr = + usb_buffer_alloc(ua->dev, size, GFP_KERNEL, + &stream->buffers[i].dma); + if (!stream->buffers[i].addr) + return -ENOMEM; + stream->buffers[i].size = size; + remaining_packets -= packets; + if (!remaining_packets) + break; + } + if (remaining_packets) { + dev_err(&ua->dev->dev, "too many packets\n"); + return -ENXIO; + } + return 0; +} + +static void free_stream_buffers(struct ua101 *ua, struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < ARRAY_SIZE(stream->buffers); ++i) + usb_buffer_free(ua->dev, + stream->buffers[i].size, + stream->buffers[i].addr, + stream->buffers[i].dma); +} + +static int alloc_stream_urbs(struct ua101 *ua, struct ua101_stream *stream, + void (*urb_complete)(struct urb *)) +{ + unsigned max_packet_size = stream->max_packet_bytes; + struct ua101_urb *urb; + unsigned int b, u = 0; + + for (b = 0; b < ARRAY_SIZE(stream->buffers); ++b) { + unsigned int size = stream->buffers[b].size; + u8 *addr = stream->buffers[b].addr; + dma_addr_t dma = stream->buffers[b].dma; + + while (size >= max_packet_size) { + if (u >= stream->queue_length) + goto bufsize_error; + urb = kmalloc(sizeof(*urb), GFP_KERNEL); + if (!urb) + return -ENOMEM; + usb_init_urb(&urb->urb); + urb->urb.dev = ua->dev; + urb->urb.pipe = stream->usb_pipe; + urb->urb.transfer_flags = URB_ISO_ASAP | + URB_NO_TRANSFER_DMA_MAP; + urb->urb.transfer_buffer = addr; + urb->urb.transfer_dma = dma; + urb->urb.transfer_buffer_length = max_packet_size; + urb->urb.number_of_packets = 1; + urb->urb.interval = 1; + urb->urb.context = ua; + urb->urb.complete = urb_complete; + urb->urb.iso_frame_desc[0].offset = 0; + urb->urb.iso_frame_desc[0].length = max_packet_size; + stream->urbs[u++] = urb; + size -= max_packet_size; + addr += max_packet_size; + dma += max_packet_size; + } + } + if (u == stream->queue_length) + return 0; +bufsize_error: + dev_err(&ua->dev->dev, "internal buffer size error\n"); + return -ENXIO; +} + +static void free_stream_urbs(struct ua101_stream *stream) +{ + unsigned int i; + + for (i = 0; i < stream->queue_length; ++i) + kfree(stream->urbs[i]); +} + +static void free_usb_related_resources(struct ua101 *ua, + struct usb_interface *interface) +{ + unsigned int i; + + free_stream_urbs(&ua->capture); + free_stream_urbs(&ua->playback); + free_stream_buffers(ua, &ua->capture); + free_stream_buffers(ua, &ua->playback); + + for (i = 0; i < ARRAY_SIZE(ua->intf); ++i) + if (ua->intf[i]) { + usb_set_intfdata(ua->intf[i], NULL); + if (ua->intf[i] != interface) + usb_driver_release_interface(&ua101_driver, + ua->intf[i]); + } +} + +static void ua101_card_free(struct snd_card *card) +{ + struct ua101 *ua = card->private_data; + + mutex_destroy(&ua->mutex); +} + +static int ua101_probe(struct usb_interface *interface, + const struct usb_device_id *usb_id) +{ + static const struct snd_usb_midi_endpoint_info midi_ep = { + .out_cables = 0x0001, + .in_cables = 0x0001 + }; + static const struct snd_usb_audio_quirk midi_quirk = { + .type = QUIRK_MIDI_FIXED_ENDPOINT, + .data = &midi_ep + }; + struct snd_card *card; + struct ua101 *ua; + unsigned int card_index, i; + char usb_path[32]; + int err; + + if (interface->altsetting->desc.bInterfaceNumber != 0) + return -ENODEV; + + mutex_lock(&devices_mutex); + + for (card_index = 0; card_index < SNDRV_CARDS; ++card_index) + if (enable[card_index] && !(devices_used & (1 << card_index))) + break; + if (card_index >= SNDRV_CARDS) { + mutex_unlock(&devices_mutex); + return -ENOENT; + } + err = snd_card_create(index[card_index], id[card_index], THIS_MODULE, + sizeof(*ua), &card); + if (err < 0) { + mutex_unlock(&devices_mutex); + return err; + } + card->private_free = ua101_card_free; + ua = card->private_data; + ua->dev = interface_to_usbdev(interface); + ua->card = card; + ua->card_index = card_index; + INIT_LIST_HEAD(&ua->midi_list); + spin_lock_init(&ua->lock); + mutex_init(&ua->mutex); + INIT_LIST_HEAD(&ua->ready_playback_urbs); + tasklet_init(&ua->playback_tasklet, + playback_tasklet, (unsigned long)ua); + init_waitqueue_head(&ua->alsa_capture_wait); + init_waitqueue_head(&ua->rate_feedback_wait); + init_waitqueue_head(&ua->alsa_playback_wait); + +#ifdef UA1A_HACK + if (ua->dev->descriptor.idProduct == cpu_to_le16(0x0018)) { + ua->intf[2] = interface; + ua->intf[0] = usb_ifnum_to_if(ua->dev, 1); + ua->intf[1] = usb_ifnum_to_if(ua->dev, 2); + usb_driver_claim_interface(&ua101_driver, ua->intf[0], ua); + usb_driver_claim_interface(&ua101_driver, ua->intf[1], ua); + } else { +#endif + ua->intf[0] = interface; + for (i = 1; i < ARRAY_SIZE(ua->intf); ++i) { + ua->intf[i] = usb_ifnum_to_if(ua->dev, i); + if (!ua->intf[i]) { + dev_err(&ua->dev->dev, "interface %u not found\n", i); + err = -ENXIO; + goto probe_error; + } + err = usb_driver_claim_interface(&ua101_driver, + ua->intf[i], ua); + if (err < 0) { + ua->intf[i] = NULL; + err = -EBUSY; + goto probe_error; + } + } +#ifdef UA1A_HACK + } +#endif + + snd_card_set_dev(card, &interface->dev); + +#ifdef UA1A_HACK + if (ua->dev->descriptor.idProduct == cpu_to_le16(0x0018)) { + ua->format_bit = SNDRV_PCM_FMTBIT_S16_LE; + ua->rate = 44100; + ua->packets_per_second = 1000; + ua->capture.channels = 2; + ua->playback.channels = 2; + ua->capture.frame_bytes = 4; + ua->playback.frame_bytes = 4; + ua->capture.usb_pipe = usb_rcvisocpipe(ua->dev, 2); + ua->playback.usb_pipe = usb_sndisocpipe(ua->dev, 1); + ua->capture.max_packet_bytes = 192; + ua->playback.max_packet_bytes = 192; + } else { +#endif + err = detect_usb_format(ua); + if (err < 0) + goto probe_error; +#ifdef UA1A_HACK + } +#endif + + strcpy(card->driver, "UA-101"); + strcpy(card->shortname, "UA-101"); + usb_make_path(ua->dev, usb_path, sizeof(usb_path)); + snprintf(ua->card->longname, sizeof(ua->card->longname), + "EDIROL UA-101 (serial %s), %u Hz at %s, %s speed", + ua->dev->serial ? ua->dev->serial : "?", ua->rate, usb_path, + ua->dev->speed == USB_SPEED_HIGH ? "high" : "full"); + + err = alloc_stream_buffers(ua, &ua->capture); + if (err < 0) + goto probe_error; + err = alloc_stream_buffers(ua, &ua->playback); + if (err < 0) + goto probe_error; + + err = alloc_stream_urbs(ua, &ua->capture, capture_urb_complete); + if (err < 0) + goto probe_error; + err = alloc_stream_urbs(ua, &ua->playback, playback_urb_complete); + if (err < 0) + goto probe_error; + + err = snd_pcm_new(card, "UA-101", 0, 1, 1, &ua->pcm); + if (err < 0) + goto probe_error; + ua->pcm->private_data = ua; + strcpy(ua->pcm->name, "UA-101"); + snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_PLAYBACK, &playback_pcm_ops); + snd_pcm_set_ops(ua->pcm, SNDRV_PCM_STREAM_CAPTURE, &capture_pcm_ops); + +#ifdef UA1A_HACK + if (ua->dev->descriptor.idProduct != cpu_to_le16(0x0018)) { +#endif + err = snd_usbmidi_create(card, ua->intf[INTF_MIDI], + &ua->midi_list, &midi_quirk); + if (err < 0) + goto probe_error; +#ifdef UA1A_HACK + } +#endif + + err = snd_card_register(card); + if (err < 0) + goto probe_error; + + usb_set_intfdata(interface, ua); + devices_used |= 1 << card_index; + + mutex_unlock(&devices_mutex); + return 0; + +probe_error: + free_usb_related_resources(ua, interface); + snd_card_free(card); + mutex_unlock(&devices_mutex); + return err; +} + +static void ua101_disconnect(struct usb_interface *interface) +{ + struct ua101 *ua = usb_get_intfdata(interface); + struct list_head *midi; + + if (!ua) + return; + + mutex_lock(&devices_mutex); + + set_bit(DISCONNECTED, &ua->states); + wake_up(&ua->rate_feedback_wait); + + /* make sure that userspace cannot create new requests */ + snd_card_disconnect(ua->card); + + /* make sure that there are no pending USB requests */ + __list_for_each(midi, &ua->midi_list) + snd_usbmidi_disconnect(midi); + abort_alsa_playback(ua); + abort_alsa_capture(ua); + mutex_lock(&ua->mutex); + stop_usb_playback(ua); + stop_usb_capture(ua); + mutex_unlock(&ua->mutex); + + free_usb_related_resources(ua, interface); + + devices_used &= ~(1 << ua->card_index); + + snd_card_free_when_closed(ua->card); + + mutex_unlock(&devices_mutex); +} + +static struct usb_device_id ua101_ids[] = { +#ifdef UA1A_HACK + { USB_DEVICE(0x0582, 0x0018) }, +#endif + { USB_DEVICE(0x0582, 0x007d) }, + { USB_DEVICE(0x0582, 0x008d) }, + { } +}; +MODULE_DEVICE_TABLE(usb, ua101_ids); + +static struct usb_driver ua101_driver = { + .name = "snd-ua101", + .id_table = ua101_ids, + .probe = ua101_probe, + .disconnect = ua101_disconnect, +#if 0 + .suspend = ua101_suspend, + .resume = ua101_resume, +#endif +}; + +static int __init alsa_card_ua101_init(void) +{ + return usb_register(&ua101_driver); +} + +static void __exit alsa_card_ua101_exit(void) +{ + usb_deregister(&ua101_driver); + mutex_destroy(&devices_mutex); +} + +module_init(alsa_card_ua101_init); +module_exit(alsa_card_ua101_exit); diff --git a/sound/usb/usbaudio.c b/sound/usb/usbaudio.c index b074a594c59..f352141cf8e 100644 --- a/sound/usb/usbaudio.c +++ b/sound/usb/usbaudio.c @@ -3142,59 +3142,6 @@ static int create_ua1000_quirk(struct snd_usb_audio *chip, return 0; } -/* - * Create a stream for an Edirol UA-101 interface. - * Copy, paste and modify from Edirol UA-1000 - */ -static int create_ua101_quirk(struct snd_usb_audio *chip, - struct usb_interface *iface, - const struct snd_usb_audio_quirk *quirk) -{ - static const struct audioformat ua101_format = { - .format = SNDRV_PCM_FORMAT_S32_LE, - .fmt_type = USB_FORMAT_TYPE_I, - .altsetting = 1, - .altset_idx = 1, - .attributes = 0, - .rates = SNDRV_PCM_RATE_CONTINUOUS, - }; - struct usb_host_interface *alts; - struct usb_interface_descriptor *altsd; - struct audioformat *fp; - int stream, err; - - if (iface->num_altsetting != 2) - return -ENXIO; - alts = &iface->altsetting[1]; - altsd = get_iface_desc(alts); - if (alts->extralen != 18 || alts->extra[1] != USB_DT_CS_INTERFACE || - altsd->bNumEndpoints != 1) - return -ENXIO; - - fp = kmemdup(&ua101_format, sizeof(*fp), GFP_KERNEL); - if (!fp) - return -ENOMEM; - - fp->channels = alts->extra[11]; - fp->iface = altsd->bInterfaceNumber; - fp->endpoint = get_endpoint(alts, 0)->bEndpointAddress; - fp->ep_attr = get_endpoint(alts, 0)->bmAttributes; - fp->datainterval = parse_datainterval(chip, alts); - fp->maxpacksize = le16_to_cpu(get_endpoint(alts, 0)->wMaxPacketSize); - fp->rate_max = fp->rate_min = combine_triple(&alts->extra[15]); - - stream = (fp->endpoint & USB_DIR_IN) - ? SNDRV_PCM_STREAM_CAPTURE : SNDRV_PCM_STREAM_PLAYBACK; - err = add_audio_endpoint(chip, stream, fp); - if (err < 0) { - kfree(fp); - return err; - } - /* FIXME: playback must be synchronized to capture */ - usb_set_interface(chip->dev, fp->iface, 0); - return 0; -} - static int snd_usb_create_quirk(struct snd_usb_audio *chip, struct usb_interface *iface, const struct snd_usb_audio_quirk *quirk); @@ -3406,7 +3353,6 @@ static int snd_usb_create_quirk(struct snd_usb_audio *chip, [QUIRK_AUDIO_STANDARD_INTERFACE] = create_standard_audio_quirk, [QUIRK_AUDIO_FIXED_ENDPOINT] = create_fixed_stream_quirk, [QUIRK_AUDIO_EDIROL_UA1000] = create_ua1000_quirk, - [QUIRK_AUDIO_EDIROL_UA101] = create_ua101_quirk, [QUIRK_AUDIO_EDIROL_UAXX] = create_uaxx_quirk }; diff --git a/sound/usb/usbaudio.h b/sound/usb/usbaudio.h index 40ba8115fb8..9826337c76b 100644 --- a/sound/usb/usbaudio.h +++ b/sound/usb/usbaudio.h @@ -159,7 +159,6 @@ enum quirk_type { QUIRK_AUDIO_STANDARD_INTERFACE, QUIRK_AUDIO_FIXED_ENDPOINT, QUIRK_AUDIO_EDIROL_UA1000, - QUIRK_AUDIO_EDIROL_UA101, QUIRK_AUDIO_EDIROL_UAXX, QUIRK_TYPE_COUNT diff --git a/sound/usb/usbquirks.h b/sound/usb/usbquirks.h index a892bda03df..bd6706c2d53 100644 --- a/sound/usb/usbquirks.h +++ b/sound/usb/usbquirks.h @@ -1266,37 +1266,6 @@ YAMAHA_DEVICE(0x7010, "UB99"), } } }, -/* Roland UA-101 in High-Speed Mode only */ -{ - USB_DEVICE(0x0582, 0x007d), - .driver_info = (unsigned long) & (const struct snd_usb_audio_quirk) { - .vendor_name = "Roland", - .product_name = "UA-101", - .ifnum = QUIRK_ANY_INTERFACE, - .type = QUIRK_COMPOSITE, - .data = (const struct snd_usb_audio_quirk[]) { - { - .ifnum = 0, - .type = QUIRK_AUDIO_EDIROL_UA101 - }, - { - .ifnum = 1, - .type = QUIRK_AUDIO_EDIROL_UA101 - }, - { - .ifnum = 2, - .type = QUIRK_MIDI_FIXED_ENDPOINT, - .data = & (const struct snd_usb_midi_endpoint_info) { - .out_cables = 0x0001, - .in_cables = 0x0001 - } - }, - { - .ifnum = -1 - } - } - } -}, { /* has ID 0x0081 when not in "Advanced Driver" mode */ USB_DEVICE(0x0582, 0x0080), -- cgit v1.2.3-70-g09d2 From 3b377ea9d4efc94dc52fe41b4dfdb463635ab298 Mon Sep 17 00:00:00 2001 From: "John W. Linville" Date: Fri, 18 Dec 2009 17:59:01 -0500 Subject: wireless: support internal statically compiled regulatory database This patch provides infrastructure for machine translation of the regulatory rules database used by CRDA into a C data structure. It includes code for searching that database as an alternative to dynamic regulatory rules updates via CRDA. Most people should use CRDA instead of this infrastructure, but it provides a better alternative than the WIRELESS_OLD_REGULATORY infrastructure (which can now be removed). Signed-off-by: John W. Linville --- Documentation/networking/regulatory.txt | 24 +++++++ net/wireless/.gitignore | 1 + net/wireless/Kconfig | 16 +++++ net/wireless/Makefile | 6 ++ net/wireless/db.txt | 17 +++++ net/wireless/genregdb.awk | 118 +++++++++++++++++++++++++++++++ net/wireless/reg.c | 120 +++++++++++++++++++++++++------- net/wireless/regdb.h | 7 ++ 8 files changed, 285 insertions(+), 24 deletions(-) create mode 100644 net/wireless/.gitignore create mode 100644 net/wireless/db.txt create mode 100644 net/wireless/genregdb.awk create mode 100644 net/wireless/regdb.h (limited to 'Documentation') diff --git a/Documentation/networking/regulatory.txt b/Documentation/networking/regulatory.txt index ee31369e9e5..9551622d0a7 100644 --- a/Documentation/networking/regulatory.txt +++ b/Documentation/networking/regulatory.txt @@ -188,3 +188,27 @@ Then in some part of your code after your wiphy has been registered: &mydriver_jp_regdom.reg_rules[i], sizeof(struct ieee80211_reg_rule)); regulatory_struct_hint(rd); + +Statically compiled regulatory database +--------------------------------------- + +In most situations the userland solution using CRDA as described +above is the preferred solution. However in some cases a set of +rules built into the kernel itself may be desirable. To account +for this situation, a configuration option has been provided +(i.e. CONFIG_CFG80211_INTERNAL_REGDB). With this option enabled, +the wireless database information contained in net/wireless/db.txt is +used to generate a data structure encoded in net/wireless/regdb.c. +That option also enables code in net/wireless/reg.c which queries +the data in regdb.c as an alternative to using CRDA. + +The file net/wireless/db.txt should be kept up-to-date with the db.txt +file available in the git repository here: + + git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-regdb.git + +Again, most users in most situations should be using the CRDA package +provided with their distribution, and in most other situations users +should be building and using CRDA on their own rather than using +this option. If you are not absolutely sure that you should be using +CONFIG_CFG80211_INTERNAL_REGDB then _DO_NOT_USE_IT_. diff --git a/net/wireless/.gitignore b/net/wireless/.gitignore new file mode 100644 index 00000000000..c33451b896d --- /dev/null +++ b/net/wireless/.gitignore @@ -0,0 +1 @@ +regdb.c diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig index 90e93a5701a..8419971f07c 100644 --- a/net/wireless/Kconfig +++ b/net/wireless/Kconfig @@ -109,6 +109,22 @@ config WIRELESS_OLD_REGULATORY Say N and if you say Y, please tell us why. The default is N. +config CFG80211_INTERNAL_REGDB + bool "use statically compiled regulatory rules database" if EMBEDDED + default n + depends on CFG80211 + ---help--- + This option generates an internal data structure representing + the wireless regulatory rules described in net/wireless/db.txt + and includes code to query that database. This is an alternative + to using CRDA for defining regulatory rules for the kernel. + + For details see: + + http://wireless.kernel.org/en/developers/Regulatory + + Most distributions have a CRDA package. So if unsure, say N. + config CFG80211_WEXT bool "cfg80211 wireless extensions compatibility" depends on CFG80211 diff --git a/net/wireless/Makefile b/net/wireless/Makefile index f07c8dc7aab..e77e508126f 100644 --- a/net/wireless/Makefile +++ b/net/wireless/Makefile @@ -13,5 +13,11 @@ cfg80211-y += core.o sysfs.o radiotap.o util.o reg.o scan.o nl80211.o cfg80211-y += mlme.o ibss.o sme.o chan.o ethtool.o cfg80211-$(CONFIG_CFG80211_DEBUGFS) += debugfs.o cfg80211-$(CONFIG_CFG80211_WEXT) += wext-compat.o wext-sme.o +cfg80211-$(CONFIG_CFG80211_INTERNAL_REGDB) += regdb.o ccflags-y += -D__CHECK_ENDIAN__ + +$(obj)/regdb.c: $(src)/db.txt $(src)/genregdb.awk + @$(AWK) -f $(srctree)/$(src)/genregdb.awk < $< > $@ + +clean-files := regdb.c diff --git a/net/wireless/db.txt b/net/wireless/db.txt new file mode 100644 index 00000000000..a2fc3a09ccd --- /dev/null +++ b/net/wireless/db.txt @@ -0,0 +1,17 @@ +# +# This file is a placeholder to prevent accidental build breakage if someone +# enables CONFIG_CFG80211_INTERNAL_REGDB. Almost no one actually needs to +# enable that build option. +# +# You should be using CRDA instead. It is even better if you use the CRDA +# package provided by your distribution, since they will probably keep it +# up-to-date on your behalf. +# +# If you _really_ intend to use CONFIG_CFG80211_INTERNAL_REGDB then you will +# need to replace this file with one containing appropriately formatted +# regulatory rules that cover the regulatory domains you will be using. Your +# best option is to extract the db.txt file from the wireless-regdb git +# repository: +# +# git://git.kernel.org/pub/scm/linux/kernel/git/linville/wireless-regdb.git +# diff --git a/net/wireless/genregdb.awk b/net/wireless/genregdb.awk new file mode 100644 index 00000000000..8316cf075ce --- /dev/null +++ b/net/wireless/genregdb.awk @@ -0,0 +1,118 @@ +#!/usr/bin/awk -f +# +# genregdb.awk -- generate regdb.c from db.txt +# +# Actually, it reads from stdin (presumed to be db.txt) and writes +# to stdout (presumed to be regdb.c), but close enough... +# +# Copyright 2009 John W. Linville +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# + +BEGIN { + active = 0 + rules = 0; + print "/*" + print " * DO NOT EDIT -- file generated from data in db.txt" + print " */" + print "" + print "#include " + print "#include " + print "" + regdb = "const struct ieee80211_regdomain *reg_regdb[] = {\n" +} + +/^[ \t]*#/ { + /* Ignore */ +} + +!active && /^[ \t]*$/ { + /* Ignore */ +} + +!active && /country/ { + country=$2 + sub(/:/, "", country) + printf "static const struct ieee80211_regdomain regdom_%s = {\n", country + printf "\t.alpha2 = \"%s\",\n", country + printf "\t.reg_rules = {\n" + active = 1 + regdb = regdb "\t®dom_" country ",\n" +} + +active && /^[ \t]*\(/ { + start = $1 + sub(/\(/, "", start) + end = $3 + bw = $5 + sub(/\),/, "", bw) + gain = $6 + sub(/\(/, "", gain) + sub(/,/, "", gain) + power = $7 + sub(/\)/, "", power) + sub(/,/, "", power) + # power might be in mW... + units = $8 + sub(/\)/, "", units) + sub(/,/, "", units) + if (units == "mW") { + if (power == 100) { + power = 20 + } else if (power == 200) { + power = 23 + } else if (power == 500) { + power = 27 + } else if (power == 1000) { + power = 30 + } else { + print "Unknown power value in database!" + } + } + flagstr = "" + for (i=8; i<=NF; i++) + flagstr = flagstr $i + split(flagstr, flagarray, ",") + flags = "" + for (arg in flagarray) { + if (flagarray[arg] == "NO-OFDM") { + flags = flags "\n\t\t\tNL80211_RRF_NO_OFDM | " + } else if (flagarray[arg] == "NO-CCK") { + flags = flags "\n\t\t\tNL80211_RRF_NO_CCK | " + } else if (flagarray[arg] == "NO-INDOOR") { + flags = flags "\n\t\t\tNL80211_RRF_NO_INDOOR | " + } else if (flagarray[arg] == "NO-OUTDOOR") { + flags = flags "\n\t\t\tNL80211_RRF_NO_OUTDOOR | " + } else if (flagarray[arg] == "DFS") { + flags = flags "\n\t\t\tNL80211_RRF_DFS | " + } else if (flagarray[arg] == "PTP-ONLY") { + flags = flags "\n\t\t\tNL80211_RRF_PTP_ONLY | " + } else if (flagarray[arg] == "PTMP-ONLY") { + flags = flags "\n\t\t\tNL80211_RRF_PTMP_ONLY | " + } else if (flagarray[arg] == "PASSIVE-SCAN") { + flags = flags "\n\t\t\tNL80211_RRF_PASSIVE_SCAN | " + } else if (flagarray[arg] == "NO-IBSS") { + flags = flags "\n\t\t\tNL80211_RRF_NO_IBSS | " + } + } + flags = flags "0" + printf "\t\tREG_RULE(%d, %d, %d, %d, %d, %s),\n", start, end, bw, gain, power, flags + rules++ +} + +active && /^[ \t]*$/ { + active = 0 + printf "\t},\n" + printf "\t.n_reg_rules = %d\n", rules + printf "};\n\n" + rules = 0; +} + +END { + print regdb "};" + print "" + print "int reg_regdb_size = ARRAY_SIZE(reg_regdb);" +} diff --git a/net/wireless/reg.c b/net/wireless/reg.c index c01470e7de1..65f86264f7b 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -40,6 +40,7 @@ #include #include "core.h" #include "reg.h" +#include "regdb.h" #include "nl80211.h" /* Receipt of information from last regulatory request */ @@ -360,6 +361,98 @@ static bool country_ie_integrity_changes(u32 checksum) return false; } +static int reg_copy_regd(const struct ieee80211_regdomain **dst_regd, + const struct ieee80211_regdomain *src_regd) +{ + struct ieee80211_regdomain *regd; + int size_of_regd = 0; + unsigned int i; + + size_of_regd = sizeof(struct ieee80211_regdomain) + + ((src_regd->n_reg_rules + 1) * sizeof(struct ieee80211_reg_rule)); + + regd = kzalloc(size_of_regd, GFP_KERNEL); + if (!regd) + return -ENOMEM; + + memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); + + for (i = 0; i < src_regd->n_reg_rules; i++) + memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], + sizeof(struct ieee80211_reg_rule)); + + *dst_regd = regd; + return 0; +} + +#ifdef CONFIG_CFG80211_INTERNAL_REGDB +struct reg_regdb_search_request { + char alpha2[2]; + struct list_head list; +}; + +static LIST_HEAD(reg_regdb_search_list); +static DEFINE_SPINLOCK(reg_regdb_search_lock); + +static void reg_regdb_search(struct work_struct *work) +{ + struct reg_regdb_search_request *request; + const struct ieee80211_regdomain *curdom, *regdom; + int i, r; + + spin_lock(®_regdb_search_lock); + while (!list_empty(®_regdb_search_list)) { + request = list_first_entry(®_regdb_search_list, + struct reg_regdb_search_request, + list); + list_del(&request->list); + + for (i=0; ialpha2, curdom->alpha2, 2)) { + r = reg_copy_regd(®dom, curdom); + if (r) + break; + spin_unlock(®_regdb_search_lock); + mutex_lock(&cfg80211_mutex); + set_regdom(regdom); + mutex_unlock(&cfg80211_mutex); + spin_lock(®_regdb_search_lock); + break; + } + } + + kfree(request); + } + spin_unlock(®_regdb_search_lock); +} + +static DECLARE_WORK(reg_regdb_work, reg_regdb_search); + +static void reg_regdb_query(const char *alpha2) +{ + struct reg_regdb_search_request *request; + + if (!alpha2) + return; + + request = kzalloc(sizeof(struct reg_regdb_search_request), GFP_KERNEL); + if (!request) + return; + + memcpy(request->alpha2, alpha2, 2); + + spin_lock(®_regdb_search_lock); + list_add_tail(&request->list, ®_regdb_search_list); + spin_unlock(®_regdb_search_lock); + + schedule_work(®_regdb_work); +} +#else +static inline void reg_regdb_query(const char *alpha2) {} +#endif /* CONFIG_CFG80211_INTERNAL_REGDB */ + /* * This lets us keep regulatory code which is updated on a regulatory * basis in userspace. @@ -379,6 +472,9 @@ static int call_crda(const char *alpha2) printk(KERN_INFO "cfg80211: Calling CRDA to update world " "regulatory domain\n"); + /* query internal regulatory database (if it exists) */ + reg_regdb_query(alpha2); + country_env[8] = alpha2[0]; country_env[9] = alpha2[1]; @@ -1367,30 +1463,6 @@ void wiphy_apply_custom_regulatory(struct wiphy *wiphy, } EXPORT_SYMBOL(wiphy_apply_custom_regulatory); -static int reg_copy_regd(const struct ieee80211_regdomain **dst_regd, - const struct ieee80211_regdomain *src_regd) -{ - struct ieee80211_regdomain *regd; - int size_of_regd = 0; - unsigned int i; - - size_of_regd = sizeof(struct ieee80211_regdomain) + - ((src_regd->n_reg_rules + 1) * sizeof(struct ieee80211_reg_rule)); - - regd = kzalloc(size_of_regd, GFP_KERNEL); - if (!regd) - return -ENOMEM; - - memcpy(regd, src_regd, sizeof(struct ieee80211_regdomain)); - - for (i = 0; i < src_regd->n_reg_rules; i++) - memcpy(®d->reg_rules[i], &src_regd->reg_rules[i], - sizeof(struct ieee80211_reg_rule)); - - *dst_regd = regd; - return 0; -} - /* * Return value which can be used by ignore_request() to indicate * it has been determined we should intersect two regulatory domains diff --git a/net/wireless/regdb.h b/net/wireless/regdb.h new file mode 100644 index 00000000000..818222c9251 --- /dev/null +++ b/net/wireless/regdb.h @@ -0,0 +1,7 @@ +#ifndef __REGDB_H__ +#define __REGDB_H__ + +extern const struct ieee80211_regdomain *reg_regdb[]; +extern int reg_regdb_size; + +#endif /* __REGDB_H__ */ -- cgit v1.2.3-70-g09d2 From 44eba3e82b35ae796826a65d8040001582adc10a Mon Sep 17 00:00:00 2001 From: Krzysztof Helt Date: Wed, 23 Dec 2009 18:02:41 +0100 Subject: ALSA: jazz16: refine dma and irq selection Narrow the dma and irq selection after the DOS driver. Add ALSA configuration description as well. Signed-off-by: Krzysztof Helt Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 15 +++++++++++++++ sound/isa/sb/jazz16.c | 21 ++++++++++++++++++++- sound/isa/sb/sb8_main.c | 3 ++- 3 files changed, 37 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 7a0a4a9dc18..c540637eb16 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1123,6 +1123,21 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. This module supports multiple cards, autoprobe and ISA PnP. + Module snd-jazz16 + ------------------- + + Module for Media Vision Jazz16 chipset. The chipset consists of 3 chips: + MVD1216 + MVA416 + MVA514. + + port - port # for SB DSP chip (0x210,0x220,0x230,0x240,0x250,0x260) + irq - IRQ # for SB DSP chip (3,5,7,9,10,15) + dma8 - DMA # for SB DSP chip (1,3) + dma16 - DMA # for SB DSP chip (5,7) + mpu_port - MPU-401 port # (0x300,0x310,0x320,0x330) + mpu_irq - MPU-401 irq # (2,3,5,7) + + This module supports multiple cards. + Module snd-korg1212 ------------------- diff --git a/sound/isa/sb/jazz16.c b/sound/isa/sb/jazz16.c index d52966b7584..8d21a3feda3 100644 --- a/sound/isa/sb/jazz16.c +++ b/sound/isa/sb/jazz16.c @@ -189,10 +189,29 @@ static int __devinit snd_jazz16_match(struct device *devptr, unsigned int dev) if (port[dev] == SNDRV_AUTO_PORT) { snd_printk(KERN_ERR "please specify port\n"); return 0; + } else if (port[dev] == 0x200 || (port[dev] & ~0x270)) { + snd_printk(KERN_ERR "incorrect port specified\n"); + return 0; + } + if (dma8[dev] != SNDRV_AUTO_DMA && + dma8[dev] != 1 && dma8[dev] != 3) { + snd_printk(KERN_ERR "dma8 must be 1 or 3\n"); + return 0; } if (dma16[dev] != SNDRV_AUTO_DMA && dma16[dev] != 5 && dma16[dev] != 7) { - snd_printk(KERN_ERR "dma16 must be 5 or 7"); + snd_printk(KERN_ERR "dma16 must be 5 or 7\n"); + return 0; + } + if (mpu_port[dev] != SNDRV_AUTO_PORT && + (mpu_port[dev] & ~0x030) != 0x300) { + snd_printk(KERN_ERR "incorrect mpu_port specified\n"); + return 0; + } + if (mpu_irq[dev] != SNDRV_AUTO_DMA && + mpu_irq[dev] != 2 && mpu_irq[dev] != 3 && + mpu_irq[dev] != 5 && mpu_irq[dev] != 7) { + snd_printk(KERN_ERR "mpu_irq must be 2, 3, 5 or 7\n"); return 0; } return 1; diff --git a/sound/isa/sb/sb8_main.c b/sound/isa/sb/sb8_main.c index 3222aed5fac..7d84c9f34dc 100644 --- a/sound/isa/sb/sb8_main.c +++ b/sound/isa/sb/sb8_main.c @@ -510,7 +510,8 @@ static int snd_sb8_open(struct snd_pcm_substream *substream) } switch (chip->hardware) { case SB_HW_JAZZ16: - runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE; + if (chip->dma16 == 5 || chip->dma16 == 7) + runtime->hw.formats |= SNDRV_PCM_FMTBIT_S16_LE; runtime->hw.rates |= SNDRV_PCM_RATE_8000_48000; runtime->hw.rate_min = 4000; runtime->hw.rate_max = 50000; -- cgit v1.2.3-70-g09d2 From baeb66fe2306783e3b9a492b03882f2e249b2eeb Mon Sep 17 00:00:00 2001 From: "John W. Linville" Date: Fri, 18 Dec 2009 17:59:02 -0500 Subject: wireless: remove CONFIG_WIRELESS_OLD_REGULATORY This is no longer needed with the availability of CONFIG_CFG80211_INTERNAL_REGDB. Signed-off-by: John W. Linville --- Documentation/feature-removal-schedule.txt | 21 ------- net/wireless/Kconfig | 15 ----- net/wireless/nl80211.c | 6 -- net/wireless/reg.c | 89 +----------------------------- 4 files changed, 2 insertions(+), 129 deletions(-) (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 591e94448e6..86f2ec90af8 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -88,27 +88,6 @@ Who: Luis R. Rodriguez --------------------------- -What: CONFIG_WIRELESS_OLD_REGULATORY - old static regulatory information -When: March 2010 / desktop catchup - -Why: The old regulatory infrastructure has been replaced with a new one - which does not require statically defined regulatory domains. We do - not want to keep static regulatory domains in the kernel due to the - the dynamic nature of regulatory law and localization. We kept around - the old static definitions for the regulatory domains of: - - * US - * JP - * EU - - and used by default the US when CONFIG_WIRELESS_OLD_REGULATORY was - set. We will remove this option once the standard Linux desktop catches - up with the new userspace APIs we have implemented. - -Who: Luis R. Rodriguez - ---------------------------- - What: dev->power.power_state When: July 2007 Why: Broken design for runtime control over driver power states, confusing diff --git a/net/wireless/Kconfig b/net/wireless/Kconfig index 8419971f07c..d0ee29063e5 100644 --- a/net/wireless/Kconfig +++ b/net/wireless/Kconfig @@ -94,21 +94,6 @@ config CFG80211_DEBUGFS If unsure, say N. -config WIRELESS_OLD_REGULATORY - bool "Old wireless static regulatory definitions" - default n - depends on CFG80211 - ---help--- - This option enables the old static regulatory information - and uses it within the new framework. This option is available - for historical reasons and it is advised to leave it off. - - For details see: - - http://wireless.kernel.org/en/developers/Regulatory - - Say N and if you say Y, please tell us why. The default is N. - config CFG80211_INTERNAL_REGDB bool "use statically compiled regulatory rules database" if EMBEDDED default n diff --git a/net/wireless/nl80211.c b/net/wireless/nl80211.c index 7cb0d647fc3..60f854377f9 100644 --- a/net/wireless/nl80211.c +++ b/net/wireless/nl80211.c @@ -2550,12 +2550,6 @@ static int nl80211_req_set_reg(struct sk_buff *skb, struct genl_info *info) data = nla_data(info->attrs[NL80211_ATTR_REG_ALPHA2]); -#ifdef CONFIG_WIRELESS_OLD_REGULATORY - /* We ignore world regdom requests with the old regdom setup */ - if (is_world_regdom(data)) - return -EINVAL; -#endif - r = regulatory_hint_user(data); return r; diff --git a/net/wireless/reg.c b/net/wireless/reg.c index dc13c3ffeca..87ea60d84c3 100644 --- a/net/wireless/reg.c +++ b/net/wireless/reg.c @@ -129,78 +129,6 @@ static char *ieee80211_regdom = "00"; module_param(ieee80211_regdom, charp, 0444); MODULE_PARM_DESC(ieee80211_regdom, "IEEE 802.11 regulatory domain code"); -#ifdef CONFIG_WIRELESS_OLD_REGULATORY -/* - * We assume 40 MHz bandwidth for the old regulatory work. - * We make emphasis we are using the exact same frequencies - * as before - */ - -static const struct ieee80211_regdomain us_regdom = { - .n_reg_rules = 6, - .alpha2 = "US", - .reg_rules = { - /* IEEE 802.11b/g, channels 1..11 */ - REG_RULE(2412-10, 2462+10, 40, 6, 27, 0), - /* IEEE 802.11a, channel 36..48 */ - REG_RULE(5180-10, 5240+10, 40, 6, 17, 0), - /* IEEE 802.11a, channels 48..64 */ - REG_RULE(5260-10, 5320+10, 40, 6, 20, NL80211_RRF_DFS), - /* IEEE 802.11a, channels 100..124 */ - REG_RULE(5500-10, 5590+10, 40, 6, 20, NL80211_RRF_DFS), - /* IEEE 802.11a, channels 132..144 */ - REG_RULE(5660-10, 5700+10, 40, 6, 20, NL80211_RRF_DFS), - /* IEEE 802.11a, channels 149..165, outdoor */ - REG_RULE(5745-10, 5825+10, 40, 6, 30, 0), - } -}; - -static const struct ieee80211_regdomain jp_regdom = { - .n_reg_rules = 6, - .alpha2 = "JP", - .reg_rules = { - /* IEEE 802.11b/g, channels 1..11 */ - REG_RULE(2412-10, 2462+10, 40, 6, 20, 0), - /* IEEE 802.11b/g, channels 12..13 */ - REG_RULE(2467-10, 2472+10, 20, 6, 20, 0), - /* IEEE 802.11b/g, channel 14 */ - REG_RULE(2484-10, 2484+10, 20, 6, 20, NL80211_RRF_NO_OFDM), - /* IEEE 802.11a, channels 36..48 */ - REG_RULE(5180-10, 5240+10, 40, 6, 20, 0), - /* IEEE 802.11a, channels 52..64 */ - REG_RULE(5260-10, 5320+10, 40, 6, 20, NL80211_RRF_DFS), - /* IEEE 802.11a, channels 100..144 */ - REG_RULE(5500-10, 5700+10, 40, 6, 23, NL80211_RRF_DFS), - } -}; - -static const struct ieee80211_regdomain *static_regdom(char *alpha2) -{ - if (alpha2[0] == 'U' && alpha2[1] == 'S') - return &us_regdom; - if (alpha2[0] == 'J' && alpha2[1] == 'P') - return &jp_regdom; - /* Use world roaming rules for "EU", since it was a pseudo - domain anyway... */ - if (alpha2[0] == 'E' && alpha2[1] == 'U') - return &world_regdom; - /* Default, world roaming rules */ - return &world_regdom; -} - -static bool is_old_static_regdom(const struct ieee80211_regdomain *rd) -{ - if (rd == &us_regdom || rd == &jp_regdom || rd == &world_regdom) - return true; - return false; -} -#else -static inline bool is_old_static_regdom(const struct ieee80211_regdomain *rd) -{ - return false; -} -#endif - static void reset_regdomains(void) { /* avoid freeing static information or freeing something twice */ @@ -210,8 +138,6 @@ static void reset_regdomains(void) cfg80211_world_regdom = NULL; if (cfg80211_regdomain == &world_regdom) cfg80211_regdomain = NULL; - if (is_old_static_regdom(cfg80211_regdomain)) - cfg80211_regdomain = NULL; kfree(cfg80211_regdomain); kfree(cfg80211_world_regdom); @@ -1490,8 +1416,6 @@ static int ignore_request(struct wiphy *wiphy, return REG_INTERSECT; case NL80211_REGDOM_SET_BY_DRIVER: if (last_request->initiator == NL80211_REGDOM_SET_BY_CORE) { - if (is_old_static_regdom(cfg80211_regdomain)) - return 0; if (regdom_changes(pending_request->alpha2)) return 0; return -EALREADY; @@ -1528,8 +1452,7 @@ static int ignore_request(struct wiphy *wiphy, return -EAGAIN; } - if (!is_old_static_regdom(cfg80211_regdomain) && - !regdom_changes(pending_request->alpha2)) + if (!regdom_changes(pending_request->alpha2)) return -EALREADY; return 0; @@ -2111,8 +2034,7 @@ static int __set_regdom(const struct ieee80211_regdomain *rd) * If someone else asked us to change the rd lets only bother * checking if the alpha2 changes if CRDA was already called */ - if (!is_old_static_regdom(cfg80211_regdomain) && - !regdom_changes(rd->alpha2)) + if (!regdom_changes(rd->alpha2)) return -EINVAL; } @@ -2311,15 +2233,8 @@ int regulatory_init(void) spin_lock_init(®_requests_lock); spin_lock_init(®_pending_beacons_lock); -#ifdef CONFIG_WIRELESS_OLD_REGULATORY - cfg80211_regdomain = static_regdom(ieee80211_regdom); - - printk(KERN_INFO "cfg80211: Using static regulatory domain info\n"); - print_regdomain_info(cfg80211_regdomain); -#else cfg80211_regdomain = cfg80211_world_regdom; -#endif /* We always try to get an update for the static regdomain */ err = regulatory_hint_core(cfg80211_regdomain->alpha2); if (err) { -- cgit v1.2.3-70-g09d2 From 65324144b50bc7022cc9b6ca8f4a536a957019e3 Mon Sep 17 00:00:00 2001 From: Jesper Dangaard Brouer Date: Tue, 5 Jan 2010 05:50:47 +0000 Subject: net: RFC3069, private VLAN proxy arp support This is to be used together with switch technologies, like RFC3069, that where the individual ports are not allowed to communicate with each other, but they are allowed to talk to the upstream router. As described in RFC 3069, it is possible to allow these hosts to communicate through the upstream router by proxy_arp'ing. This patch basically allow proxy arp replies back to the same interface (from which the ARP request/solicitation was received). Tunable per device via proc "proxy_arp_pvlan": /proc/sys/net/ipv4/conf/*/proxy_arp_pvlan This switch technology is known by different vendor names: - In RFC 3069 it is called VLAN Aggregation. - Cisco and Allied Telesyn call it Private VLAN. - Hewlett-Packard call it Source-Port filtering or port-isolation. - Ericsson call it MAC-Forced Forwarding (RFC Draft). Signed-off-by: Jesper Dangaard Brouer Signed-off-by: David S. Miller --- Documentation/networking/ip-sysctl.txt | 19 +++++++++++++ include/linux/inetdevice.h | 1 + include/linux/sysctl.h | 1 + net/ipv4/arp.c | 52 +++++++++++++++++++++++++++++++--- net/ipv4/devinet.c | 1 + net/ipv4/route.c | 7 ++++- 6 files changed, 76 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt index 006b39dec87..c532884f4fe 100644 --- a/Documentation/networking/ip-sysctl.txt +++ b/Documentation/networking/ip-sysctl.txt @@ -692,6 +692,25 @@ proxy_arp - BOOLEAN conf/{all,interface}/proxy_arp is set to TRUE, it will be disabled otherwise +proxy_arp_pvlan - BOOLEAN + Private VLAN proxy arp. + Basically allow proxy arp replies back to the same interface + (from which the ARP request/solicitation was received). + + This is done to support (ethernet) switch features, like RFC + 3069, where the individual ports are NOT allowed to + communicate with each other, but they are allowed to talk to + the upstream router. As described in RFC 3069, it is possible + to allow these hosts to communicate through the upstream + router by proxy_arp'ing. Don't need to be used together with + proxy_arp. + + This technology is known by different names: + In RFC 3069 it is called VLAN Aggregation. + Cisco and Allied Telesyn call it Private VLAN. + Hewlett-Packard call it Source-Port filtering or port-isolation. + Ericsson call it MAC-Forced Forwarding (RFC Draft). + shared_media - BOOLEAN Send(router) or accept(host) RFC1620 shared media redirects. Overrides ip_secure_redirects. diff --git a/include/linux/inetdevice.h b/include/linux/inetdevice.h index 699e85c01a4..9a8c57467d3 100644 --- a/include/linux/inetdevice.h +++ b/include/linux/inetdevice.h @@ -88,6 +88,7 @@ static inline void ipv4_devconf_setall(struct in_device *in_dev) #define IN_DEV_LOG_MARTIANS(in_dev) IN_DEV_ORCONF((in_dev), LOG_MARTIANS) #define IN_DEV_PROXY_ARP(in_dev) IN_DEV_ORCONF((in_dev), PROXY_ARP) +#define IN_DEV_PROXY_ARP_PVLAN(in_dev) IN_DEV_CONF_GET(in_dev, PROXY_ARP_PVLAN) #define IN_DEV_SHARED_MEDIA(in_dev) IN_DEV_ORCONF((in_dev), SHARED_MEDIA) #define IN_DEV_TX_REDIRECTS(in_dev) IN_DEV_ORCONF((in_dev), SEND_REDIRECTS) #define IN_DEV_SEC_REDIRECTS(in_dev) IN_DEV_ORCONF((in_dev), \ diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h index 877ba039e6a..24ff7e3a0d5 100644 --- a/include/linux/sysctl.h +++ b/include/linux/sysctl.h @@ -482,6 +482,7 @@ enum NET_IPV4_CONF_ARP_ACCEPT=21, NET_IPV4_CONF_ARP_NOTIFY=22, NET_IPV4_CONF_ACCEPT_LOCAL=23, + NET_IPV4_CONF_PROXY_ARP_PVLAN=24, __NET_IPV4_CONF_MAX }; diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c index c95cd93acf2..078709233bc 100644 --- a/net/ipv4/arp.c +++ b/net/ipv4/arp.c @@ -70,6 +70,7 @@ * bonding can change the skb before * sending (e.g. insert 8021q tag). * Harald Welte : convert to make use of jenkins hash + * Jesper D. Brouer: Proxy ARP PVLAN RFC 3069 support. */ #include @@ -524,12 +525,15 @@ int arp_bind_neighbour(struct dst_entry *dst) /* * Check if we can use proxy ARP for this path */ - -static inline int arp_fwd_proxy(struct in_device *in_dev, struct rtable *rt) +static inline int arp_fwd_proxy(struct in_device *in_dev, + struct net_device *dev, struct rtable *rt) { struct in_device *out_dev; int imi, omi = -1; + if (rt->u.dst.dev == dev) + return 0; + if (!IN_DEV_PROXY_ARP(in_dev)) return 0; @@ -547,6 +551,43 @@ static inline int arp_fwd_proxy(struct in_device *in_dev, struct rtable *rt) return (omi != imi && omi != -1); } +/* + * Check for RFC3069 proxy arp private VLAN (allow to send back to same dev) + * + * RFC3069 supports proxy arp replies back to the same interface. This + * is done to support (ethernet) switch features, like RFC 3069, where + * the individual ports are not allowed to communicate with each + * other, BUT they are allowed to talk to the upstream router. As + * described in RFC 3069, it is possible to allow these hosts to + * communicate through the upstream router, by proxy_arp'ing. + * + * RFC 3069: "VLAN Aggregation for Efficient IP Address Allocation" + * + * This technology is known by different names: + * In RFC 3069 it is called VLAN Aggregation. + * Cisco and Allied Telesyn call it Private VLAN. + * Hewlett-Packard call it Source-Port filtering or port-isolation. + * Ericsson call it MAC-Forced Forwarding (RFC Draft). + * + */ +static inline int arp_fwd_pvlan(struct in_device *in_dev, + struct net_device *dev, struct rtable *rt, + __be32 sip, __be32 tip) +{ + /* Private VLAN is only concerned about the same ethernet segment */ + if (rt->u.dst.dev != dev) + return 0; + + /* Don't reply on self probes (often done by windowz boxes)*/ + if (sip == tip) + return 0; + + if (IN_DEV_PROXY_ARP_PVLAN(in_dev)) + return 1; + else + return 0; +} + /* * Interface to link layer: send routine and receive handler. */ @@ -833,8 +874,11 @@ static int arp_process(struct sk_buff *skb) } goto out; } else if (IN_DEV_FORWARD(in_dev)) { - if (addr_type == RTN_UNICAST && rt->u.dst.dev != dev && - (arp_fwd_proxy(in_dev, rt) || pneigh_lookup(&arp_tbl, net, &tip, dev, 0))) { + if (addr_type == RTN_UNICAST && + (arp_fwd_proxy(in_dev, dev, rt) || + arp_fwd_pvlan(in_dev, dev, rt, sip, tip) || + pneigh_lookup(&arp_tbl, net, &tip, dev, 0))) + { n = neigh_event_ns(&arp_tbl, sha, &sip, dev); if (n) neigh_release(n); diff --git a/net/ipv4/devinet.c b/net/ipv4/devinet.c index 5cdbc102a41..0715f4cac39 100644 --- a/net/ipv4/devinet.c +++ b/net/ipv4/devinet.c @@ -1407,6 +1407,7 @@ static struct devinet_sysctl_table { DEVINET_SYSCTL_RW_ENTRY(ARP_IGNORE, "arp_ignore"), DEVINET_SYSCTL_RW_ENTRY(ARP_ACCEPT, "arp_accept"), DEVINET_SYSCTL_RW_ENTRY(ARP_NOTIFY, "arp_notify"), + DEVINET_SYSCTL_RW_ENTRY(PROXY_ARP_PVLAN, "proxy_arp_pvlan"), DEVINET_SYSCTL_FLUSHING_ENTRY(NOXFRM, "disable_xfrm"), DEVINET_SYSCTL_FLUSHING_ENTRY(NOPOLICY, "disable_policy"), diff --git a/net/ipv4/route.c b/net/ipv4/route.c index e446496f564..1cc339441e7 100644 --- a/net/ipv4/route.c +++ b/net/ipv4/route.c @@ -1988,8 +1988,13 @@ static int __mkroute_input(struct sk_buff *skb, if (skb->protocol != htons(ETH_P_IP)) { /* Not IP (i.e. ARP). Do not create route, if it is * invalid for proxy arp. DNAT routes are always valid. + * + * Proxy arp feature have been extended to allow, ARP + * replies back to the same interface, to support + * Private VLAN switch technologies. See arp.c. */ - if (out_dev == in_dev) { + if (out_dev == in_dev && + IN_DEV_PROXY_ARP_PVLAN(in_dev) == 0) { err = -EINVAL; goto cleanup; } -- cgit v1.2.3-70-g09d2 From 959d20bfe8b90023c862dec3956054027a5a0912 Mon Sep 17 00:00:00 2001 From: Wolfgang Grandegger Date: Thu, 7 Jan 2010 09:43:08 +0000 Subject: powerpc/mpc5xxx: add OF platform binding doc for FSL MSCAN devices This patch adds documentation for the MSCAN OF device bindings for the MPC512x and moves the one for the MPC5200 to the new common file "Documentation/powerpc/dts-bindings/fsl/can.txt". Signed-off-by: Wolfgang Grandegger Reviewed-by: Wolfram Sang Signed-off-by: David S. Miller --- Documentation/powerpc/dts-bindings/fsl/can.txt | 53 ++++++++++++++++++++++ Documentation/powerpc/dts-bindings/fsl/mpc5200.txt | 9 +--- 2 files changed, 54 insertions(+), 8 deletions(-) create mode 100644 Documentation/powerpc/dts-bindings/fsl/can.txt (limited to 'Documentation') diff --git a/Documentation/powerpc/dts-bindings/fsl/can.txt b/Documentation/powerpc/dts-bindings/fsl/can.txt new file mode 100644 index 00000000000..2fa4fcd38fd --- /dev/null +++ b/Documentation/powerpc/dts-bindings/fsl/can.txt @@ -0,0 +1,53 @@ +CAN Device Tree Bindings +------------------------ + +(c) 2006-2009 Secret Lab Technologies Ltd +Grant Likely + +fsl,mpc5200-mscan nodes +----------------------- +In addition to the required compatible-, reg- and interrupt-properties, you can +also specify which clock source shall be used for the controller: + +- fsl,mscan-clock-source : a string describing the clock source. Valid values + are: "ip" for ip bus clock + "ref" for reference clock (XTAL) + "ref" is default in case this property is not + present. + +fsl,mpc5121-mscan nodes +----------------------- +In addition to the required compatible-, reg- and interrupt-properties, you can +also specify which clock source and divider shall be used for the controller: + +- fsl,mscan-clock-source : a string describing the clock source. Valid values + are: "ip" for ip bus clock + "ref" for reference clock + "sys" for system clock + If this property is not present, an optimal CAN + clock source and frequency based on the system + clock will be selected. If this is not possible, + the reference clock will be used. + +- fsl,mscan-clock-divider: for the reference and system clock, an additional + clock divider can be specified. By default, a + value of 1 is used. + +Note that the MPC5121 Rev. 1 processor is not supported. + +Examples: + can@1300 { + compatible = "fsl,mpc5121-mscan"; + interrupts = <12 0x8>; + interrupt-parent = <&ipic>; + reg = <0x1300 0x80>; + }; + + can@1380 { + compatible = "fsl,mpc5121-mscan"; + interrupts = <13 0x8>; + interrupt-parent = <&ipic>; + reg = <0x1380 0x80>; + fsl,mscan-clock-source = "ref"; + fsl,mscan-clock-divider = <3>; + }; diff --git a/Documentation/powerpc/dts-bindings/fsl/mpc5200.txt b/Documentation/powerpc/dts-bindings/fsl/mpc5200.txt index 5c6602dbfdc..4ccb2cd5df9 100644 --- a/Documentation/powerpc/dts-bindings/fsl/mpc5200.txt +++ b/Documentation/powerpc/dts-bindings/fsl/mpc5200.txt @@ -195,11 +195,4 @@ External interrupts: fsl,mpc5200-mscan nodes ----------------------- -In addition to the required compatible-, reg- and interrupt-properites, you can -also specify which clock source shall be used for the controller: - -- fsl,mscan-clock-source- a string describing the clock source. Valid values - are: "ip" for ip bus clock - "ref" for reference clock (XTAL) - "ref" is default in case this property is not - present. +See file can.txt in this directory. -- cgit v1.2.3-70-g09d2 From ecc6703cbb2bb648c7345c652a704f7af56322b8 Mon Sep 17 00:00:00 2001 From: Greg Rose Date: Sat, 9 Jan 2010 02:24:50 +0000 Subject: ixgbevf: Kconfig, Makefile and Documentation Modifications for the Kconfig and network device Makefile to add the ixgbevf driver module to the kernel plus basic driver documentation. Signed-off-by: Greg Rose Signed-off-by: Jeff Kirsher Signed-off-by: David S. Miller --- Documentation/networking/ixgbevf.txt | 90 ++++++++++++++++++++++++++++++++++++ drivers/net/Kconfig | 22 +++++++++ drivers/net/Makefile | 1 + 3 files changed, 113 insertions(+) create mode 100755 Documentation/networking/ixgbevf.txt (limited to 'Documentation') diff --git a/Documentation/networking/ixgbevf.txt b/Documentation/networking/ixgbevf.txt new file mode 100755 index 00000000000..19015de6725 --- /dev/null +++ b/Documentation/networking/ixgbevf.txt @@ -0,0 +1,90 @@ +Linux* Base Driver for Intel(R) Network Connection +================================================== + +November 24, 2009 + +Contents +======== + +- In This Release +- Identifying Your Adapter +- Known Issues/Troubleshooting +- Support + +In This Release +=============== + +This file describes the ixgbevf Linux* Base Driver for Intel Network +Connection. + +The ixgbevf driver supports 82599-based virtual function devices that can only +be activated on kernels with CONFIG_PCI_IOV enabled. + +The ixgbevf driver supports virtual functions generated by the ixgbe driver +with a max_vfs value of 1 or greater. + +The guest OS loading the ixgbevf driver must support MSI-X interrupts. + +VLANs: There is a limit of a total of 32 shared VLANs to 1 or more VFs. + +Identifying Your Adapter +======================== + +For more information on how to identify your adapter, go to the Adapter & +Driver ID Guide at: + + http://support.intel.com/support/network/sb/CS-008441.htm + +Known Issues/Troubleshooting +============================ + + Unloading Physical Function (PF) Driver Causes System Reboots When VM is + Running and VF is Loaded on the VM + ------------------------------------------------------------------------ + Do not unload the PF driver (ixgbe) while VFs are assigned to guests. + +Support +======= + +For general information, go to the Intel support website at: + + http://support.intel.com + +or the Intel Wired Networking project hosted by Sourceforge at: + + http://sourceforge.net/projects/e1000 + +If an issue is identified with the released source code on the supported +kernel with a supported adapter, email the specific information related +to the issue to e1000-devel@lists.sf.net + +License +======= + +Intel 10 Gigabit Linux driver. +Copyright(c) 1999 - 2009 Intel Corporation. + +This program is free software; you can redistribute it and/or modify it +under the terms and conditions of the GNU General Public License, +version 2, as published by the Free Software Foundation. + +This program is distributed in the hope it will be useful, but WITHOUT +ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for +more details. + +You should have received a copy of the GNU General Public License along with +this program; if not, write to the Free Software Foundation, Inc., +51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + +The full GNU General Public License is included in this distribution in +the file called "COPYING". + +Trademarks +========== + +Intel, Itanium, and Pentium are trademarks or registered trademarks of +Intel Corporation or its subsidiaries in the United States and other +countries. + +* Other names and brands may be claimed as the property of others. diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index dd9a09c72df..5be6a2376f4 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -2618,6 +2618,28 @@ config IXGBE_DCB If unsure, say N. +config IXGBEVF + tristate "Intel(R) 82599 Virtual Function Ethernet support" + depends on PCI_MSI + ---help--- + This driver supports Intel(R) 82599 virtual functions. For more + information on how to identify your adapter, go to the Adapter & + Driver ID Guide at: + + + + For general information and support, go to the Intel support + website at: + + + + More specific information on configuring the driver is in + . + + To compile this driver as a module, choose M here. The module + will be called ixgbevf. MSI-X interrupt support is required + for this driver to work correctly. + config IXGB tristate "Intel(R) PRO/10GbE support" depends on PCI diff --git a/drivers/net/Makefile b/drivers/net/Makefile index ad1346dd9da..6746e8b8bdf 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -14,6 +14,7 @@ obj-$(CONFIG_IBM_NEW_EMAC) += ibm_newemac/ obj-$(CONFIG_IGB) += igb/ obj-$(CONFIG_IGBVF) += igbvf/ obj-$(CONFIG_IXGBE) += ixgbe/ +obj-$(CONFIG_IXGBEVF) += ixgbevf/ obj-$(CONFIG_IXGB) += ixgb/ obj-$(CONFIG_IP1000) += ipg.o obj-$(CONFIG_CHELSIO_T1) += chelsio/ -- cgit v1.2.3-70-g09d2 From 3920ab0ae729e73bbcb5b3d0358c048ff9163629 Mon Sep 17 00:00:00 2001 From: Tai-hwa Liang Date: Wed, 13 Jan 2010 00:22:29 -0800 Subject: Input: update Sentelic protocol documentation iSigned-off-by: Tai-hwa Liang Signed-off-by: Dmitry Torokhov --- Documentation/input/sentelic.txt | 124 ++++++++++++++++++++++++++++++++++----- 1 file changed, 110 insertions(+), 14 deletions(-) (limited to 'Documentation') diff --git a/Documentation/input/sentelic.txt b/Documentation/input/sentelic.txt index f7160a2fb6a..b35affd5c64 100644 --- a/Documentation/input/sentelic.txt +++ b/Documentation/input/sentelic.txt @@ -1,5 +1,5 @@ -Copyright (C) 2002-2008 Sentelic Corporation. -Last update: Oct-31-2008 +Copyright (C) 2002-2010 Sentelic Corporation. +Last update: Jan-13-2010 ============================================================================== * Finger Sensing Pad Intellimouse Mode(scrolling wheel, 4th and 5th buttons) @@ -44,7 +44,7 @@ B) MSID 6: Horizontal and Vertical scrolling. Packet 1 Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 BYTE |---------------|BYTE |---------------|BYTE|---------------|BYTE|---------------| - 1 |Y|X|y|x|1|M|R|L| 2 |X|X|X|X|X|X|X|X| 3 |Y|Y|Y|Y|Y|Y|Y|Y| 4 | | |B|F|l|r|u|d| + 1 |Y|X|y|x|1|M|R|L| 2 |X|X|X|X|X|X|X|X| 3 |Y|Y|Y|Y|Y|Y|Y|Y| 4 | | |B|F|r|l|u|d| |---------------| |---------------| |---------------| |---------------| Byte 1: Bit7 => Y overflow @@ -59,15 +59,15 @@ Byte 2: X Movement(9-bit 2's complement integers) Byte 3: Y Movement(9-bit 2's complement integers) Byte 4: Bit0 => the Vertical scrolling movement downward. Bit1 => the Vertical scrolling movement upward. - Bit2 => the Vertical scrolling movement rightward. - Bit3 => the Vertical scrolling movement leftward. + Bit2 => the Horizontal scrolling movement leftward. + Bit3 => the Horizontal scrolling movement rightward. Bit4 => 1 = 4th mouse button is pressed, Forward one page. 0 = 4th mouse button is not pressed. Bit5 => 1 = 5th mouse button is pressed, Backward one page. 0 = 5th mouse button is not pressed. C) MSID 7: -# FSP uses 2 packets(8 Bytes) data to represent Absolute Position +# FSP uses 2 packets (8 Bytes) to represent Absolute Position. so we have PACKET NUMBER to identify packets. If PACKET NUMBER is 0, the packet is Packet 1. If PACKET NUMBER is 1, the packet is Packet 2. @@ -129,7 +129,7 @@ Byte 3: Message Type => 0x00 (Disabled) Byte 4: Bit7~Bit0 => Don't Care ============================================================================== -* Absolute position for STL3888-A0. +* Absolute position for STL3888-Ax. ============================================================================== Packet 1 (ABSOLUTE POSITION) Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 @@ -179,14 +179,14 @@ Byte 4: Bit1~Bit0 => Y coordinate (xpos[1:0]) Bit5~Bit4 => y2_g Bit7~Bit6 => x2_g -Notify Packet for STL3888-A0 +Notify Packet for STL3888-Ax Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 BYTE |---------------|BYTE |---------------|BYTE|---------------|BYTE|---------------| 1 |1|0|1|P|1|M|R|L| 2 |C|C|C|C|C|C|C|C| 3 |0|0|F|F|0|0|0|i| 4 |r|l|d|u|0|0|0|0| |---------------| |---------------| |---------------| |---------------| Byte 1: Bit7~Bit6 => 00, Normal data packet - => 01, Absolute coordination packet + => 01, Absolute coordinates packet => 10, Notify packet Bit5 => 1 Bit4 => when in absolute coordinates mode (valid when EN_PKT_GO is 1): @@ -205,15 +205,106 @@ Byte 4: Bit7 => scroll right button Bit6 => scroll left button Bit5 => scroll down button Bit4 => scroll up button - * Note that if gesture and additional button (Bit4~Bit7) - happen at the same time, the button information will not - be sent. + * Note that if gesture and additional buttoni (Bit4~Bit7) + happen at the same time, the button information will not + be sent. + Bit3~Bit0 => Reserved + +Sample sequence of Multi-finger, Multi-coordinate mode: + + notify packet (valid bit == 1), abs pkt 1, abs pkt 2, abs pkt 1, + abs pkt 2, ..., notify packet (valid bit == 0) + +============================================================================== +* Absolute position for STL3888-B0. +============================================================================== +Packet 1(ABSOLUTE POSITION) + Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +BYTE |---------------|BYTE |---------------|BYTE|---------------|BYTE|---------------| + 1 |0|1|V|F|1|0|R|L| 2 |X|X|X|X|X|X|X|X| 3 |Y|Y|Y|Y|Y|Y|Y|Y| 4 |r|l|u|d|X|X|Y|Y| + |---------------| |---------------| |---------------| |---------------| + +Byte 1: Bit7~Bit6 => 00, Normal data packet + => 01, Absolute coordinates packet + => 10, Notify packet + Bit5 => Valid bit, 0 means that the coordinate is invalid or finger up. + When both fingers are up, the last two reports have zero valid + bit. + Bit4 => finger up/down information. 1: finger down, 0: finger up. + Bit3 => 1 + Bit2 => finger index, 0 is the first finger, 1 is the second finger. + Bit1 => Right Button, 1 is pressed, 0 is not pressed. + Bit0 => Left Button, 1 is pressed, 0 is not pressed. +Byte 2: X coordinate (xpos[9:2]) +Byte 3: Y coordinate (ypos[9:2]) +Byte 4: Bit1~Bit0 => Y coordinate (xpos[1:0]) + Bit3~Bit2 => X coordinate (ypos[1:0]) + Bit4 => scroll down button + Bit5 => scroll up button + Bit6 => scroll left button + Bit7 => scroll right button + +Packet 2 (ABSOLUTE POSITION) + Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +BYTE |---------------|BYTE |---------------|BYTE|---------------|BYTE|---------------| + 1 |0|1|V|F|1|1|R|L| 2 |X|X|X|X|X|X|X|X| 3 |Y|Y|Y|Y|Y|Y|Y|Y| 4 |r|l|u|d|X|X|Y|Y| + |---------------| |---------------| |---------------| |---------------| + +Byte 1: Bit7~Bit6 => 00, Normal data packet + => 01, Absolute coordination packet + => 10, Notify packet + Bit5 => Valid bit, 0 means that the coordinate is invalid or finger up. + When both fingers are up, the last two reports have zero valid + bit. + Bit4 => finger up/down information. 1: finger down, 0: finger up. + Bit3 => 1 + Bit2 => finger index, 0 is the first finger, 1 is the second finger. + Bit1 => Right Button, 1 is pressed, 0 is not pressed. + Bit0 => Left Button, 1 is pressed, 0 is not pressed. +Byte 2: X coordinate (xpos[9:2]) +Byte 3: Y coordinate (ypos[9:2]) +Byte 4: Bit1~Bit0 => Y coordinate (xpos[1:0]) + Bit3~Bit2 => X coordinate (ypos[1:0]) + Bit4 => scroll down button + Bit5 => scroll up button + Bit6 => scroll left button + Bit7 => scroll right button + +Notify Packet for STL3888-B0 + Bit 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +BYTE |---------------|BYTE |---------------|BYTE|---------------|BYTE|---------------| + 1 |1|0|1|P|1|M|R|L| 2 |C|C|C|C|C|C|C|C| 3 |0|0|F|F|0|0|0|i| 4 |r|l|u|d|0|0|0|0| + |---------------| |---------------| |---------------| |---------------| + +Byte 1: Bit7~Bit6 => 00, Normal data packet + => 01, Absolute coordination packet + => 10, Notify packet + Bit5 => 1 + Bit4 => when in absolute coordinate mode (valid when EN_PKT_GO is 1): + 0: left button is generated by the on-pad command + 1: left button is generated by the external button + Bit3 => 1 + Bit2 => Middle Button, 1 is pressed, 0 is not pressed. + Bit1 => Right Button, 1 is pressed, 0 is not pressed. + Bit0 => Left Button, 1 is pressed, 0 is not pressed. +Byte 2: Message Type => 0xB7 (Multi Finger, Multi Coordinate mode) +Byte 3: Bit7~Bit6 => Don't care + Bit5~Bit4 => Number of fingers + Bit3~Bit1 => Reserved + Bit0 => 1: enter gesture mode; 0: leaving gesture mode +Byte 4: Bit7 => scroll right button + Bit6 => scroll left button + Bit5 => scroll up button + Bit4 => scroll down button + * Note that if gesture and additional button(Bit4~Bit7) + happen at the same time, the button information will not + be sent. Bit3~Bit0 => Reserved Sample sequence of Multi-finger, Multi-coordinate mode: notify packet (valid bit == 1), abs pkt 1, abs pkt 2, abs pkt 1, - abs pkt 2, ..., notify packet(valid bit == 0) + abs pkt 2, ..., notify packet (valid bit == 0) ============================================================================== * FSP Enable/Disable packet @@ -409,7 +500,8 @@ offset width default r/w name 0: read only, 1: read/write enable (Note that following registers does not require clock gating being enabled prior to write: 05 06 07 08 09 0c 0f 10 11 12 16 17 18 23 2e - 40 41 42 43.) + 40 41 42 43. In addition to that, this bit must be 1 when gesture + mode is enabled) 0x31 RW on-pad command detection bit7 0 RW on-pad command left button down tag @@ -463,6 +555,10 @@ offset width default r/w name absolute coordinates; otherwise, host only receives packets with relative coordinate.) + bit7 0 RW EN_PS2_F2: PS/2 gesture mode 2nd + finger packet enable + 0: disable, 1: enable + 0x43 RW on-pad control bit0 0 RW on-pad control enable 0: disable, 1: enable -- cgit v1.2.3-70-g09d2 From ec3a9039601af210fca4650d229621fe5a21df0b Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Tue, 5 Jan 2010 17:46:41 -0500 Subject: tracing/kprobe: Update example output in documentation Update example output in documentation according to current implementation. Signed-off-by: Masami Hiramatsu Cc: Frederic Weisbecker Cc: Arnaldo Carvalho de Melo Cc: systemtap Cc: DLE Cc: Steven Rostedt Cc: Frederic Weisbecker LKML-Reference: <20100105224641.19431.34967.stgit@dhcp-100-2-132.bos.redhat.com> Signed-off-by: Ingo Molnar --- Documentation/trace/kprobetrace.txt | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'Documentation') diff --git a/Documentation/trace/kprobetrace.txt b/Documentation/trace/kprobetrace.txt index 47aabeebbdf..c3eff6ff945 100644 --- a/Documentation/trace/kprobetrace.txt +++ b/Documentation/trace/kprobetrace.txt @@ -97,23 +97,24 @@ recording return value as "myretprobe" event. cat /sys/kernel/debug/tracing/events/kprobes/myprobe/format name: myprobe -ID: 75 +ID: 780 format: - field:unsigned short common_type; offset:0; size:2; - field:unsigned char common_flags; offset:2; size:1; - field:unsigned char common_preempt_count; offset:3; size:1; - field:int common_pid; offset:4; size:4; - field:int common_tgid; offset:8; size:4; - - field: unsigned long ip; offset:16;tsize:8; - field: int nargs; offset:24;tsize:4; - field: unsigned long dfd; offset:32;tsize:8; - field: unsigned long filename; offset:40;tsize:8; - field: unsigned long flags; offset:48;tsize:8; - field: unsigned long mode; offset:56;tsize:8; - -print fmt: "(%lx) dfd=%lx filename=%lx flags=%lx mode=%lx", REC->ip, REC->dfd, REC->filename, REC->flags, REC->mode - + field:unsigned short common_type; offset:0; size:2; signed:0; + field:unsigned char common_flags; offset:2; size:1; signed:0; + field:unsigned char common_preempt_count; offset:3; size:1;signed:0; + field:int common_pid; offset:4; size:4; signed:1; + field:int common_lock_depth; offset:8; size:4; signed:1; + + field:unsigned long __probe_ip; offset:12; size:4; signed:0; + field:int __probe_nargs; offset:16; size:4; signed:1; + field:unsigned long dfd; offset:20; size:4; signed:0; + field:unsigned long filename; offset:24; size:4; signed:0; + field:unsigned long flags; offset:28; size:4; signed:0; + field:unsigned long mode; offset:32; size:4; signed:0; + + +print fmt: "(%lx) dfd=%lx filename=%lx flags=%lx mode=%lx", REC->__probe_ip, +REC->dfd, REC->filename, REC->flags, REC->mode You can see that the event has 4 arguments as in the expressions you specified. -- cgit v1.2.3-70-g09d2 From 14640106f243a3b29944d7198569090fa6546f2d Mon Sep 17 00:00:00 2001 From: Masami Hiramatsu Date: Tue, 5 Jan 2010 17:46:48 -0500 Subject: tracing/kprobe: Drop function argument access syntax Drop function argument access syntax, because the function arguments depend on not only architecture but also compile-options and function API. And now, we have perf-probe for finding register/memory assigned to each argument. Signed-off-by: Masami Hiramatsu Cc: Frederic Weisbecker Cc: Arnaldo Carvalho de Melo Cc: systemtap Cc: DLE Cc: Frederic Weisbecker Cc: Steven Rostedt Cc: Roland McGrath Cc: Oleg Nesterov Cc: Mahesh Salgaonkar Cc: Benjamin Herrenschmidt Cc: Michael Neuling Cc: linuxppc-dev@ozlabs.org LKML-Reference: <20100105224648.19431.52309.stgit@dhcp-100-2-132.bos.redhat.com> Signed-off-by: Ingo Molnar --- Documentation/trace/kprobetrace.txt | 21 ++++++++++----------- kernel/trace/trace_kprobe.c | 18 +----------------- 2 files changed, 11 insertions(+), 28 deletions(-) (limited to 'Documentation') diff --git a/Documentation/trace/kprobetrace.txt b/Documentation/trace/kprobetrace.txt index c3eff6ff945..f30978e001f 100644 --- a/Documentation/trace/kprobetrace.txt +++ b/Documentation/trace/kprobetrace.txt @@ -37,15 +37,12 @@ Synopsis of kprobe_events @SYM[+|-offs] : Fetch memory at SYM +|- offs (SYM should be a data symbol) $stackN : Fetch Nth entry of stack (N >= 0) $stack : Fetch stack address. - $argN : Fetch function argument. (N >= 0)(*) - $retval : Fetch return value.(**) - +|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(***) + $retval : Fetch return value.(*) + +|-offs(FETCHARG) : Fetch memory at FETCHARG +|- offs address.(**) NAME=FETCHARG: Set NAME as the argument name of FETCHARG. - (*) aN may not correct on asmlinkaged functions and at the middle of - function body. - (**) only for return probe. - (***) this is useful for fetching a field of data structures. + (*) only for return probe. + (**) this is useful for fetching a field of data structures. Per-Probe Event Filtering @@ -82,11 +79,14 @@ Usage examples To add a probe as a new event, write a new definition to kprobe_events as below. - echo p:myprobe do_sys_open dfd=$arg0 filename=$arg1 flags=$arg2 mode=$arg3 > /sys/kernel/debug/tracing/kprobe_events + echo p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack) > /sys/kernel/debug/tracing/kprobe_events This sets a kprobe on the top of do_sys_open() function with recording -1st to 4th arguments as "myprobe" event. As this example shows, users can -choose more familiar names for each arguments. +1st to 4th arguments as "myprobe" event. Note, which register/stack entry is +assigned to each function argument depends on arch-specific ABI. If you unsure +the ABI, please try to use probe subcommand of perf-tools (you can find it +under tools/perf/). +As this example shows, users can choose more familiar names for each arguments. echo r:myretprobe do_sys_open $retval >> /sys/kernel/debug/tracing/kprobe_events @@ -147,4 +147,3 @@ events, you need to enable it. returns from SYMBOL(e.g. "sys_open+0x1b/0x1d <- do_sys_open" means kernel returns from do_sys_open to sys_open+0x1b). - diff --git a/kernel/trace/trace_kprobe.c b/kernel/trace/trace_kprobe.c index 47f54ab57b6..7ac728ded96 100644 --- a/kernel/trace/trace_kprobe.c +++ b/kernel/trace/trace_kprobe.c @@ -91,11 +91,6 @@ static __kprobes unsigned long fetch_memory(struct pt_regs *regs, void *addr) return retval; } -static __kprobes unsigned long fetch_argument(struct pt_regs *regs, void *num) -{ - return regs_get_argument_nth(regs, (unsigned int)((unsigned long)num)); -} - static __kprobes unsigned long fetch_retvalue(struct pt_regs *regs, void *dummy) { @@ -231,9 +226,7 @@ static int probe_arg_string(char *buf, size_t n, struct fetch_func *ff) { int ret = -EINVAL; - if (ff->func == fetch_argument) - ret = snprintf(buf, n, "$arg%lu", (unsigned long)ff->data); - else if (ff->func == fetch_register) { + if (ff->func == fetch_register) { const char *name; name = regs_query_register_name((unsigned int)((long)ff->data)); ret = snprintf(buf, n, "%%%s", name); @@ -489,14 +482,6 @@ static int parse_probe_vars(char *arg, struct fetch_func *ff, int is_return) } } else ret = -EINVAL; - } else if (strncmp(arg, "arg", 3) == 0 && isdigit(arg[3])) { - ret = strict_strtoul(arg + 3, 10, ¶m); - if (ret || param > PARAM_MAX_ARGS) - ret = -EINVAL; - else { - ff->func = fetch_argument; - ff->data = (void *)param; - } } else ret = -EINVAL; return ret; @@ -611,7 +596,6 @@ static int create_trace_probe(int argc, char **argv) * - Add kprobe: p[:[GRP/]EVENT] KSYM[+OFFS]|KADDR [FETCHARGS] * - Add kretprobe: r[:[GRP/]EVENT] KSYM[+0] [FETCHARGS] * Fetch args: - * $argN : fetch Nth of function argument. (N:0-) * $retval : fetch return value * $stack : fetch stack address * $stackN : fetch Nth of stack (N:0-) -- cgit v1.2.3-70-g09d2 From 408bffd01cfcda2907b07fb86b3666e3db86fd82 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 14 Jan 2010 09:19:46 +0100 Subject: ALSA: ctxfi - Add subsystem option Added a new option "subsystem" to override the PCI SSID for identifying the card type. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/ALSA-Configuration.txt | 3 +++ sound/pci/ctxfi/ctatc.c | 23 +++++++++++++++-------- sound/pci/ctxfi/ctatc.h | 2 +- sound/pci/ctxfi/xfi.c | 5 ++++- 4 files changed, 23 insertions(+), 10 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index c540637eb16..c83fd7b6420 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -482,6 +482,9 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. reference_rate - reference sample rate, 44100 or 48000 (default) multiple - multiple to ref. sample rate, 1 or 2 (default) + subsystem - override the PCI SSID for probing; the value + consists of SSVID << 16 | SSDID. The default is + zero, which means no override. This module supports multiple cards. diff --git a/sound/pci/ctxfi/ctatc.c b/sound/pci/ctxfi/ctatc.c index cb65bd0dd35..903594e6ed7 100644 --- a/sound/pci/ctxfi/ctatc.c +++ b/sound/pci/ctxfi/ctatc.c @@ -1225,10 +1225,11 @@ static int atc_dev_free(struct snd_device *dev) return ct_atc_destroy(atc); } -static int __devinit atc_identify_card(struct ct_atc *atc) +static int __devinit atc_identify_card(struct ct_atc *atc, unsigned int ssid) { const struct snd_pci_quirk *p; const struct snd_pci_quirk *list; + u16 vendor_id, device_id; switch (atc->chip_type) { case ATC20K1: @@ -1242,13 +1243,19 @@ static int __devinit atc_identify_card(struct ct_atc *atc) default: return -ENOENT; } - p = snd_pci_quirk_lookup(atc->pci, list); + if (ssid) { + vendor_id = ssid >> 16; + device_id = ssid & 0xffff; + } else { + vendor_id = atc->pci->subsystem_vendor; + device_id = atc->pci->subsystem_device; + } + p = snd_pci_quirk_lookup_id(vendor_id, device_id, list); if (p) { if (p->value < 0) { printk(KERN_ERR "ctxfi: " "Device %04x:%04x is black-listed\n", - atc->pci->subsystem_vendor, - atc->pci->subsystem_device); + vendor_id, device_id); return -ENOENT; } atc->model = p->value; @@ -1261,8 +1268,7 @@ static int __devinit atc_identify_card(struct ct_atc *atc) atc->model_name = ct_subsys_name[atc->model]; snd_printd("ctxfi: chip %s model %s (%04x:%04x) is found\n", atc->chip_name, atc->model_name, - atc->pci->subsystem_vendor, - atc->pci->subsystem_device); + vendor_id, device_id); return 0; } @@ -1636,7 +1642,8 @@ static struct ct_atc atc_preset __devinitdata = { int __devinit ct_atc_create(struct snd_card *card, struct pci_dev *pci, unsigned int rsr, unsigned int msr, - int chip_type, struct ct_atc **ratc) + int chip_type, unsigned int ssid, + struct ct_atc **ratc) { struct ct_atc *atc; static struct snd_device_ops ops = { @@ -1662,7 +1669,7 @@ int __devinit ct_atc_create(struct snd_card *card, struct pci_dev *pci, mutex_init(&atc->atc_mutex); /* Find card model */ - err = atc_identify_card(atc); + err = atc_identify_card(atc, ssid); if (err < 0) { printk(KERN_ERR "ctatc: Card not recognised\n"); goto error1; diff --git a/sound/pci/ctxfi/ctatc.h b/sound/pci/ctxfi/ctatc.h index 9fd8a570894..7167c0185d5 100644 --- a/sound/pci/ctxfi/ctatc.h +++ b/sound/pci/ctxfi/ctatc.h @@ -148,7 +148,7 @@ struct ct_atc { int __devinit ct_atc_create(struct snd_card *card, struct pci_dev *pci, unsigned int rsr, unsigned int msr, int chip_type, - struct ct_atc **ratc); + unsigned int subsysid, struct ct_atc **ratc); int __devinit ct_atc_create_alsa_devs(struct ct_atc *atc); #endif /* CTATC_H */ diff --git a/sound/pci/ctxfi/xfi.c b/sound/pci/ctxfi/xfi.c index 76541748e7b..ed44ed788b6 100644 --- a/sound/pci/ctxfi/xfi.c +++ b/sound/pci/ctxfi/xfi.c @@ -32,6 +32,7 @@ module_param(multiple, uint, S_IRUGO); static int index[SNDRV_CARDS] = SNDRV_DEFAULT_IDX; static char *id[SNDRV_CARDS] = SNDRV_DEFAULT_STR; static int enable[SNDRV_CARDS] = SNDRV_DEFAULT_ENABLE_PNP; +static unsigned int subsystem[SNDRV_CARDS]; module_param_array(index, int, NULL, 0444); MODULE_PARM_DESC(index, "Index value for Creative X-Fi driver"); @@ -39,6 +40,8 @@ module_param_array(id, charp, NULL, 0444); MODULE_PARM_DESC(id, "ID string for Creative X-Fi driver"); module_param_array(enable, bool, NULL, 0444); MODULE_PARM_DESC(enable, "Enable Creative X-Fi driver"); +module_param_array(subsystem, int, NULL, 0444); +MODULE_PARM_DESC(subsystem, "Override subsystem ID for Creative X-Fi driver"); static struct pci_device_id ct_pci_dev_ids[] = { /* only X-Fi is supported, so... */ @@ -85,7 +88,7 @@ ct_card_probe(struct pci_dev *pci, const struct pci_device_id *pci_id) multiple = 2; } err = ct_atc_create(card, pci, reference_rate, multiple, - pci_id->driver_data, &atc); + pci_id->driver_data, subsystem[dev], &atc); if (err < 0) goto error; -- cgit v1.2.3-70-g09d2 From 9a58a80a701bdb2d220cdab4914218df5b48d781 Mon Sep 17 00:00:00 2001 From: Alexey Dobriyan Date: Thu, 14 Jan 2010 03:10:54 -0800 Subject: proc_fops: convert drivers/isdn/ to seq_file Convert code away from ->read_proc/->write_proc interfaces. Switch to proc_create()/proc_create_data() which make addition of proc entries reliable wrt NULL ->proc_fops, NULL ->data and so on. Problem with ->read_proc et al is described here commit 786d7e1612f0b0adb6046f19b906609e4fe8b1ba "Fix rmmod/read/write races in /proc entries" [akpm@linux-foundation.org: CONFIG_PROC_FS=n build fix] Signed-off-by: Alexey Dobriyan Signed-off-by: Tilman Schmidt Signed-off-by: Karsten Keil Signed-off-by: David S. Miller --- Documentation/isdn/INTERFACE.CAPI | 9 +- drivers/isdn/capi/capi.c | 99 ++++++---------- drivers/isdn/capi/capidrv.c | 55 +++------ drivers/isdn/capi/kcapi.c | 8 +- drivers/isdn/gigaset/capi.c | 75 ++++++------ drivers/isdn/hardware/avm/avmcard.h | 6 +- drivers/isdn/hardware/avm/b1.c | 54 +++++---- drivers/isdn/hardware/avm/b1dma.c | 71 ++++++------ drivers/isdn/hardware/avm/b1isa.c | 2 +- drivers/isdn/hardware/avm/b1pci.c | 4 +- drivers/isdn/hardware/avm/b1pcmcia.c | 2 +- drivers/isdn/hardware/avm/c4.c | 53 +++++---- drivers/isdn/hardware/avm/t1isa.c | 2 +- drivers/isdn/hardware/avm/t1pci.c | 2 +- drivers/isdn/hardware/eicon/capimain.c | 40 ++++--- drivers/isdn/hardware/eicon/diva_didd.c | 45 ++++---- drivers/isdn/hardware/eicon/divasi.c | 48 ++++---- drivers/isdn/hardware/eicon/divasproc.c | 198 ++++++++++++++------------------ drivers/isdn/hysdn/hycapi.c | 56 ++++----- include/linux/isdn/capilli.h | 3 +- net/bluetooth/cmtp/capi.c | 37 +++--- 21 files changed, 411 insertions(+), 458 deletions(-) (limited to 'Documentation') diff --git a/Documentation/isdn/INTERFACE.CAPI b/Documentation/isdn/INTERFACE.CAPI index 5fe8de5cc72..f172091fb7c 100644 --- a/Documentation/isdn/INTERFACE.CAPI +++ b/Documentation/isdn/INTERFACE.CAPI @@ -149,10 +149,11 @@ char *(*procinfo)(struct capi_ctr *ctrlr) pointer to a callback function returning the entry for the device in the CAPI controller info table, /proc/capi/controller -read_proc_t *ctr_read_proc - pointer to the read_proc callback function for the device's proc file - system entry, /proc/capi/controllers/; will be called with a - pointer to the device's capi_ctr structure as the last (data) argument +const struct file_operations *proc_fops + pointers to callback functions for the device's proc file + system entry, /proc/capi/controllers/; pointer to the device's + capi_ctr structure is available from struct proc_dir_entry::data + which is available from struct inode. Note: Callback functions except send_message() are never called in interrupt context. diff --git a/drivers/isdn/capi/capi.c b/drivers/isdn/capi/capi.c index 65bf91e16a4..79f9364aded 100644 --- a/drivers/isdn/capi/capi.c +++ b/drivers/isdn/capi/capi.c @@ -33,6 +33,7 @@ #endif /* CONFIG_ISDN_CAPI_MIDDLEWARE */ #include #include +#include #include #include #include @@ -1407,114 +1408,84 @@ static void capinc_tty_exit(void) * /proc/capi/capi20: * minor applid nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt */ -static int proc_capidev_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int capi20_proc_show(struct seq_file *m, void *v) { struct capidev *cdev; struct list_head *l; - int len = 0; read_lock(&capidev_list_lock); list_for_each(l, &capidev_list) { cdev = list_entry(l, struct capidev, list); - len += sprintf(page+len, "0 %d %lu %lu %lu %lu\n", + seq_printf(m, "0 %d %lu %lu %lu %lu\n", cdev->ap.applid, cdev->ap.nrecvctlpkt, cdev->ap.nrecvdatapkt, cdev->ap.nsentctlpkt, cdev->ap.nsentdatapkt); - if (len <= off) { - off -= len; - len = 0; - } else { - if (len-off > count) - goto endloop; - } } - -endloop: read_unlock(&capidev_list_lock); - if (len < count) - *eof = 1; - if (len > count) len = count; - if (len < 0) len = 0; - return len; + return 0; } +static int capi20_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, capi20_proc_show, NULL); +} + +static const struct file_operations capi20_proc_fops = { + .owner = THIS_MODULE, + .open = capi20_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + /* * /proc/capi/capi20ncci: * applid ncci */ -static int proc_capincci_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int capi20ncci_proc_show(struct seq_file *m, void *v) { struct capidev *cdev; struct capincci *np; struct list_head *l; - int len = 0; read_lock(&capidev_list_lock); list_for_each(l, &capidev_list) { cdev = list_entry(l, struct capidev, list); for (np=cdev->nccis; np; np = np->next) { - len += sprintf(page+len, "%d 0x%x\n", + seq_printf(m, "%d 0x%x\n", cdev->ap.applid, np->ncci); - if (len <= off) { - off -= len; - len = 0; - } else { - if (len-off > count) - goto endloop; - } } } -endloop: read_unlock(&capidev_list_lock); - *start = page+off; - if (len < count) - *eof = 1; - if (len>count) len = count; - if (len<0) len = 0; - return len; + return 0; } -static struct procfsentries { - char *name; - mode_t mode; - int (*read_proc)(char *page, char **start, off_t off, - int count, int *eof, void *data); - struct proc_dir_entry *procent; -} procfsentries[] = { - /* { "capi", S_IFDIR, 0 }, */ - { "capi/capi20", 0 , proc_capidev_read_proc }, - { "capi/capi20ncci", 0 , proc_capincci_read_proc }, +static int capi20ncci_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, capi20ncci_proc_show, NULL); +} + +static const struct file_operations capi20ncci_proc_fops = { + .owner = THIS_MODULE, + .open = capi20ncci_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, }; static void __init proc_init(void) { - int nelem = ARRAY_SIZE(procfsentries); - int i; - - for (i=0; i < nelem; i++) { - struct procfsentries *p = procfsentries + i; - p->procent = create_proc_entry(p->name, p->mode, NULL); - if (p->procent) p->procent->read_proc = p->read_proc; - } + proc_create("capi/capi20", 0, NULL, &capi20_proc_fops); + proc_create("capi/capi20ncci", 0, NULL, &capi20ncci_proc_fops); } static void __exit proc_exit(void) { - int nelem = ARRAY_SIZE(procfsentries); - int i; - - for (i=nelem-1; i >= 0; i--) { - struct procfsentries *p = procfsentries + i; - if (p->procent) { - remove_proc_entry(p->name, NULL); - p->procent = NULL; - } - } + remove_proc_entry("capi/capi20", NULL); + remove_proc_entry("capi/capi20ncci", NULL); } /* -------- init function and module interface ---------------------- */ diff --git a/drivers/isdn/capi/capidrv.c b/drivers/isdn/capi/capidrv.c index 66b7d7a8647..bb450152fb7 100644 --- a/drivers/isdn/capi/capidrv.c +++ b/drivers/isdn/capi/capidrv.c @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -2229,59 +2230,37 @@ static void lower_callback(unsigned int cmd, u32 contr, void *data) * /proc/capi/capidrv: * nrecvctlpkt nrecvdatapkt nsendctlpkt nsenddatapkt */ -static int proc_capidrv_read_proc(char *page, char **start, off_t off, - int count, int *eof, void *data) +static int capidrv_proc_show(struct seq_file *m, void *v) { - int len = 0; - - len += sprintf(page+len, "%lu %lu %lu %lu\n", + seq_printf(m, "%lu %lu %lu %lu\n", global.ap.nrecvctlpkt, global.ap.nrecvdatapkt, global.ap.nsentctlpkt, global.ap.nsentdatapkt); - if (off+count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len-off) ? count : len-off); + return 0; +} + +static int capidrv_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, capidrv_proc_show, NULL); } -static struct procfsentries { - char *name; - mode_t mode; - int (*read_proc)(char *page, char **start, off_t off, - int count, int *eof, void *data); - struct proc_dir_entry *procent; -} procfsentries[] = { - /* { "capi", S_IFDIR, 0 }, */ - { "capi/capidrv", 0 , proc_capidrv_read_proc }, +static const struct file_operations capidrv_proc_fops = { + .owner = THIS_MODULE, + .open = capidrv_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, }; static void __init proc_init(void) { - int nelem = ARRAY_SIZE(procfsentries); - int i; - - for (i=0; i < nelem; i++) { - struct procfsentries *p = procfsentries + i; - p->procent = create_proc_entry(p->name, p->mode, NULL); - if (p->procent) p->procent->read_proc = p->read_proc; - } + proc_create("capi/capidrv", 0, NULL, &capidrv_proc_fops); } static void __exit proc_exit(void) { - int nelem = ARRAY_SIZE(procfsentries); - int i; - - for (i=nelem-1; i >= 0; i--) { - struct procfsentries *p = procfsentries + i; - if (p->procent) { - remove_proc_entry(p->name, NULL); - p->procent = NULL; - } - } + remove_proc_entry("capi/capidrv", NULL); } static int __init capidrv_init(void) diff --git a/drivers/isdn/capi/kcapi.c b/drivers/isdn/capi/kcapi.c index dc506ab99ca..b0bacf377c1 100644 --- a/drivers/isdn/capi/kcapi.c +++ b/drivers/isdn/capi/kcapi.c @@ -490,13 +490,7 @@ attach_capi_ctr(struct capi_ctr *card) card->traceflag = showcapimsgs; sprintf(card->procfn, "capi/controllers/%d", card->cnr); - card->procent = create_proc_entry(card->procfn, 0, NULL); - if (card->procent) { - card->procent->read_proc = - (int (*)(char *,char **,off_t,int,int *,void *)) - card->ctr_read_proc; - card->procent->data = card; - } + card->procent = proc_create_data(card->procfn, 0, NULL, card->proc_fops, card); ncards++; printk(KERN_NOTICE "kcapi: Controller [%03d]: %s attached\n", diff --git a/drivers/isdn/gigaset/capi.c b/drivers/isdn/gigaset/capi.c index 3f5cd06af10..6f0ae32906b 100644 --- a/drivers/isdn/gigaset/capi.c +++ b/drivers/isdn/gigaset/capi.c @@ -13,6 +13,8 @@ #include "gigaset.h" #include +#include +#include #include #include #include @@ -2106,35 +2108,22 @@ static char *gigaset_procinfo(struct capi_ctr *ctr) return ctr->name; /* ToDo: more? */ } -/** - * gigaset_ctr_read_proc() - build controller proc file entry - * @page: buffer of PAGE_SIZE bytes for receiving the entry. - * @start: unused. - * @off: unused. - * @count: unused. - * @eof: unused. - * @ctr: controller descriptor structure. - * - * Return value: length of generated entry - */ -static int gigaset_ctr_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctr) +static int gigaset_proc_show(struct seq_file *m, void *v) { + struct capi_ctr *ctr = m->private; struct cardstate *cs = ctr->driverdata; char *s; int i; - int len = 0; - len += sprintf(page+len, "%-16s %s\n", "name", ctr->name); - len += sprintf(page+len, "%-16s %s %s\n", "dev", + + seq_printf(m, "%-16s %s\n", "name", ctr->name); + seq_printf(m, "%-16s %s %s\n", "dev", dev_driver_string(cs->dev), dev_name(cs->dev)); - len += sprintf(page+len, "%-16s %d\n", "id", cs->myid); + seq_printf(m, "%-16s %d\n", "id", cs->myid); if (cs->gotfwver) - len += sprintf(page+len, "%-16s %d.%d.%d.%d\n", "firmware", + seq_printf(m, "%-16s %d.%d.%d.%d\n", "firmware", cs->fwver[0], cs->fwver[1], cs->fwver[2], cs->fwver[3]); - len += sprintf(page+len, "%-16s %d\n", "channels", - cs->channels); - len += sprintf(page+len, "%-16s %s\n", "onechannel", - cs->onechannel ? "yes" : "no"); + seq_printf(m, "%-16s %d\n", "channels", cs->channels); + seq_printf(m, "%-16s %s\n", "onechannel", cs->onechannel ? "yes" : "no"); switch (cs->mode) { case M_UNKNOWN: @@ -2152,7 +2141,7 @@ static int gigaset_ctr_read_proc(char *page, char **start, off_t off, default: s = "??"; } - len += sprintf(page+len, "%-16s %s\n", "mode", s); + seq_printf(m, "%-16s %s\n", "mode", s); switch (cs->mstate) { case MS_UNINITIALIZED: @@ -2176,25 +2165,21 @@ static int gigaset_ctr_read_proc(char *page, char **start, off_t off, default: s = "??"; } - len += sprintf(page+len, "%-16s %s\n", "mstate", s); + seq_printf(m, "%-16s %s\n", "mstate", s); - len += sprintf(page+len, "%-16s %s\n", "running", - cs->running ? "yes" : "no"); - len += sprintf(page+len, "%-16s %s\n", "connected", - cs->connected ? "yes" : "no"); - len += sprintf(page+len, "%-16s %s\n", "isdn_up", - cs->isdn_up ? "yes" : "no"); - len += sprintf(page+len, "%-16s %s\n", "cidmode", - cs->cidmode ? "yes" : "no"); + seq_printf(m, "%-16s %s\n", "running", cs->running ? "yes" : "no"); + seq_printf(m, "%-16s %s\n", "connected", cs->connected ? "yes" : "no"); + seq_printf(m, "%-16s %s\n", "isdn_up", cs->isdn_up ? "yes" : "no"); + seq_printf(m, "%-16s %s\n", "cidmode", cs->cidmode ? "yes" : "no"); for (i = 0; i < cs->channels; i++) { - len += sprintf(page+len, "[%d]%-13s %d\n", i, "corrupted", + seq_printf(m, "[%d]%-13s %d\n", i, "corrupted", cs->bcs[i].corrupted); - len += sprintf(page+len, "[%d]%-13s %d\n", i, "trans_down", + seq_printf(m, "[%d]%-13s %d\n", i, "trans_down", cs->bcs[i].trans_down); - len += sprintf(page+len, "[%d]%-13s %d\n", i, "trans_up", + seq_printf(m, "[%d]%-13s %d\n", i, "trans_up", cs->bcs[i].trans_up); - len += sprintf(page+len, "[%d]%-13s %d\n", i, "chstate", + seq_printf(m, "[%d]%-13s %d\n", i, "chstate", cs->bcs[i].chstate); switch (cs->bcs[i].proto2) { case L2_BITSYNC: @@ -2209,11 +2194,23 @@ static int gigaset_ctr_read_proc(char *page, char **start, off_t off, default: s = "??"; } - len += sprintf(page+len, "[%d]%-13s %s\n", i, "proto2", s); + seq_printf(m, "[%d]%-13s %s\n", i, "proto2", s); } - return len; + return 0; } +static int gigaset_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, gigaset_proc_show, PDE(inode)->data); +} + +static const struct file_operations gigaset_proc_fops = { + .owner = THIS_MODULE, + .open = gigaset_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; static struct capi_driver capi_driver_gigaset = { .name = "gigaset", @@ -2256,7 +2253,7 @@ int gigaset_isdn_register(struct cardstate *cs, const char *isdnid) iif->ctr.release_appl = gigaset_release_appl; iif->ctr.send_message = gigaset_send_message; iif->ctr.procinfo = gigaset_procinfo; - iif->ctr.ctr_read_proc = gigaset_ctr_read_proc; + iif->ctr.proc_fops = &gigaset_proc_fops; INIT_LIST_HEAD(&iif->appls); skb_queue_head_init(&iif->sendqueue); atomic_set(&iif->sendqlen, 0); diff --git a/drivers/isdn/hardware/avm/avmcard.h b/drivers/isdn/hardware/avm/avmcard.h index d964f07e4a5..a70e8854461 100644 --- a/drivers/isdn/hardware/avm/avmcard.h +++ b/drivers/isdn/hardware/avm/avmcard.h @@ -556,8 +556,7 @@ u16 b1_send_message(struct capi_ctr *ctrl, struct sk_buff *skb); void b1_parse_version(avmctrl_info *card); irqreturn_t b1_interrupt(int interrupt, void *devptr); -int b1ctl_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctrl); +extern const struct file_operations b1ctl_proc_fops; avmcard_dmainfo *avmcard_dma_alloc(char *name, struct pci_dev *, long rsize, long ssize); @@ -577,7 +576,6 @@ void b1dma_register_appl(struct capi_ctr *ctrl, capi_register_params *rp); void b1dma_release_appl(struct capi_ctr *ctrl, u16 appl); u16 b1dma_send_message(struct capi_ctr *ctrl, struct sk_buff *skb); -int b1dmactl_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctrl); +extern const struct file_operations b1dmactl_proc_fops; #endif /* _AVMCARD_H_ */ diff --git a/drivers/isdn/hardware/avm/b1.c b/drivers/isdn/hardware/avm/b1.c index a7c0083e78a..c38fa0f4c72 100644 --- a/drivers/isdn/hardware/avm/b1.c +++ b/drivers/isdn/hardware/avm/b1.c @@ -12,6 +12,8 @@ #include #include #include +#include +#include #include #include #include @@ -634,18 +636,17 @@ irqreturn_t b1_interrupt(int interrupt, void *devptr) } /* ------------------------------------------------------------- */ -int b1ctl_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctrl) +static int b1ctl_proc_show(struct seq_file *m, void *v) { + struct capi_ctr *ctrl = m->private; avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); avmcard *card = cinfo->card; u8 flag; - int len = 0; char *s; - len += sprintf(page+len, "%-16s %s\n", "name", card->name); - len += sprintf(page+len, "%-16s 0x%x\n", "io", card->port); - len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); + seq_printf(m, "%-16s %s\n", "name", card->name); + seq_printf(m, "%-16s 0x%x\n", "io", card->port); + seq_printf(m, "%-16s %d\n", "irq", card->irq); switch (card->cardtype) { case avm_b1isa: s = "B1 ISA"; break; case avm_b1pci: s = "B1 PCI"; break; @@ -658,20 +659,20 @@ int b1ctl_read_proc(char *page, char **start, off_t off, case avm_c2: s = "C2"; break; default: s = "???"; break; } - len += sprintf(page+len, "%-16s %s\n", "type", s); + seq_printf(m, "%-16s %s\n", "type", s); if (card->cardtype == avm_t1isa) - len += sprintf(page+len, "%-16s %d\n", "cardnr", card->cardnr); + seq_printf(m, "%-16s %d\n", "cardnr", card->cardnr); if ((s = cinfo->version[VER_DRIVER]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + seq_printf(m, "%-16s %s\n", "ver_driver", s); if ((s = cinfo->version[VER_CARDTYPE]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); if ((s = cinfo->version[VER_SERIAL]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + seq_printf(m, "%-16s %s\n", "ver_serial", s); if (card->cardtype != avm_m1) { flag = ((u8 *)(ctrl->profile.manu))[3]; if (flag) - len += sprintf(page+len, "%-16s%s%s%s%s%s%s%s\n", + seq_printf(m, "%-16s%s%s%s%s%s%s%s\n", "protocol", (flag & 0x01) ? " DSS1" : "", (flag & 0x02) ? " CT1" : "", @@ -685,7 +686,7 @@ int b1ctl_read_proc(char *page, char **start, off_t off, if (card->cardtype != avm_m1) { flag = ((u8 *)(ctrl->profile.manu))[5]; if (flag) - len += sprintf(page+len, "%-16s%s%s%s%s\n", + seq_printf(m, "%-16s%s%s%s%s\n", "linetype", (flag & 0x01) ? " point to point" : "", (flag & 0x02) ? " point to multipoint" : "", @@ -693,16 +694,25 @@ int b1ctl_read_proc(char *page, char **start, off_t off, (flag & 0x04) ? " leased line with D-channel" : "" ); } - len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); - - if (off+count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len-off) ? count : len-off); + seq_printf(m, "%-16s %s\n", "cardname", cinfo->cardname); + + return 0; +} + +static int b1ctl_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, b1ctl_proc_show, PDE(inode)->data); } +const struct file_operations b1ctl_proc_fops = { + .owner = THIS_MODULE, + .open = b1ctl_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +EXPORT_SYMBOL(b1ctl_proc_fops); + /* ------------------------------------------------------------- */ #ifdef CONFIG_PCI @@ -781,8 +791,6 @@ EXPORT_SYMBOL(b1_send_message); EXPORT_SYMBOL(b1_parse_version); EXPORT_SYMBOL(b1_interrupt); -EXPORT_SYMBOL(b1ctl_read_proc); - static int __init b1_init(void) { char *p; diff --git a/drivers/isdn/hardware/avm/b1dma.c b/drivers/isdn/hardware/avm/b1dma.c index 0e84aaae43f..124550d0dbf 100644 --- a/drivers/isdn/hardware/avm/b1dma.c +++ b/drivers/isdn/hardware/avm/b1dma.c @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include #include @@ -855,21 +857,20 @@ u16 b1dma_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) /* ------------------------------------------------------------- */ -int b1dmactl_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctrl) +static int b1dmactl_proc_show(struct seq_file *m, void *v) { + struct capi_ctr *ctrl = m->private; avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); avmcard *card = cinfo->card; u8 flag; - int len = 0; char *s; u32 txoff, txlen, rxoff, rxlen, csr; unsigned long flags; - len += sprintf(page+len, "%-16s %s\n", "name", card->name); - len += sprintf(page+len, "%-16s 0x%x\n", "io", card->port); - len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); - len += sprintf(page+len, "%-16s 0x%lx\n", "membase", card->membase); + seq_printf(m, "%-16s %s\n", "name", card->name); + seq_printf(m, "%-16s 0x%x\n", "io", card->port); + seq_printf(m, "%-16s %d\n", "irq", card->irq); + seq_printf(m, "%-16s 0x%lx\n", "membase", card->membase); switch (card->cardtype) { case avm_b1isa: s = "B1 ISA"; break; case avm_b1pci: s = "B1 PCI"; break; @@ -882,18 +883,18 @@ int b1dmactl_read_proc(char *page, char **start, off_t off, case avm_c2: s = "C2"; break; default: s = "???"; break; } - len += sprintf(page+len, "%-16s %s\n", "type", s); + seq_printf(m, "%-16s %s\n", "type", s); if ((s = cinfo->version[VER_DRIVER]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + seq_printf(m, "%-16s %s\n", "ver_driver", s); if ((s = cinfo->version[VER_CARDTYPE]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); if ((s = cinfo->version[VER_SERIAL]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + seq_printf(m, "%-16s %s\n", "ver_serial", s); if (card->cardtype != avm_m1) { flag = ((u8 *)(ctrl->profile.manu))[3]; if (flag) - len += sprintf(page+len, "%-16s%s%s%s%s%s%s%s\n", + seq_printf(m, "%-16s%s%s%s%s%s%s%s\n", "protocol", (flag & 0x01) ? " DSS1" : "", (flag & 0x02) ? " CT1" : "", @@ -907,7 +908,7 @@ int b1dmactl_read_proc(char *page, char **start, off_t off, if (card->cardtype != avm_m1) { flag = ((u8 *)(ctrl->profile.manu))[5]; if (flag) - len += sprintf(page+len, "%-16s%s%s%s%s\n", + seq_printf(m, "%-16s%s%s%s%s\n", "linetype", (flag & 0x01) ? " point to point" : "", (flag & 0x02) ? " point to multipoint" : "", @@ -915,7 +916,7 @@ int b1dmactl_read_proc(char *page, char **start, off_t off, (flag & 0x04) ? " leased line with D-channel" : "" ); } - len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); + seq_printf(m, "%-16s %s\n", "cardname", cinfo->cardname); spin_lock_irqsave(&card->lock, flags); @@ -930,27 +931,30 @@ int b1dmactl_read_proc(char *page, char **start, off_t off, spin_unlock_irqrestore(&card->lock, flags); - len += sprintf(page+len, "%-16s 0x%lx\n", - "csr (cached)", (unsigned long)card->csr); - len += sprintf(page+len, "%-16s 0x%lx\n", - "csr", (unsigned long)csr); - len += sprintf(page+len, "%-16s %lu\n", - "txoff", (unsigned long)txoff); - len += sprintf(page+len, "%-16s %lu\n", - "txlen", (unsigned long)txlen); - len += sprintf(page+len, "%-16s %lu\n", - "rxoff", (unsigned long)rxoff); - len += sprintf(page+len, "%-16s %lu\n", - "rxlen", (unsigned long)rxlen); - - if (off+count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len-off) ? count : len-off); + seq_printf(m, "%-16s 0x%lx\n", "csr (cached)", (unsigned long)card->csr); + seq_printf(m, "%-16s 0x%lx\n", "csr", (unsigned long)csr); + seq_printf(m, "%-16s %lu\n", "txoff", (unsigned long)txoff); + seq_printf(m, "%-16s %lu\n", "txlen", (unsigned long)txlen); + seq_printf(m, "%-16s %lu\n", "rxoff", (unsigned long)rxoff); + seq_printf(m, "%-16s %lu\n", "rxlen", (unsigned long)rxlen); + + return 0; +} + +static int b1dmactl_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, b1dmactl_proc_show, PDE(inode)->data); } +const struct file_operations b1dmactl_proc_fops = { + .owner = THIS_MODULE, + .open = b1dmactl_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; +EXPORT_SYMBOL(b1dmactl_proc_fops); + /* ------------------------------------------------------------- */ EXPORT_SYMBOL(b1dma_reset); @@ -963,7 +967,6 @@ EXPORT_SYMBOL(b1dma_reset_ctr); EXPORT_SYMBOL(b1dma_register_appl); EXPORT_SYMBOL(b1dma_release_appl); EXPORT_SYMBOL(b1dma_send_message); -EXPORT_SYMBOL(b1dmactl_read_proc); static int __init b1dma_init(void) { diff --git a/drivers/isdn/hardware/avm/b1isa.c b/drivers/isdn/hardware/avm/b1isa.c index 6461a32bc83..ff5390546f9 100644 --- a/drivers/isdn/hardware/avm/b1isa.c +++ b/drivers/isdn/hardware/avm/b1isa.c @@ -121,7 +121,7 @@ static int b1isa_probe(struct pci_dev *pdev) cinfo->capi_ctrl.load_firmware = b1_load_firmware; cinfo->capi_ctrl.reset_ctr = b1_reset_ctr; cinfo->capi_ctrl.procinfo = b1isa_procinfo; - cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + cinfo->capi_ctrl.proc_fops = &b1ctl_proc_fops; strcpy(cinfo->capi_ctrl.name, card->name); retval = attach_capi_ctr(&cinfo->capi_ctrl); diff --git a/drivers/isdn/hardware/avm/b1pci.c b/drivers/isdn/hardware/avm/b1pci.c index 5b314a2c404..c97e4315079 100644 --- a/drivers/isdn/hardware/avm/b1pci.c +++ b/drivers/isdn/hardware/avm/b1pci.c @@ -112,7 +112,7 @@ static int b1pci_probe(struct capicardparams *p, struct pci_dev *pdev) cinfo->capi_ctrl.load_firmware = b1_load_firmware; cinfo->capi_ctrl.reset_ctr = b1_reset_ctr; cinfo->capi_ctrl.procinfo = b1pci_procinfo; - cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + cinfo->capi_ctrl.proc_fops = &b1ctl_proc_fops; strcpy(cinfo->capi_ctrl.name, card->name); cinfo->capi_ctrl.owner = THIS_MODULE; @@ -251,7 +251,7 @@ static int b1pciv4_probe(struct capicardparams *p, struct pci_dev *pdev) cinfo->capi_ctrl.load_firmware = b1dma_load_firmware; cinfo->capi_ctrl.reset_ctr = b1dma_reset_ctr; cinfo->capi_ctrl.procinfo = b1pciv4_procinfo; - cinfo->capi_ctrl.ctr_read_proc = b1dmactl_read_proc; + cinfo->capi_ctrl.proc_fops = &b1dmactl_proc_fops; strcpy(cinfo->capi_ctrl.name, card->name); retval = attach_capi_ctr(&cinfo->capi_ctrl); diff --git a/drivers/isdn/hardware/avm/b1pcmcia.c b/drivers/isdn/hardware/avm/b1pcmcia.c index 7740403b40e..d6391e0afee 100644 --- a/drivers/isdn/hardware/avm/b1pcmcia.c +++ b/drivers/isdn/hardware/avm/b1pcmcia.c @@ -108,7 +108,7 @@ static int b1pcmcia_add_card(unsigned int port, unsigned irq, cinfo->capi_ctrl.load_firmware = b1_load_firmware; cinfo->capi_ctrl.reset_ctr = b1_reset_ctr; cinfo->capi_ctrl.procinfo = b1pcmcia_procinfo; - cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + cinfo->capi_ctrl.proc_fops = &b1ctl_proc_fops; strcpy(cinfo->capi_ctrl.name, card->name); retval = attach_capi_ctr(&cinfo->capi_ctrl); diff --git a/drivers/isdn/hardware/avm/c4.c b/drivers/isdn/hardware/avm/c4.c index 6833301a45f..de6e6b31181 100644 --- a/drivers/isdn/hardware/avm/c4.c +++ b/drivers/isdn/hardware/avm/c4.c @@ -11,6 +11,8 @@ #include #include +#include +#include #include #include #include @@ -1062,19 +1064,18 @@ static char *c4_procinfo(struct capi_ctr *ctrl) return cinfo->infobuf; } -static int c4_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctrl) +static int c4_proc_show(struct seq_file *m, void *v) { + struct capi_ctr *ctrl = m->private; avmctrl_info *cinfo = (avmctrl_info *)(ctrl->driverdata); avmcard *card = cinfo->card; u8 flag; - int len = 0; char *s; - len += sprintf(page+len, "%-16s %s\n", "name", card->name); - len += sprintf(page+len, "%-16s 0x%x\n", "io", card->port); - len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); - len += sprintf(page+len, "%-16s 0x%lx\n", "membase", card->membase); + seq_printf(m, "%-16s %s\n", "name", card->name); + seq_printf(m, "%-16s 0x%x\n", "io", card->port); + seq_printf(m, "%-16s %d\n", "irq", card->irq); + seq_printf(m, "%-16s 0x%lx\n", "membase", card->membase); switch (card->cardtype) { case avm_b1isa: s = "B1 ISA"; break; case avm_b1pci: s = "B1 PCI"; break; @@ -1087,18 +1088,18 @@ static int c4_read_proc(char *page, char **start, off_t off, case avm_c2: s = "C2"; break; default: s = "???"; break; } - len += sprintf(page+len, "%-16s %s\n", "type", s); + seq_printf(m, "%-16s %s\n", "type", s); if ((s = cinfo->version[VER_DRIVER]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + seq_printf(m, "%-16s %s\n", "ver_driver", s); if ((s = cinfo->version[VER_CARDTYPE]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); if ((s = cinfo->version[VER_SERIAL]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + seq_printf(m, "%-16s %s\n", "ver_serial", s); if (card->cardtype != avm_m1) { flag = ((u8 *)(ctrl->profile.manu))[3]; if (flag) - len += sprintf(page+len, "%-16s%s%s%s%s%s%s%s\n", + seq_printf(m, "%-16s%s%s%s%s%s%s%s\n", "protocol", (flag & 0x01) ? " DSS1" : "", (flag & 0x02) ? " CT1" : "", @@ -1112,7 +1113,7 @@ static int c4_read_proc(char *page, char **start, off_t off, if (card->cardtype != avm_m1) { flag = ((u8 *)(ctrl->profile.manu))[5]; if (flag) - len += sprintf(page+len, "%-16s%s%s%s%s\n", + seq_printf(m, "%-16s%s%s%s%s\n", "linetype", (flag & 0x01) ? " point to point" : "", (flag & 0x02) ? " point to multipoint" : "", @@ -1120,16 +1121,24 @@ static int c4_read_proc(char *page, char **start, off_t off, (flag & 0x04) ? " leased line with D-channel" : "" ); } - len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); - - if (off+count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len-off) ? count : len-off); + seq_printf(m, "%-16s %s\n", "cardname", cinfo->cardname); + + return 0; } +static int c4_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, c4_proc_show, PDE(inode)->data); +} + +static const struct file_operations c4_proc_fops = { + .owner = THIS_MODULE, + .open = c4_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + /* ------------------------------------------------------------- */ static int c4_add_card(struct capicardparams *p, struct pci_dev *dev, @@ -1201,7 +1210,7 @@ static int c4_add_card(struct capicardparams *p, struct pci_dev *dev, cinfo->capi_ctrl.load_firmware = c4_load_firmware; cinfo->capi_ctrl.reset_ctr = c4_reset_ctr; cinfo->capi_ctrl.procinfo = c4_procinfo; - cinfo->capi_ctrl.ctr_read_proc = c4_read_proc; + cinfo->capi_ctrl.proc_fops = &c4_proc_fops; strcpy(cinfo->capi_ctrl.name, card->name); retval = attach_capi_ctr(&cinfo->capi_ctrl); diff --git a/drivers/isdn/hardware/avm/t1isa.c b/drivers/isdn/hardware/avm/t1isa.c index 1c53fd49adb..baeeb3c2a3e 100644 --- a/drivers/isdn/hardware/avm/t1isa.c +++ b/drivers/isdn/hardware/avm/t1isa.c @@ -429,7 +429,7 @@ static int t1isa_probe(struct pci_dev *pdev, int cardnr) cinfo->capi_ctrl.load_firmware = t1isa_load_firmware; cinfo->capi_ctrl.reset_ctr = t1isa_reset_ctr; cinfo->capi_ctrl.procinfo = t1isa_procinfo; - cinfo->capi_ctrl.ctr_read_proc = b1ctl_read_proc; + cinfo->capi_ctrl.proc_fops = &b1ctl_proc_fops; strcpy(cinfo->capi_ctrl.name, card->name); retval = attach_capi_ctr(&cinfo->capi_ctrl); diff --git a/drivers/isdn/hardware/avm/t1pci.c b/drivers/isdn/hardware/avm/t1pci.c index e6d298d7514..5a3f8309801 100644 --- a/drivers/isdn/hardware/avm/t1pci.c +++ b/drivers/isdn/hardware/avm/t1pci.c @@ -119,7 +119,7 @@ static int t1pci_add_card(struct capicardparams *p, struct pci_dev *pdev) cinfo->capi_ctrl.load_firmware = b1dma_load_firmware; cinfo->capi_ctrl.reset_ctr = b1dma_reset_ctr; cinfo->capi_ctrl.procinfo = t1pci_procinfo; - cinfo->capi_ctrl.ctr_read_proc = b1dmactl_read_proc; + cinfo->capi_ctrl.proc_fops = &b1dmactl_proc_fops; strcpy(cinfo->capi_ctrl.name, card->name); retval = attach_capi_ctr(&cinfo->capi_ctrl); diff --git a/drivers/isdn/hardware/eicon/capimain.c b/drivers/isdn/hardware/eicon/capimain.c index 98fcdfc7ca5..0f073cd7376 100644 --- a/drivers/isdn/hardware/eicon/capimain.c +++ b/drivers/isdn/hardware/eicon/capimain.c @@ -13,6 +13,7 @@ #include #include #include +#include #include #include "os_capi.h" @@ -75,25 +76,32 @@ void diva_os_free_message_buffer(diva_os_message_buffer_s * dmb) /* * proc function for controller info */ -static int diva_ctl_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctrl) +static int diva_ctl_proc_show(struct seq_file *m, void *v) { + struct capi_ctr *ctrl = m->private; diva_card *card = (diva_card *) ctrl->driverdata; - int len = 0; - - len += sprintf(page + len, "%s\n", ctrl->name); - len += sprintf(page + len, "Serial No. : %s\n", ctrl->serial); - len += sprintf(page + len, "Id : %d\n", card->Id); - len += sprintf(page + len, "Channels : %d\n", card->d.channels); - - if (off + count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len - off) ? count : len - off); + + seq_printf(m, "%s\n", ctrl->name); + seq_printf(m, "Serial No. : %s\n", ctrl->serial); + seq_printf(m, "Id : %d\n", card->Id); + seq_printf(m, "Channels : %d\n", card->d.channels); + + return 0; +} + +static int diva_ctl_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, diva_ctl_proc_show, NULL); } +static const struct file_operations diva_ctl_proc_fops = { + .owner = THIS_MODULE, + .open = diva_ctl_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + /* * set additional os settings in capi_ctr struct */ @@ -102,7 +110,7 @@ void diva_os_set_controller_struct(struct capi_ctr *ctrl) ctrl->driver_name = DRIVERLNAME; ctrl->load_firmware = NULL; ctrl->reset_ctr = NULL; - ctrl->ctr_read_proc = diva_ctl_read_proc; + ctrl->proc_fops = &diva_ctl_proc_fops; ctrl->owner = THIS_MODULE; } diff --git a/drivers/isdn/hardware/eicon/diva_didd.c b/drivers/isdn/hardware/eicon/diva_didd.c index 993b14cf177..5d06a743782 100644 --- a/drivers/isdn/hardware/eicon/diva_didd.c +++ b/drivers/isdn/hardware/eicon/diva_didd.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include "platform.h" @@ -62,39 +63,41 @@ static char *getrev(const char *revision) return rev; } -static int -proc_read(char *page, char **start, off_t off, int count, int *eof, - void *data) +static int divadidd_proc_show(struct seq_file *m, void *v) { - int len = 0; char tmprev[32]; strcpy(tmprev, main_revision); - len += sprintf(page + len, "%s\n", DRIVERNAME); - len += sprintf(page + len, "name : %s\n", DRIVERLNAME); - len += sprintf(page + len, "release : %s\n", DRIVERRELEASE_DIDD); - len += sprintf(page + len, "build : %s(%s)\n", + seq_printf(m, "%s\n", DRIVERNAME); + seq_printf(m, "name : %s\n", DRIVERLNAME); + seq_printf(m, "release : %s\n", DRIVERRELEASE_DIDD); + seq_printf(m, "build : %s(%s)\n", diva_didd_common_code_build, DIVA_BUILD); - len += sprintf(page + len, "revision : %s\n", getrev(tmprev)); - - if (off + count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len - off) ? count : len - off); + seq_printf(m, "revision : %s\n", getrev(tmprev)); + + return 0; } +static int divadidd_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, divadidd_proc_show, NULL); +} + +static const struct file_operations divadidd_proc_fops = { + .owner = THIS_MODULE, + .open = divadidd_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + static int DIVA_INIT_FUNCTION create_proc(void) { proc_net_eicon = proc_mkdir("eicon", init_net.proc_net); if (proc_net_eicon) { - if ((proc_didd = - create_proc_entry(DRIVERLNAME, S_IFREG | S_IRUGO, - proc_net_eicon))) { - proc_didd->read_proc = proc_read; - } + proc_didd = proc_create(DRIVERLNAME, S_IRUGO, proc_net_eicon, + &divadidd_proc_fops); return (1); } return (0); diff --git a/drivers/isdn/hardware/eicon/divasi.c b/drivers/isdn/hardware/eicon/divasi.c index 69e71ebe784..f577719ab3f 100644 --- a/drivers/isdn/hardware/eicon/divasi.c +++ b/drivers/isdn/hardware/eicon/divasi.c @@ -17,6 +17,7 @@ #include #include #include +#include #include #include @@ -86,39 +87,40 @@ static void diva_um_timer_function(unsigned long data); extern struct proc_dir_entry *proc_net_eicon; static struct proc_dir_entry *um_idi_proc_entry = NULL; -static int -um_idi_proc_read(char *page, char **start, off_t off, int count, int *eof, - void *data) +static int um_idi_proc_show(struct seq_file *m, void *v) { - int len = 0; char tmprev[32]; - len += sprintf(page + len, "%s\n", DRIVERNAME); - len += sprintf(page + len, "name : %s\n", DRIVERLNAME); - len += sprintf(page + len, "release : %s\n", DRIVERRELEASE_IDI); + seq_printf(m, "%s\n", DRIVERNAME); + seq_printf(m, "name : %s\n", DRIVERLNAME); + seq_printf(m, "release : %s\n", DRIVERRELEASE_IDI); strcpy(tmprev, main_revision); - len += sprintf(page + len, "revision : %s\n", getrev(tmprev)); - len += sprintf(page + len, "build : %s\n", DIVA_BUILD); - len += sprintf(page + len, "major : %d\n", major); - - if (off + count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len - off) ? count : len - off); + seq_printf(m, "revision : %s\n", getrev(tmprev)); + seq_printf(m, "build : %s\n", DIVA_BUILD); + seq_printf(m, "major : %d\n", major); + + return 0; +} + +static int um_idi_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, um_idi_proc_show, NULL); } +static const struct file_operations um_idi_proc_fops = { + .owner = THIS_MODULE, + .open = um_idi_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + static int DIVA_INIT_FUNCTION create_um_idi_proc(void) { - um_idi_proc_entry = create_proc_entry(DRIVERLNAME, - S_IFREG | S_IRUGO | S_IWUSR, - proc_net_eicon); + um_idi_proc_entry = proc_create(DRIVERLNAME, S_IRUGO, proc_net_eicon, + &um_idi_proc_fops); if (!um_idi_proc_entry) return (0); - - um_idi_proc_entry->read_proc = um_idi_proc_read; - return (1); } diff --git a/drivers/isdn/hardware/eicon/divasproc.c b/drivers/isdn/hardware/eicon/divasproc.c index 040827288ec..46d44a94262 100644 --- a/drivers/isdn/hardware/eicon/divasproc.c +++ b/drivers/isdn/hardware/eicon/divasproc.c @@ -14,6 +14,7 @@ #include #include #include +#include #include #include @@ -141,14 +142,10 @@ void remove_divas_proc(void) } } -/* -** write group_optimization -*/ -static int -write_grp_opt(struct file *file, const char __user *buffer, unsigned long count, - void *data) +static ssize_t grp_opt_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) { - diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + diva_os_xdi_adapter_t *a = PDE(file->f_path.dentry->d_inode)->data; PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; if ((count == 1) || (count == 2)) { @@ -172,14 +169,10 @@ write_grp_opt(struct file *file, const char __user *buffer, unsigned long count, return (-EINVAL); } -/* -** write dynamic_l1_down -*/ -static int -write_d_l1_down(struct file *file, const char __user *buffer, unsigned long count, - void *data) +static ssize_t d_l1_down_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) { - diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + diva_os_xdi_adapter_t *a = PDE(file->f_path.dentry->d_inode)->data; PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; if ((count == 1) || (count == 2)) { @@ -203,63 +196,62 @@ write_d_l1_down(struct file *file, const char __user *buffer, unsigned long coun return (-EINVAL); } - -/* -** read dynamic_l1_down -*/ -static int -read_d_l1_down(char *page, char **start, off_t off, int count, int *eof, - void *data) +static int d_l1_down_proc_show(struct seq_file *m, void *v) { - int len = 0; - diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + diva_os_xdi_adapter_t *a = m->private; PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; - len += sprintf(page + len, "%s\n", + seq_printf(m, "%s\n", (IoAdapter->capi_cfg. cfg_1 & DIVA_XDI_CAPI_CFG_1_DYNAMIC_L1_ON) ? "1" : "0"); + return 0; +} - if (off + count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len - off) ? count : len - off); +static int d_l1_down_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, d_l1_down_proc_show, PDE(inode)->data); } -/* -** read group_optimization -*/ -static int -read_grp_opt(char *page, char **start, off_t off, int count, int *eof, - void *data) +static const struct file_operations d_l1_down_proc_fops = { + .owner = THIS_MODULE, + .open = d_l1_down_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = d_l1_down_proc_write, +}; + +static int grp_opt_proc_show(struct seq_file *m, void *v) { - int len = 0; - diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + diva_os_xdi_adapter_t *a = m->private; PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; - len += sprintf(page + len, "%s\n", + seq_printf(m, "%s\n", (IoAdapter->capi_cfg. cfg_1 & DIVA_XDI_CAPI_CFG_1_GROUP_POPTIMIZATION_ON) ? "1" : "0"); + return 0; +} - if (off + count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len - off) ? count : len - off); +static int grp_opt_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, grp_opt_proc_show, PDE(inode)->data); } -/* -** info write -*/ -static int -info_write(struct file *file, const char __user *buffer, unsigned long count, - void *data) +static const struct file_operations grp_opt_proc_fops = { + .owner = THIS_MODULE, + .open = grp_opt_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = grp_opt_proc_write, +}; + +static ssize_t info_proc_write(struct file *file, const char __user *buffer, + size_t count, loff_t *pos) { - diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + diva_os_xdi_adapter_t *a = PDE(file->f_path.dentry->d_inode)->data; PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; char c[4]; @@ -277,63 +269,46 @@ info_write(struct file *file, const char __user *buffer, unsigned long count, return (-EINVAL); } -/* -** info read -*/ -static int -info_read(char *page, char **start, off_t off, int count, int *eof, - void *data) +static int info_proc_show(struct seq_file *m, void *v) { int i = 0; - int len = 0; char *p; char tmpser[16]; - diva_os_xdi_adapter_t *a = (diva_os_xdi_adapter_t *) data; + diva_os_xdi_adapter_t *a = m->private; PISDN_ADAPTER IoAdapter = IoAdapters[a->controller - 1]; - len += - sprintf(page + len, "Name : %s\n", - IoAdapter->Properties.Name); - len += sprintf(page + len, "DSP state : %08x\n", a->dsp_mask); - len += sprintf(page + len, "Channels : %02d\n", - IoAdapter->Properties.Channels); - len += sprintf(page + len, "E. max/used : %03d/%03d\n", + seq_printf(m, "Name : %s\n", IoAdapter->Properties.Name); + seq_printf(m, "DSP state : %08x\n", a->dsp_mask); + seq_printf(m, "Channels : %02d\n", IoAdapter->Properties.Channels); + seq_printf(m, "E. max/used : %03d/%03d\n", IoAdapter->e_max, IoAdapter->e_count); diva_get_vserial_number(IoAdapter, tmpser); - len += sprintf(page + len, "Serial : %s\n", tmpser); - len += - sprintf(page + len, "IRQ : %d\n", - IoAdapter->irq_info.irq_nr); - len += sprintf(page + len, "CardIndex : %d\n", a->CardIndex); - len += sprintf(page + len, "CardOrdinal : %d\n", a->CardOrdinal); - len += sprintf(page + len, "Controller : %d\n", a->controller); - len += sprintf(page + len, "Bus-Type : %s\n", + seq_printf(m, "Serial : %s\n", tmpser); + seq_printf(m, "IRQ : %d\n", IoAdapter->irq_info.irq_nr); + seq_printf(m, "CardIndex : %d\n", a->CardIndex); + seq_printf(m, "CardOrdinal : %d\n", a->CardOrdinal); + seq_printf(m, "Controller : %d\n", a->controller); + seq_printf(m, "Bus-Type : %s\n", (a->Bus == DIVAS_XDI_ADAPTER_BUS_ISA) ? "ISA" : "PCI"); - len += sprintf(page + len, "Port-Name : %s\n", a->port_name); + seq_printf(m, "Port-Name : %s\n", a->port_name); if (a->Bus == DIVAS_XDI_ADAPTER_BUS_PCI) { - len += - sprintf(page + len, "PCI-bus : %d\n", - a->resources.pci.bus); - len += - sprintf(page + len, "PCI-func : %d\n", - a->resources.pci.func); + seq_printf(m, "PCI-bus : %d\n", a->resources.pci.bus); + seq_printf(m, "PCI-func : %d\n", a->resources.pci.func); for (i = 0; i < 8; i++) { if (a->resources.pci.bar[i]) { - len += - sprintf(page + len, + seq_printf(m, "Mem / I/O %d : 0x%x / mapped : 0x%lx", i, a->resources.pci.bar[i], (unsigned long) a->resources. pci.addr[i]); if (a->resources.pci.length[i]) { - len += - sprintf(page + len, + seq_printf(m, " / length : %d", a->resources.pci. length[i]); } - len += sprintf(page + len, "\n"); + seq_putc(m, '\n'); } } } @@ -353,16 +328,25 @@ info_read(char *page, char **start, off_t off, int count, int *eof, } else { p = "ready"; } - len += sprintf(page + len, "State : %s\n", p); + seq_printf(m, "State : %s\n", p); - if (off + count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len - off) ? count : len - off); + return 0; +} + +static int info_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, info_proc_show, PDE(inode)->data); } +static const struct file_operations info_proc_fops = { + .owner = THIS_MODULE, + .open = info_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = info_proc_write, +}; + /* ** adapter proc init/de-init */ @@ -380,28 +364,20 @@ int create_adapter_proc(diva_os_xdi_adapter_t * a) return (0); a->proc_adapter_dir = (void *) de; - if (!(pe = - create_proc_entry(info_proc_name, S_IFREG | S_IRUGO | S_IWUSR, de))) + pe = proc_create_data(info_proc_name, S_IRUGO | S_IWUSR, de, + &info_proc_fops, a); + if (!pe) return (0); a->proc_info = (void *) pe; - pe->write_proc = info_write; - pe->read_proc = info_read; - pe->data = a; - if ((pe = create_proc_entry(grp_opt_proc_name, - S_IFREG | S_IRUGO | S_IWUSR, de))) { + pe = proc_create_data(grp_opt_proc_name, S_IRUGO | S_IWUSR, de, + &grp_opt_proc_fops, a); + if (pe) a->proc_grp_opt = (void *) pe; - pe->write_proc = write_grp_opt; - pe->read_proc = read_grp_opt; - pe->data = a; - } - if ((pe = create_proc_entry(d_l1_down_proc_name, - S_IFREG | S_IRUGO | S_IWUSR, de))) { + pe = proc_create_data(d_l1_down_proc_name, S_IRUGO | S_IWUSR, de, + &d_l1_down_proc_fops, a); + if (pe) a->proc_d_l1_down = (void *) pe; - pe->write_proc = write_d_l1_down; - pe->read_proc = read_d_l1_down; - pe->data = a; - } DBG_TRC(("proc entry %s created", tmp)); diff --git a/drivers/isdn/hysdn/hycapi.c b/drivers/isdn/hysdn/hycapi.c index 4ffaa14b9fc..fe874afa4f8 100644 --- a/drivers/isdn/hysdn/hycapi.c +++ b/drivers/isdn/hysdn/hycapi.c @@ -11,6 +11,8 @@ */ #include +#include +#include #include #include #include @@ -432,26 +434,16 @@ static u16 hycapi_send_message(struct capi_ctr *ctrl, struct sk_buff *skb) return retval; } -/********************************************************************* -hycapi_read_proc - -Informations provided in the /proc/capi-entries. - -*********************************************************************/ - -static int hycapi_read_proc(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *ctrl) +static int hycapi_proc_show(struct seq_file *m, void *v) { + struct capi_ctr *ctrl = m->private; hycapictrl_info *cinfo = (hycapictrl_info *)(ctrl->driverdata); hysdn_card *card = cinfo->card; - int len = 0; char *s; -#ifdef HYCAPI_PRINTFNAMES - printk(KERN_NOTICE "hycapi_read_proc\n"); -#endif - len += sprintf(page+len, "%-16s %s\n", "name", cinfo->cardname); - len += sprintf(page+len, "%-16s 0x%x\n", "io", card->iobase); - len += sprintf(page+len, "%-16s %d\n", "irq", card->irq); + + seq_printf(m, "%-16s %s\n", "name", cinfo->cardname); + seq_printf(m, "%-16s 0x%x\n", "io", card->iobase); + seq_printf(m, "%-16s %d\n", "irq", card->irq); switch (card->brdtype) { case BD_PCCARD: s = "HYSDN Hycard"; break; @@ -461,24 +453,32 @@ static int hycapi_read_proc(char *page, char **start, off_t off, case BD_PLEXUS: s = "HYSDN Plexus30"; break; default: s = "???"; break; } - len += sprintf(page+len, "%-16s %s\n", "type", s); + seq_printf(m, "%-16s %s\n", "type", s); if ((s = cinfo->version[VER_DRIVER]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_driver", s); + seq_printf(m, "%-16s %s\n", "ver_driver", s); if ((s = cinfo->version[VER_CARDTYPE]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_cardtype", s); + seq_printf(m, "%-16s %s\n", "ver_cardtype", s); if ((s = cinfo->version[VER_SERIAL]) != NULL) - len += sprintf(page+len, "%-16s %s\n", "ver_serial", s); + seq_printf(m, "%-16s %s\n", "ver_serial", s); - len += sprintf(page+len, "%-16s %s\n", "cardname", cinfo->cardname); + seq_printf(m, "%-16s %s\n", "cardname", cinfo->cardname); - if (off+count >= len) - *eof = 1; - if (len < off) - return 0; - *start = page + off; - return ((count < len-off) ? count : len-off); + return 0; +} + +static int hycapi_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, hycapi_proc_show, PDE(inode)->data); } +static const struct file_operations hycapi_proc_fops = { + .owner = THIS_MODULE, + .open = hycapi_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + /************************************************************** hycapi_load_firmware @@ -774,7 +774,7 @@ hycapi_capi_create(hysdn_card *card) ctrl->load_firmware = hycapi_load_firmware; ctrl->reset_ctr = hycapi_reset_ctr; ctrl->procinfo = hycapi_procinfo; - ctrl->ctr_read_proc = hycapi_read_proc; + ctrl->proc_fops = &hycapi_proc_fops; strcpy(ctrl->name, cinfo->cardname); ctrl->owner = THIS_MODULE; diff --git a/include/linux/isdn/capilli.h b/include/linux/isdn/capilli.h index 7acb87a4487..d3e5e9da0c8 100644 --- a/include/linux/isdn/capilli.h +++ b/include/linux/isdn/capilli.h @@ -50,8 +50,7 @@ struct capi_ctr { u16 (*send_message)(struct capi_ctr *, struct sk_buff *skb); char *(*procinfo)(struct capi_ctr *); - int (*ctr_read_proc)(char *page, char **start, off_t off, - int count, int *eof, struct capi_ctr *card); + const struct file_operations *proc_fops; /* filled in before calling ready callback */ u8 manu[CAPI_MANUFACTURER_LEN]; /* CAPI_GET_MANUFACTURER */ diff --git a/net/bluetooth/cmtp/capi.c b/net/bluetooth/cmtp/capi.c index 97f8d68d574..3487cfe74ae 100644 --- a/net/bluetooth/cmtp/capi.c +++ b/net/bluetooth/cmtp/capi.c @@ -21,7 +21,8 @@ */ #include - +#include +#include #include #include #include @@ -516,33 +517,37 @@ static char *cmtp_procinfo(struct capi_ctr *ctrl) return "CAPI Message Transport Protocol"; } -static int cmtp_ctr_read_proc(char *page, char **start, off_t off, int count, int *eof, struct capi_ctr *ctrl) +static int cmtp_proc_show(struct seq_file *m, void *v) { + struct capi_ctr *ctrl = m->private; struct cmtp_session *session = ctrl->driverdata; struct cmtp_application *app; struct list_head *p, *n; - int len = 0; - len += sprintf(page + len, "%s\n\n", cmtp_procinfo(ctrl)); - len += sprintf(page + len, "addr %s\n", session->name); - len += sprintf(page + len, "ctrl %d\n", session->num); + seq_printf(m, "%s\n\n", cmtp_procinfo(ctrl)); + seq_printf(m, "addr %s\n", session->name); + seq_printf(m, "ctrl %d\n", session->num); list_for_each_safe(p, n, &session->applications) { app = list_entry(p, struct cmtp_application, list); - len += sprintf(page + len, "appl %d -> %d\n", app->appl, app->mapping); + seq_printf(m, "appl %d -> %d\n", app->appl, app->mapping); } - if (off + count >= len) - *eof = 1; - - if (len < off) - return 0; - - *start = page + off; + return 0; +} - return ((count < len - off) ? count : len - off); +static int cmtp_proc_open(struct inode *inode, struct file *file) +{ + return single_open(file, cmtp_proc_show, PDE(inode)->data); } +static const struct file_operations cmtp_proc_fops = { + .owner = THIS_MODULE, + .open = cmtp_proc_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; int cmtp_attach_device(struct cmtp_session *session) { @@ -582,7 +587,7 @@ int cmtp_attach_device(struct cmtp_session *session) session->ctrl.send_message = cmtp_send_message; session->ctrl.procinfo = cmtp_procinfo; - session->ctrl.ctr_read_proc = cmtp_ctr_read_proc; + session->ctrl.proc_fops = &cmtp_proc_fops; if (attach_capi_ctr(&session->ctrl) < 0) { BT_ERR("Can't attach new controller"); -- cgit v1.2.3-70-g09d2 From 9d173fc5dfa8c1b4578b331ac7ff3ce8af27006e Mon Sep 17 00:00:00 2001 From: Kalle Valo Date: Thu, 14 Jan 2010 13:09:14 +0200 Subject: mac80211: fix mac80211.h documentation warnings There were some warnings about missing documentation and a missing reference. Signed-off-by: Kalle Valo Signed-off-by: John W. Linville --- Documentation/DocBook/mac80211.tmpl | 2 +- include/net/mac80211.h | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/mac80211.tmpl b/Documentation/DocBook/mac80211.tmpl index f3f37f141db..971d1c0c83e 100644 --- a/Documentation/DocBook/mac80211.tmpl +++ b/Documentation/DocBook/mac80211.tmpl @@ -144,7 +144,7 @@ usage should require reading the full document. this though and the recommendation to allow only a single interface in STA mode at first! -!Finclude/net/mac80211.h ieee80211_if_init_conf +!Finclude/net/mac80211.h ieee80211_vif diff --git a/include/net/mac80211.h b/include/net/mac80211.h index f313a3cbabd..bbfa4750092 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -107,6 +107,7 @@ enum ieee80211_max_queues { * 2^n-1 in the range 1..32767] * @cw_max: maximum contention window [like @cw_min] * @txop: maximum burst time in units of 32 usecs, 0 meaning disabled + * @uapsd: is U-APSD mode enabled for the queue */ struct ieee80211_tx_queue_params { u16 txop; @@ -608,7 +609,11 @@ enum ieee80211_conf_changed { /** * enum ieee80211_smps_mode - spatial multiplexing power save mode * - * @ + * @IEEE80211_SMPS_AUTOMATIC: automatic + * @IEEE80211_SMPS_OFF: off + * @IEEE80211_SMPS_STATIC: static + * @IEEE80211_SMPS_DYNAMIC: dynamic + * @IEEE80211_SMPS_NUM_MODES: internal, don't use */ enum ieee80211_smps_mode { IEEE80211_SMPS_AUTOMATIC, -- cgit v1.2.3-70-g09d2 From 4c54005ca438a8b46dd542b497d4f0dc2ca375e8 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Thu, 14 Jan 2010 16:10:57 -0800 Subject: rcu: 1Q2010 update for RCU documentation Add expedited functions. Review documentation and update obsolete verbiage. Also fix the advice for the RCU CPU-stall kernel configuration parameter, and document RCU CPU-stall warnings. 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: <12635142581866-git-send-email-> Signed-off-by: Ingo Molnar --- Documentation/RCU/00-INDEX | 8 +- Documentation/RCU/RTFP.txt | 58 +++++++- Documentation/RCU/checklist.txt | 200 ++++++++++++++++----------- Documentation/RCU/rcu.txt | 48 +------ Documentation/RCU/stallwarn.txt | 58 ++++++++ Documentation/RCU/torture.txt | 12 ++ Documentation/RCU/whatisRCU.txt | 3 +- Documentation/filesystems/dentry-locking.txt | 3 +- lib/Kconfig.debug | 4 +- 9 files changed, 258 insertions(+), 136 deletions(-) create mode 100644 Documentation/RCU/stallwarn.txt (limited to 'Documentation') diff --git a/Documentation/RCU/00-INDEX b/Documentation/RCU/00-INDEX index 9bb62f7b89c..0a27ea9621f 100644 --- a/Documentation/RCU/00-INDEX +++ b/Documentation/RCU/00-INDEX @@ -8,14 +8,18 @@ listRCU.txt - Using RCU to Protect Read-Mostly Linked Lists NMI-RCU.txt - Using RCU to Protect Dynamic NMI Handlers +rcubarrier.txt + - RCU and Unloadable Modules +rculist_nulls.txt + - RCU list primitives for use with SLAB_DESTROY_BY_RCU rcuref.txt - Reference-count design for elements of lists/arrays protected by RCU rcu.txt - RCU Concepts -rcubarrier.txt - - Unloading modules that use RCU callbacks RTFP.txt - List of RCU papers (bibliography) going back to 1980. +stallwarn.txt + - RCU CPU stall warnings (CONFIG_RCU_CPU_STALL_DETECTOR) torture.txt - RCU Torture Test Operation (CONFIG_RCU_TORTURE_TEST) trace.txt diff --git a/Documentation/RCU/RTFP.txt b/Documentation/RCU/RTFP.txt index d2b85237c76..5051209e683 100644 --- a/Documentation/RCU/RTFP.txt +++ b/Documentation/RCU/RTFP.txt @@ -25,10 +25,10 @@ to be referencing the data structure. However, this mechanism was not optimized for modern computer systems, which is not surprising given that these overheads were not so expensive in the mid-80s. Nonetheless, passive serialization appears to be the first deferred-destruction -mechanism to be used in production. Furthermore, the relevant patent has -lapsed, so this approach may be used in non-GPL software, if desired. -(In contrast, use of RCU is permitted only in software licensed under -GPL. Sorry!!!) +mechanism to be used in production. Furthermore, the relevant patent +has lapsed, so this approach may be used in non-GPL software, if desired. +(In contrast, implementation of RCU is permitted only in software licensed +under either GPL or LGPL. Sorry!!!) In 1990, Pugh [Pugh90] noted that explicitly tracking which threads were reading a given data structure permitted deferred free to operate @@ -150,6 +150,18 @@ preemptible RCU [PaulEMcKenney2007PreemptibleRCU], and the three-part LWN "What is RCU?" series [PaulEMcKenney2007WhatIsRCUFundamentally, PaulEMcKenney2008WhatIsRCUUsage, and PaulEMcKenney2008WhatIsRCUAPI]. +2008 saw a journal paper on real-time RCU [DinakarGuniguntala2008IBMSysJ], +a history of how Linux changed RCU more than RCU changed Linux +[PaulEMcKenney2008RCUOSR], and a design overview of hierarchical RCU +[PaulEMcKenney2008HierarchicalRCU]. + +2009 introduced user-level RCU algorithms [PaulEMcKenney2009MaliciousURCU], +which Mathieu Desnoyers is now maintaining [MathieuDesnoyers2009URCU] +[MathieuDesnoyersPhD]. TINY_RCU [PaulEMcKenney2009BloatWatchRCU] made +its appearance, as did expedited RCU [PaulEMcKenney2009expeditedRCU]. +The problem of resizeable RCU-protected hash tables may now be on a path +to a solution [JoshTriplett2009RPHash]. + Bibtex Entries @article{Kung80 @@ -730,6 +742,11 @@ Revised: " } +# +# "What is RCU?" LWN series. +# +######################################################################## + @article{DinakarGuniguntala2008IBMSysJ ,author="D. Guniguntala and P. E. McKenney and J. Triplett and J. Walpole" ,title="The read-copy-update mechanism for supporting real-time applications on shared-memory multiprocessor systems with {Linux}" @@ -820,3 +837,36 @@ Revised: Uniprocessor assumptions allow simplified RCU implementation. " } + +@unpublished{PaulEMcKenney2009expeditedRCU +,Author="Paul E. McKenney" +,Title="[{PATCH} -tip 0/3] expedited 'big hammer' {RCU} grace periods" +,month="June" +,day="25" +,year="2009" +,note="Available: +\url{http://lkml.org/lkml/2009/6/25/306} +[Viewed August 16, 2009]" +,annotation=" + First posting of expedited RCU to be accepted into -tip. +" +} + +@unpublished{JoshTriplett2009RPHash +,Author="Josh Triplett" +,Title="Scalable concurrent hash tables via relativistic programming" +,month="September" +,year="2009" +,note="Linux Plumbers Conference presentation" +,annotation=" + RP fun with hash tables. +" +} + +@phdthesis{MathieuDesnoyersPhD +, title = "Low-impact Operating System Tracing" +, author = "Mathieu Desnoyers" +, school = "Ecole Polytechnique de Montr\'{e}al" +, month = "December" +, year = 2009 +} diff --git a/Documentation/RCU/checklist.txt b/Documentation/RCU/checklist.txt index 51525a30e8b..767cf06a427 100644 --- a/Documentation/RCU/checklist.txt +++ b/Documentation/RCU/checklist.txt @@ -8,13 +8,12 @@ would cause. This list is based on experiences reviewing such patches over a rather long period of time, but improvements are always welcome! 0. Is RCU being applied to a read-mostly situation? If the data - structure is updated more than about 10% of the time, then - you should strongly consider some other approach, unless - detailed performance measurements show that RCU is nonetheless - the right tool for the job. Yes, you might think of RCU - as simply cutting overhead off of the readers and imposing it - on the writers. That is exactly why normal uses of RCU will - do much more reading than updating. + structure is updated more than about 10% of the time, then you + should strongly consider some other approach, unless detailed + performance measurements show that RCU is nonetheless the right + tool for the job. Yes, RCU does reduce read-side overhead by + increasing write-side overhead, which is exactly why normal uses + of RCU will do much more reading than updating. Another exception is where performance is not an issue, and RCU provides a simpler implementation. An example of this situation @@ -35,13 +34,13 @@ over a rather long period of time, but improvements are always welcome! If you choose #b, be prepared to describe how you have handled memory barriers on weakly ordered machines (pretty much all of - them -- even x86 allows reads to be reordered), and be prepared - to explain why this added complexity is worthwhile. If you - choose #c, be prepared to explain how this single task does not - become a major bottleneck on big multiprocessor machines (for - example, if the task is updating information relating to itself - that other tasks can read, there by definition can be no - bottleneck). + them -- even x86 allows later loads to be reordered to precede + earlier stores), and be prepared to explain why this added + complexity is worthwhile. If you choose #c, be prepared to + explain how this single task does not become a major bottleneck on + big multiprocessor machines (for example, if the task is updating + information relating to itself that other tasks can read, there + by definition can be no bottleneck). 2. Do the RCU read-side critical sections make proper use of rcu_read_lock() and friends? These primitives are needed @@ -51,8 +50,10 @@ over a rather long period of time, but improvements are always welcome! actuarial risk of your kernel. As a rough rule of thumb, any dereference of an RCU-protected - pointer must be covered by rcu_read_lock() or rcu_read_lock_bh() - or by the appropriate update-side lock. + pointer must be covered by rcu_read_lock(), rcu_read_lock_bh(), + rcu_read_lock_sched(), or by the appropriate update-side lock. + Disabling of preemption can serve as rcu_read_lock_sched(), but + is less readable. 3. Does the update code tolerate concurrent accesses? @@ -62,25 +63,27 @@ over a rather long period of time, but improvements are always welcome! of ways to handle this concurrency, depending on the situation: a. Use the RCU variants of the list and hlist update - primitives to add, remove, and replace elements on an - RCU-protected list. Alternatively, use the RCU-protected - trees that have been added to the Linux kernel. + primitives to add, remove, and replace elements on + an RCU-protected list. Alternatively, use the other + RCU-protected data structures that have been added to + the Linux kernel. This is almost always the best approach. b. Proceed as in (a) above, but also maintain per-element locks (that are acquired by both readers and writers) that guard per-element state. Of course, fields that - the readers refrain from accessing can be guarded by the - update-side lock. + the readers refrain from accessing can be guarded by + some other lock acquired only by updaters, if desired. This works quite well, also. c. Make updates appear atomic to readers. For example, - pointer updates to properly aligned fields will appear - atomic, as will individual atomic primitives. Operations - performed under a lock and sequences of multiple atomic - primitives will -not- appear to be atomic. + pointer updates to properly aligned fields will + appear atomic, as will individual atomic primitives. + Sequences of perations performed under a lock will -not- + appear to be atomic to RCU readers, nor will sequences + of multiple atomic primitives. This can work, but is starting to get a bit tricky. @@ -98,9 +101,9 @@ over a rather long period of time, but improvements are always welcome! a new structure containing updated values. 4. Weakly ordered CPUs pose special challenges. Almost all CPUs - are weakly ordered -- even i386 CPUs allow reads to be reordered. - RCU code must take all of the following measures to prevent - memory-corruption problems: + are weakly ordered -- even x86 CPUs allow later loads to be + reordered to precede earlier stores. RCU code must take all of + the following measures to prevent memory-corruption problems: a. Readers must maintain proper ordering of their memory accesses. The rcu_dereference() primitive ensures that @@ -113,14 +116,21 @@ over a rather long period of time, but improvements are always welcome! The rcu_dereference() primitive is also an excellent documentation aid, letting the person reading the code know exactly which pointers are protected by RCU. - - The rcu_dereference() primitive is used by the various - "_rcu()" list-traversal primitives, such as the - list_for_each_entry_rcu(). Note that it is perfectly - legal (if redundant) for update-side code to use - rcu_dereference() and the "_rcu()" list-traversal - primitives. This is particularly useful in code - that is common to readers and updaters. + Please note that compilers can also reorder code, and + they are becoming increasingly aggressive about doing + just that. The rcu_dereference() primitive therefore + also prevents destructive compiler optimizations. + + The rcu_dereference() primitive is used by the + various "_rcu()" list-traversal primitives, such + as the list_for_each_entry_rcu(). Note that it is + perfectly legal (if redundant) for update-side code to + use rcu_dereference() and the "_rcu()" list-traversal + primitives. This is particularly useful in code that + is common to readers and updaters. However, neither + rcu_dereference() nor the "_rcu()" list-traversal + primitives can substitute for a good concurrency design + coordinating among multiple updaters. b. If the list macros are being used, the list_add_tail_rcu() and list_add_rcu() primitives must be used in order @@ -135,11 +145,14 @@ over a rather long period of time, but improvements are always welcome! readers. Similarly, if the hlist macros are being used, the hlist_del_rcu() primitive is required. - The list_replace_rcu() primitive may be used to - replace an old structure with a new one in an - RCU-protected list. + The list_replace_rcu() and hlist_replace_rcu() primitives + may be used to replace an old structure with a new one + in their respective types of RCU-protected lists. + + d. Rules similar to (4b) and (4c) apply to the "hlist_nulls" + type of RCU-protected linked lists. - d. Updates must ensure that initialization of a given + e. Updates must ensure that initialization of a given structure happens before pointers to that structure are publicized. Use the rcu_assign_pointer() primitive when publicizing a pointer to a structure that can @@ -151,16 +164,31 @@ over a rather long period of time, but improvements are always welcome! it cannot block. 6. Since synchronize_rcu() can block, it cannot be called from - any sort of irq context. Ditto for synchronize_sched() and - synchronize_srcu(). - -7. If the updater uses call_rcu(), then the corresponding readers - must use rcu_read_lock() and rcu_read_unlock(). If the updater - uses call_rcu_bh(), then the corresponding readers must use - rcu_read_lock_bh() and rcu_read_unlock_bh(). If the updater - uses call_rcu_sched(), then the corresponding readers must - disable preemption. Mixing things up will result in confusion - and broken kernels. + any sort of irq context. The same rule applies for + synchronize_rcu_bh(), synchronize_sched(), synchronize_srcu(), + synchronize_rcu_expedited(), synchronize_rcu_bh_expedited(), + synchronize_sched_expedite(), and synchronize_srcu_expedited(). + + The expedited forms of these primitives have the same semantics + as the non-expedited forms, but expediting is both expensive + and unfriendly to real-time workloads. Use of the expedited + primitives should be restricted to rare configuration-change + operations that would not normally be undertaken while a real-time + workload is running. + +7. If the updater uses call_rcu() or synchronize_rcu(), then the + corresponding readers must use rcu_read_lock() and + rcu_read_unlock(). If the updater uses call_rcu_bh() or + synchronize_rcu_bh(), then the corresponding readers must + use rcu_read_lock_bh() and rcu_read_unlock_bh(). If the + updater uses call_rcu_sched() or synchronize_sched(), then + the corresponding readers must disable preemption, possibly + by calling rcu_read_lock_sched() and rcu_read_unlock_sched(). + If the updater uses synchronize_srcu(), the the corresponding + readers must use srcu_read_lock() and srcu_read_unlock(), + and with the same srcu_struct. The rules for the expedited + primitives are the same as for their non-expedited counterparts. + Mixing things up will result in confusion and broken kernels. One exception to this rule: rcu_read_lock() and rcu_read_unlock() may be substituted for rcu_read_lock_bh() and rcu_read_unlock_bh() @@ -212,6 +240,8 @@ over a rather long period of time, but improvements are always welcome! e. Periodically invoke synchronize_rcu(), permitting a limited number of updates per grace period. + The same cautions apply to call_rcu_bh() and call_rcu_sched(). + 9. All RCU list-traversal primitives, which include rcu_dereference(), list_for_each_entry_rcu(), list_for_each_continue_rcu(), and list_for_each_safe_rcu(), @@ -229,7 +259,8 @@ over a rather long period of time, but improvements are always welcome! 10. Conversely, if you are in an RCU read-side critical section, and you don't hold the appropriate update-side lock, you -must- use the "_rcu()" variants of the list macros. Failing to do so - will break Alpha and confuse people reading your code. + will break Alpha, cause aggressive compilers to generate bad code, + and confuse people trying to read your code. 11. Note that synchronize_rcu() -only- guarantees to wait until all currently executing rcu_read_lock()-protected RCU read-side @@ -239,15 +270,21 @@ over a rather long period of time, but improvements are always welcome! rcu_read_lock()-protected read-side critical sections, do -not- use synchronize_rcu(). - If you want to wait for some of these other things, you might - instead need to use synchronize_irq() or synchronize_sched(). + Similarly, disabling preemption is not an acceptable substitute + for rcu_read_lock(). Code that attempts to use preemption + disabling where it should be using rcu_read_lock() will break + in real-time kernel builds. + + If you want to wait for interrupt handlers, NMI handlers, and + code under the influence of preempt_disable(), you instead + need to use synchronize_irq() or synchronize_sched(). 12. Any lock acquired by an RCU callback must be acquired elsewhere with softirq disabled, e.g., via spin_lock_irqsave(), spin_lock_bh(), etc. Failing to disable irq on a given - acquisition of that lock will result in deadlock as soon as the - RCU callback happens to interrupt that acquisition's critical - section. + acquisition of that lock will result in deadlock as soon as + the RCU softirq handler happens to run your RCU callback while + interrupting that acquisition's critical section. 13. RCU callbacks can be and are executed in parallel. In many cases, the callback code simply wrappers around kfree(), so that this @@ -265,29 +302,30 @@ over a rather long period of time, but improvements are always welcome! not the case, a self-spawning RCU callback would prevent the victim CPU from ever going offline.) -14. SRCU (srcu_read_lock(), srcu_read_unlock(), and synchronize_srcu()) - may only be invoked from process context. Unlike other forms of - RCU, it -is- permissible to block in an SRCU read-side critical - section (demarked by srcu_read_lock() and srcu_read_unlock()), - hence the "SRCU": "sleepable RCU". Please note that if you - don't need to sleep in read-side critical sections, you should - be using RCU rather than SRCU, because RCU is almost always - faster and easier to use than is SRCU. +14. SRCU (srcu_read_lock(), srcu_read_unlock(), synchronize_srcu(), + and synchronize_srcu_expedited()) may only be invoked from + process context. Unlike other forms of RCU, it -is- permissible + to block in an SRCU read-side critical section (demarked by + srcu_read_lock() and srcu_read_unlock()), hence the "SRCU": + "sleepable RCU". Please note that if you don't need to sleep + in read-side critical sections, you should be using RCU rather + than SRCU, because RCU is almost always faster and easier to + use than is SRCU. Also unlike other forms of RCU, explicit initialization and cleanup is required via init_srcu_struct() and cleanup_srcu_struct(). These are passed a "struct srcu_struct" that defines the scope of a given SRCU domain. Once initialized, the srcu_struct is passed to srcu_read_lock(), srcu_read_unlock() - and synchronize_srcu(). A given synchronize_srcu() waits only - for SRCU read-side critical sections governed by srcu_read_lock() - and srcu_read_unlock() calls that have been passd the same - srcu_struct. This property is what makes sleeping read-side - critical sections tolerable -- a given subsystem delays only - its own updates, not those of other subsystems using SRCU. - Therefore, SRCU is less prone to OOM the system than RCU would - be if RCU's read-side critical sections were permitted to - sleep. + synchronize_srcu(), and synchronize_srcu_expedited(). A given + synchronize_srcu() waits only for SRCU read-side critical + sections governed by srcu_read_lock() and srcu_read_unlock() + calls that have been passed the same srcu_struct. This property + is what makes sleeping read-side critical sections tolerable -- + a given subsystem delays only its own updates, not those of other + subsystems using SRCU. Therefore, SRCU is less prone to OOM the + system than RCU would be if RCU's read-side critical sections + were permitted to sleep. The ability to sleep in read-side critical sections does not come for free. First, corresponding srcu_read_lock() and @@ -311,12 +349,12 @@ over a rather long period of time, but improvements are always welcome! destructive operation, and -only- -then- invoke call_rcu(), synchronize_rcu(), or friends. - Because these primitives only wait for pre-existing readers, - it is the caller's responsibility to guarantee safety to - any subsequent readers. + Because these primitives only wait for pre-existing readers, it + is the caller's responsibility to guarantee that any subsequent + readers will execute safely. -16. The various RCU read-side primitives do -not- contain memory - barriers. The CPU (and in some cases, the compiler) is free - to reorder code into and out of RCU read-side critical sections. - It is the responsibility of the RCU update-side primitives to - deal with this. +16. The various RCU read-side primitives do -not- necessarily contain + memory barriers. You should therefore plan for the CPU + and the compiler to freely reorder code into and out of RCU + read-side critical sections. It is the responsibility of the + RCU update-side primitives to deal with this. diff --git a/Documentation/RCU/rcu.txt b/Documentation/RCU/rcu.txt index 2a23523ce47..31852705b58 100644 --- a/Documentation/RCU/rcu.txt +++ b/Documentation/RCU/rcu.txt @@ -75,6 +75,8 @@ o I hear that RCU is patented? What is with that? search for the string "Patent" in RTFP.txt to find them. Of these, one was allowed to lapse by the assignee, and the others have been contributed to the Linux kernel under GPL. + There are now also LGPL implementations of user-level RCU + available (http://lttng.org/?q=node/18). o I hear that RCU needs work in order to support realtime kernels? @@ -91,48 +93,4 @@ o Where can I find more information on RCU? o What are all these files in this directory? - - NMI-RCU.txt - - Describes how to use RCU to implement dynamic - NMI handlers, which can be revectored on the fly, - without rebooting. - - RTFP.txt - - List of RCU-related publications and web sites. - - UP.txt - - Discussion of RCU usage in UP kernels. - - arrayRCU.txt - - Describes how to use RCU to protect arrays, with - resizeable arrays whose elements reference other - data structures being of the most interest. - - checklist.txt - - Lists things to check for when inspecting code that - uses RCU. - - listRCU.txt - - Describes how to use RCU to protect linked lists. - This is the simplest and most common use of RCU - in the Linux kernel. - - rcu.txt - - You are reading it! - - rcuref.txt - - Describes how to combine use of reference counts - with RCU. - - whatisRCU.txt - - Overview of how the RCU implementation works. Along - the way, presents a conceptual view of RCU. + See 00-INDEX for the list. diff --git a/Documentation/RCU/stallwarn.txt b/Documentation/RCU/stallwarn.txt new file mode 100644 index 00000000000..1423d2570d7 --- /dev/null +++ b/Documentation/RCU/stallwarn.txt @@ -0,0 +1,58 @@ +Using RCU's CPU Stall Detector + +The CONFIG_RCU_CPU_STALL_DETECTOR kernel config parameter enables +RCU's CPU stall detector, which detects conditions that unduly delay +RCU grace periods. The stall detector's idea of what constitutes +"unduly delayed" is controlled by a pair of C preprocessor macros: + +RCU_SECONDS_TILL_STALL_CHECK + + This macro defines the period of time that RCU will wait from + the beginning of a grace period until it issues an RCU CPU + stall warning. It is normally ten seconds. + +RCU_SECONDS_TILL_STALL_RECHECK + + This macro defines the period of time that RCU will wait after + issuing a stall warning until it issues another stall warning. + It is normally set to thirty seconds. + +RCU_STALL_RAT_DELAY + + The CPU stall detector tries to make the offending CPU rat on itself, + as this often gives better-quality stack traces. However, if + the offending CPU does not detect its own stall in the number + of jiffies specified by RCU_STALL_RAT_DELAY, then other CPUs will + complain. This is normally set to two jiffies. + +The following problems can result in an RCU CPU stall warning: + +o A CPU looping in an RCU read-side critical section. + +o A CPU looping with interrupts disabled. + +o A CPU looping with preemption disabled. + +o For !CONFIG_PREEMPT kernels, a CPU looping anywhere in the kernel + without invoking schedule(). + +o A bug in the RCU implementation. + +o A hardware failure. This is quite unlikely, but has occurred + at least once in a former life. A CPU failed in a running system, + becoming unresponsive, but not causing an immediate crash. + This resulted in a series of RCU CPU stall warnings, eventually + leading the realization that the CPU had failed. + +The RCU, RCU-sched, and RCU-bh implementations have CPU stall warning. +SRCU does not do so directly, but its calls to synchronize_sched() will +result in RCU-sched detecting any CPU stalls that might be occurring. + +To diagnose the cause of the stall, inspect the stack traces. The offending +function will usually be near the top of the stack. If you have a series +of stall warnings from a single extended stall, comparing the stack traces +can often help determine where the stall is occurring, which will usually +be in the function nearest the top of the stack that stays the same from +trace to trace. + +RCU bugs can often be debugged with the help of CONFIG_RCU_TRACE. diff --git a/Documentation/RCU/torture.txt b/Documentation/RCU/torture.txt index 9dba3bb90e6..0e50bc2aa1e 100644 --- a/Documentation/RCU/torture.txt +++ b/Documentation/RCU/torture.txt @@ -30,6 +30,18 @@ MODULE PARAMETERS This module has the following parameters: +fqs_duration Duration (in microseconds) of artificially induced bursts + of force_quiescent_state() invocations. In RCU + implementations having force_quiescent_state(), these + bursts help force races between forcing a given grace + period and that grace period ending on its own. + +fqs_holdoff Holdoff time (in microseconds) between consecutive calls + to force_quiescent_state() within a burst. + +fqs_stutter Wait time (in seconds) between consecutive bursts + of calls to force_quiescent_state(). + irqreaders Says to invoke RCU readers from irq level. This is currently done via timers. Defaults to "1" for variants of RCU that permit this. (Or, more accurately, variants of RCU that do diff --git a/Documentation/RCU/whatisRCU.txt b/Documentation/RCU/whatisRCU.txt index d542ca243b8..469a58b2e67 100644 --- a/Documentation/RCU/whatisRCU.txt +++ b/Documentation/RCU/whatisRCU.txt @@ -327,7 +327,8 @@ a. synchronize_rcu() rcu_read_lock() / rcu_read_unlock() b. call_rcu_bh() rcu_read_lock_bh() / rcu_read_unlock_bh() -c. synchronize_sched() preempt_disable() / preempt_enable() +c. synchronize_sched() rcu_read_lock_sched() / rcu_read_unlock_sched() + preempt_disable() / preempt_enable() local_irq_save() / local_irq_restore() hardirq enter / hardirq exit NMI enter / NMI exit diff --git a/Documentation/filesystems/dentry-locking.txt b/Documentation/filesystems/dentry-locking.txt index 4c0c575a401..79334ed5daa 100644 --- a/Documentation/filesystems/dentry-locking.txt +++ b/Documentation/filesystems/dentry-locking.txt @@ -62,7 +62,8 @@ changes are : 2. Insertion of a dentry into the hash table is done using hlist_add_head_rcu() which take care of ordering the writes - the writes to the dentry must be visible before the dentry is - inserted. This works in conjunction with hlist_for_each_rcu() while + inserted. This works in conjunction with hlist_for_each_rcu(), + which has since been replaced by hlist_for_each_entry_rcu(), while walking the hash chain. The only requirement is that all initialization to the dentry must be done before hlist_add_head_rcu() since we don't have dcache_lock protection diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index 25c3ed594c5..6bf97d17632 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -765,9 +765,9 @@ config RCU_CPU_STALL_DETECTOR CPUs are delaying the current grace period, but only when the grace period extends for excessive time periods. - Say Y if you want RCU to perform such checks. + Say N if you want to disable such checks. - Say N if you are unsure. + Say Y if you are unsure. config KPROBES_SANITY_TEST bool "Kprobes sanity tests" -- cgit v1.2.3-70-g09d2 From d1db38c015a392b0ea8c15ab95abb3ee768b8d47 Mon Sep 17 00:00:00 2001 From: Clemens Ladisch Date: Mon, 18 Jan 2010 15:44:04 +0100 Subject: sound: virtuoso: add Xonar DS support Add experimental support for the Asus Xonar DS. Signed-off-by: Clemens Ladisch Signed-off-by: Jaroslav Kysela --- Documentation/sound/alsa/ALSA-Configuration.txt | 2 +- sound/pci/Kconfig | 1 + sound/pci/oxygen/Makefile | 2 +- sound/pci/oxygen/virtuoso.c | 3 + sound/pci/oxygen/wm8766.h | 73 ++ sound/pci/oxygen/wm8776.h | 177 ++++ sound/pci/oxygen/xonar.h | 2 + sound/pci/oxygen/xonar_wm87x6.c | 1021 +++++++++++++++++++++++ 8 files changed, 1279 insertions(+), 2 deletions(-) create mode 100644 sound/pci/oxygen/wm8766.h create mode 100644 sound/pci/oxygen/wm8776.h create mode 100644 sound/pci/oxygen/xonar_wm87x6.c (limited to 'Documentation') diff --git a/Documentation/sound/alsa/ALSA-Configuration.txt b/Documentation/sound/alsa/ALSA-Configuration.txt index 8923597bd2b..3579e829699 100644 --- a/Documentation/sound/alsa/ALSA-Configuration.txt +++ b/Documentation/sound/alsa/ALSA-Configuration.txt @@ -1923,7 +1923,7 @@ Prior to version 0.9.0rc4 options had a 'snd_' prefix. This was removed. ------------------- Module for sound cards based on the Asus AV100/AV200 chips, - i.e., Xonar D1, DX, D2, D2X, HDAV1.3 (Deluxe), Essence ST + i.e., Xonar D1, DX, D2, D2X, DS, HDAV1.3 (Deluxe), Essence ST (Deluxe) and Essence STX. This module supports autoprobe and multiple cards. diff --git a/sound/pci/Kconfig b/sound/pci/Kconfig index 351654cf7b0..1298c68d6bf 100644 --- a/sound/pci/Kconfig +++ b/sound/pci/Kconfig @@ -789,6 +789,7 @@ config SND_VIRTUOSO Say Y here to include support for sound cards based on the Asus AV100/AV200 chips, i.e., Xonar D1, DX, D2, D2X, Essence ST (Deluxe), and Essence STX. + Support for the DS is experimental. Support for the HDAV1.3 (Deluxe) is very experimental. To compile this driver as a module, choose M here: the module diff --git a/sound/pci/oxygen/Makefile b/sound/pci/oxygen/Makefile index 389941cf610..acd8f15f7bf 100644 --- a/sound/pci/oxygen/Makefile +++ b/sound/pci/oxygen/Makefile @@ -2,7 +2,7 @@ snd-oxygen-lib-objs := oxygen_io.o oxygen_lib.o oxygen_mixer.o oxygen_pcm.o snd-hifier-objs := hifier.o snd-oxygen-objs := oxygen.o snd-virtuoso-objs := virtuoso.o xonar_lib.o \ - xonar_pcm179x.o xonar_cs43xx.o xonar_hdmi.o + xonar_pcm179x.o xonar_cs43xx.o xonar_wm87x6.o xonar_hdmi.o obj-$(CONFIG_SND_OXYGEN_LIB) += snd-oxygen-lib.o obj-$(CONFIG_SND_HIFIER) += snd-hifier.o diff --git a/sound/pci/oxygen/virtuoso.c b/sound/pci/oxygen/virtuoso.c index 6accaf9580b..563b6f50821 100644 --- a/sound/pci/oxygen/virtuoso.c +++ b/sound/pci/oxygen/virtuoso.c @@ -49,6 +49,7 @@ static struct pci_device_id xonar_ids[] __devinitdata = { { OXYGEN_PCI_SUBID(0x1043, 0x834f) }, { OXYGEN_PCI_SUBID(0x1043, 0x835c) }, { OXYGEN_PCI_SUBID(0x1043, 0x835d) }, + { OXYGEN_PCI_SUBID(0x1043, 0x838e) }, { OXYGEN_PCI_SUBID_BROKEN_EEPROM }, { } }; @@ -61,6 +62,8 @@ static int __devinit get_xonar_model(struct oxygen *chip, return 0; if (get_xonar_cs43xx_model(chip, id) >= 0) return 0; + if (get_xonar_wm87x6_model(chip, id) >= 0) + return 0; return -EINVAL; } diff --git a/sound/pci/oxygen/wm8766.h b/sound/pci/oxygen/wm8766.h new file mode 100644 index 00000000000..e0e849a7eae --- /dev/null +++ b/sound/pci/oxygen/wm8766.h @@ -0,0 +1,73 @@ +#ifndef WM8766_H_INCLUDED +#define WM8766_H_INCLUDED + +#define WM8766_LDA1 0x00 +#define WM8766_RDA1 0x01 +#define WM8766_DAC_CTRL 0x02 +#define WM8766_INT_CTRL 0x03 +#define WM8766_LDA2 0x04 +#define WM8766_RDA2 0x05 +#define WM8766_LDA3 0x06 +#define WM8766_RDA3 0x07 +#define WM8766_MASTDA 0x08 +#define WM8766_DAC_CTRL2 0x09 +#define WM8766_DAC_CTRL3 0x0a +#define WM8766_MUTE1 0x0c +#define WM8766_MUTE2 0x0f +#define WM8766_RESET 0x1f + +/* LDAx/RDAx/MASTDA */ +#define WM8766_ATT_MASK 0x0ff +#define WM8766_UPDATE 0x100 +/* DAC_CTRL */ +#define WM8766_MUTEALL 0x001 +#define WM8766_DEEMPALL 0x002 +#define WM8766_PWDN 0x004 +#define WM8766_ATC 0x008 +#define WM8766_IZD 0x010 +#define WM8766_PL_LEFT_MASK 0x060 +#define WM8766_PL_LEFT_MUTE 0x000 +#define WM8766_PL_LEFT_LEFT 0x020 +#define WM8766_PL_LEFT_RIGHT 0x040 +#define WM8766_PL_LEFT_LRMIX 0x060 +#define WM8766_PL_RIGHT_MASK 0x180 +#define WM8766_PL_RIGHT_MUTE 0x000 +#define WM8766_PL_RIGHT_LEFT 0x080 +#define WM8766_PL_RIGHT_RIGHT 0x100 +#define WM8766_PL_RIGHT_LRMIX 0x180 +/* INT_CTRL */ +#define WM8766_FMT_MASK 0x003 +#define WM8766_FMT_RJUST 0x000 +#define WM8766_FMT_LJUST 0x001 +#define WM8766_FMT_I2S 0x002 +#define WM8766_FMT_DSP 0x003 +#define WM8766_LRP 0x004 +#define WM8766_BCP 0x008 +#define WM8766_IWL_MASK 0x030 +#define WM8766_IWL_16 0x000 +#define WM8766_IWL_20 0x010 +#define WM8766_IWL_24 0x020 +#define WM8766_IWL_32 0x030 +#define WM8766_PHASE_MASK 0x1c0 +/* DAC_CTRL2 */ +#define WM8766_ZCD 0x001 +#define WM8766_DZFM_MASK 0x006 +#define WM8766_DMUTE_MASK 0x038 +#define WM8766_DEEMP_MASK 0x1c0 +/* DAC_CTRL3 */ +#define WM8766_DACPD_MASK 0x00e +#define WM8766_PWRDNALL 0x010 +#define WM8766_MS 0x020 +#define WM8766_RATE_MASK 0x1c0 +#define WM8766_RATE_128 0x000 +#define WM8766_RATE_192 0x040 +#define WM8766_RATE_256 0x080 +#define WM8766_RATE_384 0x0c0 +#define WM8766_RATE_512 0x100 +#define WM8766_RATE_768 0x140 +/* MUTE1 */ +#define WM8766_MPD1 0x040 +/* MUTE2 */ +#define WM8766_MPD2 0x020 + +#endif diff --git a/sound/pci/oxygen/wm8776.h b/sound/pci/oxygen/wm8776.h new file mode 100644 index 00000000000..1a96f561572 --- /dev/null +++ b/sound/pci/oxygen/wm8776.h @@ -0,0 +1,177 @@ +#ifndef WM8776_H_INCLUDED +#define WM8776_H_INCLUDED + +/* + * the following register names are from: + * wm8776.h -- WM8776 ASoC driver + * + * Copyright 2009 Wolfson Microelectronics plc + * + * Author: Mark Brown + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#define WM8776_HPLVOL 0x00 +#define WM8776_HPRVOL 0x01 +#define WM8776_HPMASTER 0x02 +#define WM8776_DACLVOL 0x03 +#define WM8776_DACRVOL 0x04 +#define WM8776_DACMASTER 0x05 +#define WM8776_PHASESWAP 0x06 +#define WM8776_DACCTRL1 0x07 +#define WM8776_DACMUTE 0x08 +#define WM8776_DACCTRL2 0x09 +#define WM8776_DACIFCTRL 0x0a +#define WM8776_ADCIFCTRL 0x0b +#define WM8776_MSTRCTRL 0x0c +#define WM8776_PWRDOWN 0x0d +#define WM8776_ADCLVOL 0x0e +#define WM8776_ADCRVOL 0x0f +#define WM8776_ALCCTRL1 0x10 +#define WM8776_ALCCTRL2 0x11 +#define WM8776_ALCCTRL3 0x12 +#define WM8776_NOISEGATE 0x13 +#define WM8776_LIMITER 0x14 +#define WM8776_ADCMUX 0x15 +#define WM8776_OUTMUX 0x16 +#define WM8776_RESET 0x17 + + +/* HPLVOL/HPRVOL/HPMASTER */ +#define WM8776_HPATT_MASK 0x07f +#define WM8776_HPZCEN 0x080 +#define WM8776_UPDATE 0x100 + +/* DACLVOL/DACRVOL/DACMASTER */ +#define WM8776_DATT_MASK 0x0ff +/*#define WM8776_UPDATE 0x100*/ + +/* PHASESWAP */ +#define WM8776_PH_MASK 0x003 + +/* DACCTRL1 */ +#define WM8776_DZCEN 0x001 +#define WM8776_ATC 0x002 +#define WM8776_IZD 0x004 +#define WM8776_TOD 0x008 +#define WM8776_PL_LEFT_MASK 0x030 +#define WM8776_PL_LEFT_MUTE 0x000 +#define WM8776_PL_LEFT_LEFT 0x010 +#define WM8776_PL_LEFT_RIGHT 0x020 +#define WM8776_PL_LEFT_LRMIX 0x030 +#define WM8776_PL_RIGHT_MASK 0x0c0 +#define WM8776_PL_RIGHT_MUTE 0x000 +#define WM8776_PL_RIGHT_LEFT 0x040 +#define WM8776_PL_RIGHT_RIGHT 0x080 +#define WM8776_PL_RIGHT_LRMIX 0x0c0 + +/* DACMUTE */ +#define WM8776_DMUTE 0x001 + +/* DACCTRL2 */ +#define WM8776_DEEMPH 0x001 +#define WM8776_DZFM_MASK 0x006 +#define WM8776_DZFM_NONE 0x000 +#define WM8776_DZFM_LR 0x002 +#define WM8776_DZFM_BOTH 0x004 +#define WM8776_DZFM_EITHER 0x006 + +/* DACIFCTRL */ +#define WM8776_DACFMT_MASK 0x003 +#define WM8776_DACFMT_RJUST 0x000 +#define WM8776_DACFMT_LJUST 0x001 +#define WM8776_DACFMT_I2S 0x002 +#define WM8776_DACFMT_DSP 0x003 +#define WM8776_DACLRP 0x004 +#define WM8776_DACBCP 0x008 +#define WM8776_DACWL_MASK 0x030 +#define WM8776_DACWL_16 0x000 +#define WM8776_DACWL_20 0x010 +#define WM8776_DACWL_24 0x020 +#define WM8776_DACWL_32 0x030 + +/* ADCIFCTRL */ +#define WM8776_ADCFMT_MASK 0x003 +#define WM8776_ADCFMT_RJUST 0x000 +#define WM8776_ADCFMT_LJUST 0x001 +#define WM8776_ADCFMT_I2S 0x002 +#define WM8776_ADCFMT_DSP 0x003 +#define WM8776_ADCLRP 0x004 +#define WM8776_ADCBCP 0x008 +#define WM8776_ADCWL_MASK 0x030 +#define WM8776_ADCWL_16 0x000 +#define WM8776_ADCWL_20 0x010 +#define WM8776_ADCWL_24 0x020 +#define WM8776_ADCWL_32 0x030 +#define WM8776_ADCMCLK 0x040 +#define WM8776_ADCHPD 0x100 + +/* MSTRCTRL */ +#define WM8776_ADCRATE_MASK 0x007 +#define WM8776_ADCRATE_256 0x002 +#define WM8776_ADCRATE_384 0x003 +#define WM8776_ADCRATE_512 0x004 +#define WM8776_ADCRATE_768 0x005 +#define WM8776_ADCOSR 0x008 +#define WM8776_DACRATE_MASK 0x070 +#define WM8776_DACRATE_128 0x000 +#define WM8776_DACRATE_192 0x010 +#define WM8776_DACRATE_256 0x020 +#define WM8776_DACRATE_384 0x030 +#define WM8776_DACRATE_512 0x040 +#define WM8776_DACRATE_768 0x050 +#define WM8776_DACMS 0x080 +#define WM8776_ADCMS 0x100 + +/* PWRDOWN */ +#define WM8776_PDWN 0x001 +#define WM8776_ADCPD 0x002 +#define WM8776_DACPD 0x004 +#define WM8776_HPPD 0x008 +#define WM8776_AINPD 0x040 + +/* ADCLVOL/ADCRVOL */ +#define WM8776_AGMASK 0x0ff +#define WM8776_ZCA 0x100 + +/* ALCCTRL1 */ +#define WM8776_LCT_MASK 0x00f +#define WM8776_MAXGAIN_MASK 0x070 +#define WM8776_LCSEL_MASK 0x180 +#define WM8776_LCSEL_LIMITER 0x000 +#define WM8776_LCSEL_ALC_RIGHT 0x080 +#define WM8776_LCSEL_ALC_LEFT 0x100 +#define WM8776_LCSEL_ALC_STEREO 0x180 + +/* ALCCTRL2 */ +#define WM8776_HLD_MASK 0x00f +#define WM8776_ALCZC 0x080 +#define WM8776_LCEN 0x100 + +/* ALCCTRL3 */ +#define WM8776_ATK_MASK 0x00f +#define WM8776_DCY_MASK 0x0f0 + +/* NOISEGATE */ +#define WM8776_NGAT 0x001 +#define WM8776_NGTH_MASK 0x01c + +/* LIMITER */ +#define WM8776_MAXATTEN_MASK 0x00f +#define WM8776_TRANWIN_MASK 0x070 + +/* ADCMUX */ +#define WM8776_AMX_MASK 0x01f +#define WM8776_MUTERA 0x040 +#define WM8776_MUTELA 0x080 +#define WM8776_LRBOTH 0x100 + +/* OUTMUX */ +#define WM8776_MX_DAC 0x001 +#define WM8776_MX_AUX 0x002 +#define WM8776_MX_BYPASS 0x004 + +#endif diff --git a/sound/pci/oxygen/xonar.h b/sound/pci/oxygen/xonar.h index 89b3ed814d6..b35343b0a9a 100644 --- a/sound/pci/oxygen/xonar.h +++ b/sound/pci/oxygen/xonar.h @@ -35,6 +35,8 @@ int get_xonar_pcm179x_model(struct oxygen *chip, const struct pci_device_id *id); int get_xonar_cs43xx_model(struct oxygen *chip, const struct pci_device_id *id); +int get_xonar_wm87x6_model(struct oxygen *chip, + const struct pci_device_id *id); /* HDMI helper functions */ diff --git a/sound/pci/oxygen/xonar_wm87x6.c b/sound/pci/oxygen/xonar_wm87x6.c new file mode 100644 index 00000000000..7754db166d9 --- /dev/null +++ b/sound/pci/oxygen/xonar_wm87x6.c @@ -0,0 +1,1021 @@ +/* + * card driver for models with WM8776/WM8766 DACs (Xonar DS) + * + * Copyright (c) Clemens Ladisch + * + * + * This driver is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2. + * + * This driver 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 driver; if not, see . + */ + +/* + * Xonar DS + * -------- + * + * CMI8788: + * + * SPI 0 -> WM8766 (surround, center/LFE, back) + * SPI 1 -> WM8776 (front, input) + * + * GPIO 4 <- headphone detect + * GPIO 6 -> route input jack to input 1/2 (1/0) + * GPIO 7 -> enable output to speakers + * GPIO 8 -> enable output to speakers + */ + +#include +#include +#include +#include +#include +#include +#include +#include "xonar.h" +#include "wm8776.h" +#include "wm8766.h" + +#define GPIO_DS_HP_DETECT 0x0010 +#define GPIO_DS_INPUT_ROUTE 0x0040 +#define GPIO_DS_OUTPUT_ENABLE 0x0180 + +#define LC_CONTROL_LIMITER 0x40000000 +#define LC_CONTROL_ALC 0x20000000 + +struct xonar_wm87x6 { + struct xonar_generic generic; + u16 wm8776_regs[0x17]; + u16 wm8766_regs[0x10]; + struct snd_kcontrol *lc_controls[13]; +}; + +static void wm8776_write(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (1 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_LO, + (reg << 9) | value); + if (reg < ARRAY_SIZE(data->wm8776_regs)) { + if (reg >= WM8776_HPLVOL || reg <= WM8776_DACMASTER) + value &= ~WM8776_UPDATE; + data->wm8776_regs[reg] = value; + } +} + +static void wm8776_write_cached(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + if (reg >= ARRAY_SIZE(data->wm8776_regs) || + value != data->wm8776_regs[reg]) + wm8776_write(chip, reg, value); +} + +static void wm8766_write(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + oxygen_write_spi(chip, OXYGEN_SPI_TRIGGER | + OXYGEN_SPI_DATA_LENGTH_2 | + OXYGEN_SPI_CLOCK_160 | + (0 << OXYGEN_SPI_CODEC_SHIFT) | + OXYGEN_SPI_CEN_LATCH_CLOCK_LO, + (reg << 9) | value); + if (reg < ARRAY_SIZE(data->wm8766_regs)) + data->wm8766_regs[reg] = value; +} + +static void wm8766_write_cached(struct oxygen *chip, + unsigned int reg, unsigned int value) +{ + struct xonar_wm87x6 *data = chip->model_data; + + if (reg >= ARRAY_SIZE(data->wm8766_regs) || + value != data->wm8766_regs[reg]) { + if ((reg >= WM8766_LDA1 && reg <= WM8766_RDA1) || + (reg >= WM8766_LDA2 && reg <= WM8766_MASTDA)) + value &= ~WM8766_UPDATE; + wm8766_write(chip, reg, value); + } +} + +static void wm8776_registers_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + wm8776_write(chip, WM8776_RESET, 0); + wm8776_write(chip, WM8776_DACCTRL1, WM8776_DZCEN | + WM8776_PL_LEFT_LEFT | WM8776_PL_RIGHT_RIGHT); + wm8776_write(chip, WM8776_DACMUTE, chip->dac_mute ? WM8776_DMUTE : 0); + wm8776_write(chip, WM8776_DACIFCTRL, + WM8776_DACFMT_LJUST | WM8776_DACWL_24); + wm8776_write(chip, WM8776_ADCIFCTRL, + data->wm8776_regs[WM8776_ADCIFCTRL]); + wm8776_write(chip, WM8776_MSTRCTRL, data->wm8776_regs[WM8776_MSTRCTRL]); + wm8776_write(chip, WM8776_PWRDOWN, data->wm8776_regs[WM8776_PWRDOWN]); + wm8776_write(chip, WM8776_HPLVOL, data->wm8776_regs[WM8776_HPLVOL]); + wm8776_write(chip, WM8776_HPRVOL, data->wm8776_regs[WM8776_HPRVOL] | + WM8776_UPDATE); + wm8776_write(chip, WM8776_ADCLVOL, data->wm8776_regs[WM8776_ADCLVOL]); + wm8776_write(chip, WM8776_ADCRVOL, data->wm8776_regs[WM8776_ADCRVOL]); + wm8776_write(chip, WM8776_ADCMUX, data->wm8776_regs[WM8776_ADCMUX]); + wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0]); + wm8776_write(chip, WM8776_DACRVOL, chip->dac_volume[1] | WM8776_UPDATE); +} + +static void wm8766_registers_init(struct oxygen *chip) +{ + wm8766_write(chip, WM8766_RESET, 0); + wm8766_write(chip, WM8766_INT_CTRL, WM8766_FMT_LJUST | WM8766_IWL_24); + wm8766_write(chip, WM8766_DAC_CTRL2, + WM8766_ZCD | (chip->dac_mute ? WM8766_DMUTE_MASK : 0)); + wm8766_write(chip, WM8766_LDA1, chip->dac_volume[2]); + wm8766_write(chip, WM8766_RDA1, chip->dac_volume[3]); + wm8766_write(chip, WM8766_LDA2, chip->dac_volume[4]); + wm8766_write(chip, WM8766_RDA2, chip->dac_volume[5]); + wm8766_write(chip, WM8766_LDA3, chip->dac_volume[6]); + wm8766_write(chip, WM8766_RDA3, chip->dac_volume[7] | WM8766_UPDATE); +} + +static void wm8776_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + data->wm8776_regs[WM8776_HPLVOL] = (0x79 - 60) | WM8776_HPZCEN; + data->wm8776_regs[WM8776_HPRVOL] = (0x79 - 60) | WM8776_HPZCEN; + data->wm8776_regs[WM8776_ADCIFCTRL] = + WM8776_ADCFMT_LJUST | WM8776_ADCWL_24 | WM8776_ADCMCLK; + data->wm8776_regs[WM8776_MSTRCTRL] = + WM8776_ADCRATE_256 | WM8776_DACRATE_256; + data->wm8776_regs[WM8776_PWRDOWN] = WM8776_HPPD; + data->wm8776_regs[WM8776_ADCLVOL] = 0xa5 | WM8776_ZCA; + data->wm8776_regs[WM8776_ADCRVOL] = 0xa5 | WM8776_ZCA; + data->wm8776_regs[WM8776_ADCMUX] = 0x001; + wm8776_registers_init(chip); +} + +static void xonar_ds_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + + data->generic.anti_pop_delay = 300; + data->generic.output_enable_bit = GPIO_DS_OUTPUT_ENABLE; + + wm8776_init(chip); + wm8766_registers_init(chip); + + oxygen_write16_masked(chip, OXYGEN_GPIO_CONTROL, GPIO_DS_INPUT_ROUTE, + GPIO_DS_HP_DETECT | GPIO_DS_INPUT_ROUTE); + oxygen_set_bits16(chip, OXYGEN_GPIO_DATA, GPIO_DS_INPUT_ROUTE); + oxygen_set_bits16(chip, OXYGEN_GPIO_INTERRUPT_MASK, GPIO_DS_HP_DETECT); + chip->interrupt_mask |= OXYGEN_INT_GPIO; + + xonar_enable_output(chip); + + snd_component_add(chip->card, "WM8776"); + snd_component_add(chip->card, "WM8766"); +} + +static void xonar_ds_cleanup(struct oxygen *chip) +{ + xonar_disable_output(chip); +} + +static void xonar_ds_suspend(struct oxygen *chip) +{ + xonar_ds_cleanup(chip); +} + +static void xonar_ds_resume(struct oxygen *chip) +{ + wm8776_registers_init(chip); + wm8766_registers_init(chip); + xonar_enable_output(chip); +} + +static void wm8776_adc_hardware_filter(unsigned int channel, + struct snd_pcm_hardware *hardware) +{ + if (channel == PCM_A) { + hardware->rates = SNDRV_PCM_RATE_32000 | + SNDRV_PCM_RATE_44100 | + SNDRV_PCM_RATE_48000 | + SNDRV_PCM_RATE_64000 | + SNDRV_PCM_RATE_88200 | + SNDRV_PCM_RATE_96000; + hardware->rate_max = 96000; + } +} + +static void set_wm87x6_dac_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ +} + +static void set_wm8776_adc_params(struct oxygen *chip, + struct snd_pcm_hw_params *params) +{ + u16 reg; + + reg = WM8776_ADCRATE_256 | WM8776_DACRATE_256; + if (params_rate(params) > 48000) + reg |= WM8776_ADCOSR; + wm8776_write_cached(chip, WM8776_MSTRCTRL, reg); +} + +static void update_wm8776_volume(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + u8 to_change; + + if (chip->dac_volume[0] == chip->dac_volume[1]) { + if (chip->dac_volume[0] != data->wm8776_regs[WM8776_DACLVOL] || + chip->dac_volume[1] != data->wm8776_regs[WM8776_DACRVOL]) { + wm8776_write(chip, WM8776_DACMASTER, + chip->dac_volume[0] | WM8776_UPDATE); + data->wm8776_regs[WM8776_DACLVOL] = chip->dac_volume[0]; + data->wm8776_regs[WM8776_DACRVOL] = chip->dac_volume[0]; + } + } else { + to_change = (chip->dac_volume[0] != + data->wm8776_regs[WM8776_DACLVOL]) << 0; + to_change |= (chip->dac_volume[1] != + data->wm8776_regs[WM8776_DACLVOL]) << 1; + if (to_change & 1) + wm8776_write(chip, WM8776_DACLVOL, chip->dac_volume[0] | + ((to_change & 2) ? 0 : WM8776_UPDATE)); + if (to_change & 2) + wm8776_write(chip, WM8776_DACRVOL, + chip->dac_volume[1] | WM8776_UPDATE); + } +} + +static void update_wm87x6_volume(struct oxygen *chip) +{ + static const u8 wm8766_regs[6] = { + WM8766_LDA1, WM8766_RDA1, + WM8766_LDA2, WM8766_RDA2, + WM8766_LDA3, WM8766_RDA3, + }; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int i; + u8 to_change; + + update_wm8776_volume(chip); + if (chip->dac_volume[2] == chip->dac_volume[3] && + chip->dac_volume[2] == chip->dac_volume[4] && + chip->dac_volume[2] == chip->dac_volume[5] && + chip->dac_volume[2] == chip->dac_volume[6] && + chip->dac_volume[2] == chip->dac_volume[7]) { + to_change = 0; + for (i = 0; i < 6; ++i) + if (chip->dac_volume[2] != + data->wm8766_regs[wm8766_regs[i]]) + to_change = 1; + if (to_change) { + wm8766_write(chip, WM8766_MASTDA, + chip->dac_volume[2] | WM8766_UPDATE); + for (i = 0; i < 6; ++i) + data->wm8766_regs[wm8766_regs[i]] = + chip->dac_volume[2]; + } + } else { + to_change = 0; + for (i = 0; i < 6; ++i) + to_change |= (chip->dac_volume[2 + i] != + data->wm8766_regs[wm8766_regs[i]]) << i; + for (i = 0; i < 6; ++i) + if (to_change & (1 << i)) + wm8766_write(chip, wm8766_regs[i], + chip->dac_volume[2 + i] | + ((to_change & (0x3e << i)) + ? 0 : WM8766_UPDATE)); + } +} + +static void update_wm8776_mute(struct oxygen *chip) +{ + wm8776_write_cached(chip, WM8776_DACMUTE, + chip->dac_mute ? WM8776_DMUTE : 0); +} + +static void update_wm87x6_mute(struct oxygen *chip) +{ + update_wm8776_mute(chip); + wm8766_write_cached(chip, WM8766_DAC_CTRL2, WM8766_ZCD | + (chip->dac_mute ? WM8766_DMUTE_MASK : 0)); +} + +static void xonar_ds_gpio_changed(struct oxygen *chip) +{ + u16 bits; + + bits = oxygen_read16(chip, OXYGEN_GPIO_DATA); + snd_printk(KERN_INFO "HP detect: %d\n", !!(bits & GPIO_DS_HP_DETECT)); +} + +static int wm8776_bit_switch_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + u16 bit = ctl->private_value & 0xffff; + unsigned int reg_index = (ctl->private_value >> 16) & 0xff; + bool invert = (ctl->private_value >> 24) & 1; + + value->value.integer.value[0] = + ((data->wm8776_regs[reg_index] & bit) != 0) ^ invert; + return 0; +} + +static int wm8776_bit_switch_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + u16 bit = ctl->private_value & 0xffff; + u16 reg_value; + unsigned int reg_index = (ctl->private_value >> 16) & 0xff; + bool invert = (ctl->private_value >> 24) & 1; + int changed; + + mutex_lock(&chip->mutex); + reg_value = data->wm8776_regs[reg_index] & ~bit; + if (value->value.integer.value[0] ^ invert) + reg_value |= bit; + changed = reg_value != data->wm8776_regs[reg_index]; + if (changed) + wm8776_write(chip, reg_index, reg_value); + mutex_unlock(&chip->mutex); + return changed; +} + +static int wm8776_field_enum_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const hld[16] = { + "0 ms", "2.67 ms", "5.33 ms", "10.6 ms", + "21.3 ms", "42.7 ms", "85.3 ms", "171 ms", + "341 ms", "683 ms", "1.37 s", "2.73 s", + "5.46 s", "10.9 s", "21.8 s", "43.7 s", + }; + static const char *const atk_lim[11] = { + "0.25 ms", "0.5 ms", "1 ms", "2 ms", + "4 ms", "8 ms", "16 ms", "32 ms", + "64 ms", "128 ms", "256 ms", + }; + static const char *const atk_alc[11] = { + "8.40 ms", "16.8 ms", "33.6 ms", "67.2 ms", + "134 ms", "269 ms", "538 ms", "1.08 s", + "2.15 s", "4.3 s", "8.6 s", + }; + static const char *const dcy_lim[11] = { + "1.2 ms", "2.4 ms", "4.8 ms", "9.6 ms", + "19.2 ms", "38.4 ms", "76.8 ms", "154 ms", + "307 ms", "614 ms", "1.23 s", + }; + static const char *const dcy_alc[11] = { + "33.5 ms", "67.0 ms", "134 ms", "268 ms", + "536 ms", "1.07 s", "2.14 s", "4.29 s", + "8.58 s", "17.2 s", "34.3 s", + }; + static const char *const tranwin[8] = { + "0 us", "62.5 us", "125 us", "250 us", + "500 us", "1 ms", "2 ms", "4 ms", + }; + u8 max; + const char *const *names; + + max = (ctl->private_value >> 12) & 0xf; + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = 1; + info->value.enumerated.items = max + 1; + if (info->value.enumerated.item > max) + info->value.enumerated.item = max; + switch ((ctl->private_value >> 24) & 0x1f) { + case WM8776_ALCCTRL2: + names = hld; + break; + case WM8776_ALCCTRL3: + if (((ctl->private_value >> 20) & 0xf) == 0) { + if (ctl->private_value & LC_CONTROL_LIMITER) + names = atk_lim; + else + names = atk_alc; + } else { + if (ctl->private_value & LC_CONTROL_LIMITER) + names = dcy_lim; + else + names = dcy_alc; + } + break; + case WM8776_LIMITER: + names = tranwin; + break; + default: + return -ENXIO; + } + strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); + return 0; +} + +static int wm8776_field_volume_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 1; + info->value.integer.min = (ctl->private_value >> 8) & 0xf; + info->value.integer.max = (ctl->private_value >> 12) & 0xf; + return 0; +} + +static void wm8776_field_set_from_ctl(struct snd_kcontrol *ctl) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int value, reg_index, mode; + u8 min, max, shift; + u16 mask, reg_value; + bool invert; + + if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) == + WM8776_LCSEL_LIMITER) + mode = LC_CONTROL_LIMITER; + else + mode = LC_CONTROL_ALC; + if (!(ctl->private_value & mode)) + return; + + value = ctl->private_value & 0xf; + min = (ctl->private_value >> 8) & 0xf; + max = (ctl->private_value >> 12) & 0xf; + mask = (ctl->private_value >> 16) & 0xf; + shift = (ctl->private_value >> 20) & 0xf; + reg_index = (ctl->private_value >> 24) & 0x1f; + invert = (ctl->private_value >> 29) & 0x1; + + if (invert) + value = max - (value - min); + reg_value = data->wm8776_regs[reg_index]; + reg_value &= ~(mask << shift); + reg_value |= value << shift; + wm8776_write_cached(chip, reg_index, reg_value); +} + +static int wm8776_field_set(struct snd_kcontrol *ctl, unsigned int value) +{ + struct oxygen *chip = ctl->private_data; + u8 min, max; + int changed; + + min = (ctl->private_value >> 8) & 0xf; + max = (ctl->private_value >> 12) & 0xf; + if (value < min || value > max) + return -EINVAL; + mutex_lock(&chip->mutex); + changed = value != (ctl->private_value & 0xf); + if (changed) { + ctl->private_value = (ctl->private_value & ~0xf) | value; + wm8776_field_set_from_ctl(ctl); + } + mutex_unlock(&chip->mutex); + return changed; +} + +static int wm8776_field_enum_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + value->value.enumerated.item[0] = ctl->private_value & 0xf; + return 0; +} + +static int wm8776_field_volume_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + value->value.integer.value[0] = ctl->private_value & 0xf; + return 0; +} + +static int wm8776_field_enum_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + return wm8776_field_set(ctl, value->value.enumerated.item[0]); +} + +static int wm8776_field_volume_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + return wm8776_field_set(ctl, value->value.integer.value[0]); +} + +static int wm8776_hp_vol_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0x79 - 60; + info->value.integer.max = 0x7f; + return 0; +} + +static int wm8776_hp_vol_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + mutex_lock(&chip->mutex); + value->value.integer.value[0] = + data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK; + value->value.integer.value[1] = + data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK; + mutex_unlock(&chip->mutex); + return 0; +} + +static int wm8776_hp_vol_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + u8 to_update; + + mutex_lock(&chip->mutex); + to_update = (value->value.integer.value[0] != + (data->wm8776_regs[WM8776_HPLVOL] & WM8776_HPATT_MASK)) + << 0; + to_update |= (value->value.integer.value[1] != + (data->wm8776_regs[WM8776_HPRVOL] & WM8776_HPATT_MASK)) + << 1; + if (value->value.integer.value[0] == value->value.integer.value[1]) { + if (to_update) { + wm8776_write(chip, WM8776_HPMASTER, + value->value.integer.value[0] | + WM8776_HPZCEN | WM8776_UPDATE); + data->wm8776_regs[WM8776_HPLVOL] = + value->value.integer.value[0] | WM8776_HPZCEN; + data->wm8776_regs[WM8776_HPRVOL] = + value->value.integer.value[0] | WM8776_HPZCEN; + } + } else { + if (to_update & 1) + wm8776_write(chip, WM8776_HPLVOL, + value->value.integer.value[0] | + WM8776_HPZCEN | + ((to_update & 2) ? 0 : WM8776_UPDATE)); + if (to_update & 2) + wm8776_write(chip, WM8776_HPRVOL, + value->value.integer.value[1] | + WM8776_HPZCEN | WM8776_UPDATE); + } + mutex_unlock(&chip->mutex); + return to_update != 0; +} + +static int wm8776_input_mux_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int mux_bit = ctl->private_value; + + value->value.integer.value[0] = + !!(data->wm8776_regs[WM8776_ADCMUX] & mux_bit); + return 0; +} + +static int wm8776_input_mux_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int mux_bit = ctl->private_value; + u16 reg; + int changed; + + mutex_lock(&chip->mutex); + reg = data->wm8776_regs[WM8776_ADCMUX]; + if (value->value.integer.value[0]) { + reg &= ~0x003; + reg |= mux_bit; + } else + reg &= ~mux_bit; + changed = reg != data->wm8776_regs[WM8776_ADCMUX]; + if (changed) { + oxygen_write16_masked(chip, OXYGEN_GPIO_DATA, + reg & 1 ? GPIO_DS_INPUT_ROUTE : 0, + GPIO_DS_INPUT_ROUTE); + wm8776_write(chip, WM8776_ADCMUX, reg); + } + mutex_unlock(&chip->mutex); + return changed; +} + +static int wm8776_input_vol_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + info->type = SNDRV_CTL_ELEM_TYPE_INTEGER; + info->count = 2; + info->value.integer.min = 0xa5; + info->value.integer.max = 0xff; + return 0; +} + +static int wm8776_input_vol_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + mutex_lock(&chip->mutex); + value->value.integer.value[0] = + data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK; + value->value.integer.value[1] = + data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK; + mutex_unlock(&chip->mutex); + return 0; +} + +static int wm8776_input_vol_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + int changed = 0; + + mutex_lock(&chip->mutex); + changed = (value->value.integer.value[0] != + (data->wm8776_regs[WM8776_ADCLVOL] & WM8776_AGMASK)) || + (value->value.integer.value[1] != + (data->wm8776_regs[WM8776_ADCRVOL] & WM8776_AGMASK)); + wm8776_write_cached(chip, WM8776_ADCLVOL, + value->value.integer.value[0] | WM8776_ZCA); + wm8776_write_cached(chip, WM8776_ADCRVOL, + value->value.integer.value[1] | WM8776_ZCA); + mutex_unlock(&chip->mutex); + return changed; +} + +static int wm8776_level_control_info(struct snd_kcontrol *ctl, + struct snd_ctl_elem_info *info) +{ + static const char *const names[3] = { + "None", "Peak Limiter", "Automatic Level Control" + }; + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = 1; + info->value.enumerated.items = 3; + if (info->value.enumerated.item >= 3) + info->value.enumerated.item = 2; + strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); + return 0; +} + +static int wm8776_level_control_get(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + if (!(data->wm8776_regs[WM8776_ALCCTRL2] & WM8776_LCEN)) + value->value.enumerated.item[0] = 0; + else if ((data->wm8776_regs[WM8776_ALCCTRL1] & WM8776_LCSEL_MASK) == + WM8776_LCSEL_LIMITER) + value->value.enumerated.item[0] = 1; + else + value->value.enumerated.item[0] = 2; + return 0; +} + +static void activate_control(struct oxygen *chip, + struct snd_kcontrol *ctl, unsigned int mode) +{ + unsigned int access; + + if (ctl->private_value & mode) + access = 0; + else + access = SNDRV_CTL_ELEM_ACCESS_INACTIVE; + if ((ctl->vd[0].access & SNDRV_CTL_ELEM_ACCESS_INACTIVE) != access) { + ctl->vd[0].access ^= SNDRV_CTL_ELEM_ACCESS_INACTIVE; + snd_ctl_notify(chip->card, SNDRV_CTL_EVENT_MASK_INFO, &ctl->id); + } +} + +static int wm8776_level_control_put(struct snd_kcontrol *ctl, + struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int mode = 0, i; + u16 ctrl1, ctrl2; + int changed; + + if (value->value.enumerated.item[0] >= 3) + return -EINVAL; + mutex_lock(&chip->mutex); + changed = value->value.enumerated.item[0] != ctl->private_value; + if (changed) { + ctl->private_value = value->value.enumerated.item[0]; + ctrl1 = data->wm8776_regs[WM8776_ALCCTRL1]; + ctrl2 = data->wm8776_regs[WM8776_ALCCTRL2]; + switch (value->value.enumerated.item[0]) { + default: + wm8776_write_cached(chip, WM8776_ALCCTRL2, + ctrl2 & ~WM8776_LCEN); + break; + case 1: + wm8776_write_cached(chip, WM8776_ALCCTRL1, + (ctrl1 & ~WM8776_LCSEL_MASK) | + WM8776_LCSEL_LIMITER); + wm8776_write_cached(chip, WM8776_ALCCTRL2, + ctrl2 | WM8776_LCEN); + mode = LC_CONTROL_LIMITER; + break; + case 2: + wm8776_write_cached(chip, WM8776_ALCCTRL1, + (ctrl1 & ~WM8776_LCSEL_MASK) | + WM8776_LCSEL_ALC_STEREO); + wm8776_write_cached(chip, WM8776_ALCCTRL2, + ctrl2 | WM8776_LCEN); + mode = LC_CONTROL_ALC; + break; + } + for (i = 0; i < ARRAY_SIZE(data->lc_controls); ++i) + activate_control(chip, data->lc_controls[i], mode); + } + mutex_unlock(&chip->mutex); + return changed; +} + +static int hpf_info(struct snd_kcontrol *ctl, struct snd_ctl_elem_info *info) +{ + static const char *const names[2] = { + "None", "High-pass Filter" + }; + + info->type = SNDRV_CTL_ELEM_TYPE_ENUMERATED; + info->count = 1; + info->value.enumerated.items = 2; + if (info->value.enumerated.item >= 2) + info->value.enumerated.item = 1; + strcpy(info->value.enumerated.name, names[info->value.enumerated.item]); + return 0; +} + +static int hpf_get(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + + value->value.enumerated.item[0] = + !(data->wm8776_regs[WM8776_ADCIFCTRL] & WM8776_ADCHPD); + return 0; +} + +static int hpf_put(struct snd_kcontrol *ctl, struct snd_ctl_elem_value *value) +{ + struct oxygen *chip = ctl->private_data; + struct xonar_wm87x6 *data = chip->model_data; + unsigned int reg; + int changed; + + mutex_lock(&chip->mutex); + reg = data->wm8776_regs[WM8776_ADCIFCTRL] & ~WM8776_ADCHPD; + if (!value->value.enumerated.item[0]) + reg |= WM8776_ADCHPD; + changed = reg != data->wm8776_regs[WM8776_ADCIFCTRL]; + if (changed) + wm8776_write(chip, WM8776_ADCIFCTRL, reg); + mutex_unlock(&chip->mutex); + return changed; +} + +#define WM8776_BIT_SWITCH(xname, reg, bit, invert, flags) { \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .info = snd_ctl_boolean_mono_info, \ + .get = wm8776_bit_switch_get, \ + .put = wm8776_bit_switch_put, \ + .private_value = ((reg) << 16) | (bit) | ((invert) << 24) | (flags), \ +} +#define _WM8776_FIELD_CTL(xname, reg, shift, initval, min, max, mask, flags) \ + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, \ + .name = xname, \ + .private_value = (initval) | ((min) << 8) | ((max) << 12) | \ + ((mask) << 16) | ((shift) << 20) | ((reg) << 24) | (flags) +#define WM8776_FIELD_CTL_ENUM(xname, reg, shift, init, min, max, mask, flags) {\ + _WM8776_FIELD_CTL(xname " Capture Enum", \ + reg, shift, init, min, max, mask, flags), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_INACTIVE, \ + .info = wm8776_field_enum_info, \ + .get = wm8776_field_enum_get, \ + .put = wm8776_field_enum_put, \ +} +#define WM8776_FIELD_CTL_VOLUME(a, b, c, d, e, f, g, h, tlv_p) { \ + _WM8776_FIELD_CTL(a " Capture Volume", b, c, d, e, f, g, h), \ + .access = SNDRV_CTL_ELEM_ACCESS_READWRITE | \ + SNDRV_CTL_ELEM_ACCESS_INACTIVE | \ + SNDRV_CTL_ELEM_ACCESS_TLV_READ, \ + .info = wm8776_field_volume_info, \ + .get = wm8776_field_volume_get, \ + .put = wm8776_field_volume_put, \ + .tlv = { .p = tlv_p }, \ +} + +static const DECLARE_TLV_DB_SCALE(wm87x6_dac_db_scale, -6000, 50, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_adc_db_scale, -2100, 50, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_hp_db_scale, -6000, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_lct_db_scale, -1600, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxgain_db_scale, 0, 400, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_ngth_db_scale, -7800, 600, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_lim_db_scale, -1200, 100, 0); +static const DECLARE_TLV_DB_SCALE(wm8776_maxatten_alc_db_scale, -2100, 400, 0); + +static const struct snd_kcontrol_new ds_controls[] = { + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Headphone Playback Volume", + .info = wm8776_hp_vol_info, + .get = wm8776_hp_vol_get, + .put = wm8776_hp_vol_put, + .tlv = { .p = wm8776_hp_db_scale }, + }, + WM8776_BIT_SWITCH("Headphone Playback Switch", + WM8776_PWRDOWN, WM8776_HPPD, 1, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Input Capture Volume", + .info = wm8776_input_vol_info, + .get = wm8776_input_vol_get, + .put = wm8776_input_vol_put, + .tlv = { .p = wm8776_adc_db_scale }, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Line Capture Switch", + .info = snd_ctl_boolean_mono_info, + .get = wm8776_input_mux_get, + .put = wm8776_input_mux_put, + .private_value = 1 << 0, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Mic Capture Switch", + .info = snd_ctl_boolean_mono_info, + .get = wm8776_input_mux_get, + .put = wm8776_input_mux_put, + .private_value = 1 << 1, + }, + WM8776_BIT_SWITCH("Aux", WM8776_ADCMUX, 1 << 2, 0, 0), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "ADC Filter Capture Enum", + .info = hpf_info, + .get = hpf_get, + .put = hpf_put, + }, + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Level Control Capture Enum", + .info = wm8776_level_control_info, + .get = wm8776_level_control_get, + .put = wm8776_level_control_put, + .private_value = 0, + }, +}; +static const struct snd_kcontrol_new lc_controls[] = { + WM8776_FIELD_CTL_VOLUME("Limiter Threshold", + WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf, + LC_CONTROL_LIMITER, wm8776_lct_db_scale), + WM8776_FIELD_CTL_ENUM("Limiter Attack Time", + WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf, + LC_CONTROL_LIMITER), + WM8776_FIELD_CTL_ENUM("Limiter Decay Time", + WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf, + LC_CONTROL_LIMITER), + WM8776_FIELD_CTL_ENUM("Limiter Transient Window", + WM8776_LIMITER, 4, 2, 0, 7, 0x7, + LC_CONTROL_LIMITER), + WM8776_FIELD_CTL_VOLUME("Limiter Maximum Attenuation", + WM8776_LIMITER, 0, 6, 3, 12, 0xf, + LC_CONTROL_LIMITER, + wm8776_maxatten_lim_db_scale), + WM8776_FIELD_CTL_VOLUME("ALC Target Level", + WM8776_ALCCTRL1, 0, 11, 0, 15, 0xf, + LC_CONTROL_ALC, wm8776_lct_db_scale), + WM8776_FIELD_CTL_ENUM("ALC Attack Time", + WM8776_ALCCTRL3, 0, 2, 0, 10, 0xf, + LC_CONTROL_ALC), + WM8776_FIELD_CTL_ENUM("ALC Decay Time", + WM8776_ALCCTRL3, 4, 3, 0, 10, 0xf, + LC_CONTROL_ALC), + WM8776_FIELD_CTL_VOLUME("ALC Maximum Gain", + WM8776_ALCCTRL1, 4, 7, 1, 7, 0x7, + LC_CONTROL_ALC, wm8776_maxgain_db_scale), + WM8776_FIELD_CTL_VOLUME("ALC Maximum Attenuation", + WM8776_LIMITER, 0, 10, 10, 15, 0xf, + LC_CONTROL_ALC, wm8776_maxatten_alc_db_scale), + WM8776_FIELD_CTL_ENUM("ALC Hold Time", + WM8776_ALCCTRL2, 0, 0, 0, 15, 0xf, + LC_CONTROL_ALC), + WM8776_BIT_SWITCH("Noise Gate Capture Switch", + WM8776_NOISEGATE, WM8776_NGAT, 0, + LC_CONTROL_ALC), + WM8776_FIELD_CTL_VOLUME("Noise Gate Threshold", + WM8776_NOISEGATE, 2, 0, 0, 7, 0x7, + LC_CONTROL_ALC, wm8776_ngth_db_scale), +}; + +static int xonar_ds_control_filter(struct snd_kcontrol_new *template) +{ + if (!strncmp(template->name, "CD Capture ", 11)) + return 1; /* no CD input */ + return 0; +} + +static int xonar_ds_mixer_init(struct oxygen *chip) +{ + struct xonar_wm87x6 *data = chip->model_data; + unsigned int i; + struct snd_kcontrol *ctl; + int err; + + for (i = 0; i < ARRAY_SIZE(ds_controls); ++i) { + ctl = snd_ctl_new1(&ds_controls[i], chip); + if (!ctl) + return -ENOMEM; + err = snd_ctl_add(chip->card, ctl); + if (err < 0) + return err; + } + BUILD_BUG_ON(ARRAY_SIZE(lc_controls) != ARRAY_SIZE(data->lc_controls)); + for (i = 0; i < ARRAY_SIZE(lc_controls); ++i) { + ctl = snd_ctl_new1(&lc_controls[i], chip); + if (!ctl) + return -ENOMEM; + err = snd_ctl_add(chip->card, ctl); + if (err < 0) + return err; + data->lc_controls[i] = ctl; + } + return 0; +} + +static const struct oxygen_model model_xonar_ds = { + .shortname = "Xonar DS", + .longname = "Asus Virtuoso 200", + .chip = "AV200", + .init = xonar_ds_init, + .control_filter = xonar_ds_control_filter, + .mixer_init = xonar_ds_mixer_init, + .cleanup = xonar_ds_cleanup, + .suspend = xonar_ds_suspend, + .resume = xonar_ds_resume, + .pcm_hardware_filter = wm8776_adc_hardware_filter, + .get_i2s_mclk = oxygen_default_i2s_mclk, + .set_dac_params = set_wm87x6_dac_params, + .set_adc_params = set_wm8776_adc_params, + .update_dac_volume = update_wm87x6_volume, + .update_dac_mute = update_wm87x6_mute, + .gpio_changed = xonar_ds_gpio_changed, + .dac_tlv = wm87x6_dac_db_scale, + .model_data_size = sizeof(struct xonar_wm87x6), + .device_config = PLAYBACK_0_TO_I2S | + PLAYBACK_1_TO_SPDIF | + CAPTURE_0_FROM_I2S_1, + .dac_channels = 8, + .dac_volume_min = 255 - 2*60, + .dac_volume_max = 255, + .function_flags = OXYGEN_FUNCTION_SPI, + .dac_i2s_format = OXYGEN_I2S_FORMAT_LJUST, + .adc_i2s_format = OXYGEN_I2S_FORMAT_LJUST, +}; + +int __devinit get_xonar_wm87x6_model(struct oxygen *chip, + const struct pci_device_id *id) +{ + switch (id->subdevice) { + case 0x838e: + chip->model = model_xonar_ds; + break; + default: + return -EINVAL; + } + return 0; +} -- cgit v1.2.3-70-g09d2 From 6d955180b2f9ccff444df06265160868cabb289a Mon Sep 17 00:00:00 2001 From: Octavian Purdila Date: Mon, 18 Jan 2010 12:58:44 +0000 Subject: ipv4: allow warming up the ARP cache with request type gratuitous ARP If the per device ARP_ACCEPT option is enable, currently we only allow creating new ARP cache entries for response type gratuitous ARP. Allowing gratuitous ARP to create new ARP entries (not only to update existing ones) is useful when we want to avoid unnecessary delays for the first packet of a stream. This patch allows request type gratuitous ARP to create new ARP cache entries as well. This is useful when we want to populate the ARP cache entries for a large number of hosts on the same LAN. Signed-off-by: Octavian Purdila Signed-off-by: David S. Miller --- Documentation/networking/ip-sysctl.txt | 15 ++++++++++++--- net/ipv4/arp.c | 3 ++- 2 files changed, 14 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt index c532884f4fe..2dc7a1d9768 100644 --- a/Documentation/networking/ip-sysctl.txt +++ b/Documentation/networking/ip-sysctl.txt @@ -852,9 +852,18 @@ arp_notify - BOOLEAN or hardware address changes. arp_accept - BOOLEAN - Define behavior when gratuitous arp replies are received: - 0 - drop gratuitous arp frames - 1 - accept gratuitous arp frames + Define behavior for gratuitous ARP frames who's IP is not + already present in the ARP table: + 0 - don't create new entries in the ARP table + 1 - create new entries in the ARP table + + Both replies and requests type gratuitous arp will trigger the + ARP table to be updated, if this setting is on. + + If the ARP table already contains the IP address of the + gratuitous arp frame, the arp table will be updated regardless + if this setting is on or off. + app_solicit - INTEGER The maximum number of probes to send to the user space ARP daemon diff --git a/net/ipv4/arp.c b/net/ipv4/arp.c index 078709233bc..1940b4df769 100644 --- a/net/ipv4/arp.c +++ b/net/ipv4/arp.c @@ -907,7 +907,8 @@ static int arp_process(struct sk_buff *skb) devices (strip is candidate) */ if (n == NULL && - arp->ar_op == htons(ARPOP_REPLY) && + (arp->ar_op == htons(ARPOP_REPLY) || + (arp->ar_op == htons(ARPOP_REQUEST) && tip == sip)) && inet_addr_type(net, sip) == RTN_UNICAST) n = __neigh_lookup(&arp_tbl, &sip, dev, 1); } -- cgit v1.2.3-70-g09d2 From 580d9e00fdfb85e65c5097dcd739c6efcdbadc96 Mon Sep 17 00:00:00 2001 From: Motohiro KOSAKI Date: Mon, 18 Jan 2010 21:35:05 -0500 Subject: kprobetrace, doc: Shell needs single quote to use $ character Shell interprets $val as shell variable, thus we need quote if we use the echo command. Signed-off-by: KOSAKI Motohiro Signed-off-by: Masami Hiramatsu Cc: systemtap Cc: DLE LKML-Reference: <20100119023505.31880.17367.stgit@localhost6.localdomain6> Signed-off-by: Ingo Molnar --- Documentation/trace/kprobetrace.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/trace/kprobetrace.txt b/Documentation/trace/kprobetrace.txt index f30978e001f..ab57f02e53b 100644 --- a/Documentation/trace/kprobetrace.txt +++ b/Documentation/trace/kprobetrace.txt @@ -79,7 +79,7 @@ Usage examples To add a probe as a new event, write a new definition to kprobe_events as below. - echo p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack) > /sys/kernel/debug/tracing/kprobe_events + echo 'p:myprobe do_sys_open dfd=%ax filename=%dx flags=%cx mode=+4($stack)' > /sys/kernel/debug/tracing/kprobe_events This sets a kprobe on the top of do_sys_open() function with recording 1st to 4th arguments as "myprobe" event. Note, which register/stack entry is @@ -88,7 +88,7 @@ the ABI, please try to use probe subcommand of perf-tools (you can find it under tools/perf/). As this example shows, users can choose more familiar names for each arguments. - echo r:myretprobe do_sys_open $retval >> /sys/kernel/debug/tracing/kprobe_events + echo 'r:myretprobe do_sys_open $retval' >> /sys/kernel/debug/tracing/kprobe_events This sets a kretprobe on the return point of do_sys_open() function with recording return value as "myretprobe" event. -- cgit v1.2.3-70-g09d2 From df3ab708b787a2b35de5101452bd51d4a8ae0ded Mon Sep 17 00:00:00 2001 From: Motohiro KOSAKI Date: Mon, 18 Jan 2010 21:35:12 -0500 Subject: kprobetrace, doc: Add the explanation to remove probe points Latest kprobetrace can remove probe points selectively, thus the documentation should be updated too. Signed-off-by: KOSAKI Motohiro Signed-off-by: Masami Hiramatsu Cc: systemtap Cc: DLE LKML-Reference: <20100119023512.31880.35535.stgit@localhost6.localdomain6> Signed-off-by: Ingo Molnar --- Documentation/trace/kprobetrace.txt | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'Documentation') diff --git a/Documentation/trace/kprobetrace.txt b/Documentation/trace/kprobetrace.txt index ab57f02e53b..a9100b28eb8 100644 --- a/Documentation/trace/kprobetrace.txt +++ b/Documentation/trace/kprobetrace.txt @@ -24,6 +24,7 @@ Synopsis of kprobe_events ------------------------- p[:[GRP/]EVENT] SYMBOL[+offs]|MEMADDR [FETCHARGS] : Set a probe r[:[GRP/]EVENT] SYMBOL[+0] [FETCHARGS] : Set a return probe + -:[GRP/]EVENT : Clear a probe GRP : Group name. If omitted, use "kprobes" for it. EVENT : Event name. If omitted, the event name is generated @@ -122,6 +123,12 @@ REC->dfd, REC->filename, REC->flags, REC->mode This clears all probe points. + Or, + + echo -:myprobe >> kprobe_events + + This clears probe points selectively. + Right after definition, each event is disabled by default. For tracing these events, you need to enable it. -- cgit v1.2.3-70-g09d2 From 016ddd9b5b315fe52ac6ba3167521f50b3868d34 Mon Sep 17 00:00:00 2001 From: Jiri Kosina Date: Mon, 18 Jan 2010 17:05:40 +0100 Subject: x86: Put 'nopat' in kernel-parameters Add missing 'nopat' boot option into Documentation/kernel-parameters.txt. Signed-off-by: Jiri Kosina LKML-Reference: Signed-off-by: Ingo Molnar --- Documentation/kernel-parameters.txt | 3 +++ 1 file changed, 3 insertions(+) (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 736d4560288..5bf45c0ac74 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1729,6 +1729,9 @@ and is between 256 and 4096 characters. It is defined in the file nomfgpt [X86-32] Disable Multi-Function General Purpose Timer usage (for AMD Geode machines). + nopat [X86] Disable PAT (page attribute table extension of + pagetables) support. + norandmaps Don't use address space randomization. Equivalent to echo 0 > /proc/sys/kernel/randomize_va_space -- cgit v1.2.3-70-g09d2 From 7c9414385ebfdd87cc542d4e7e3bb0dbb2d3ce25 Mon Sep 17 00:00:00 2001 From: Dhaval Giani Date: Wed, 20 Jan 2010 13:26:18 +0100 Subject: sched: Remove USER_SCHED Remove the USER_SCHED feature. It has been scheduled to be removed in 2.6.34 as per http://marc.info/?l=linux-kernel&m=125728479022976&w=2 Signed-off-by: Dhaval Giani Signed-off-by: Peter Zijlstra LKML-Reference: <1263990378.24844.3.camel@localhost> Signed-off-by: Ingo Molnar --- Documentation/feature-removal-schedule.txt | 15 -- include/linux/sched.h | 14 +- init/Kconfig | 81 +++----- kernel/ksysfs.c | 8 - kernel/sched.c | 114 +---------- kernel/sys.c | 5 - kernel/user.c | 305 ----------------------------- 7 files changed, 38 insertions(+), 504 deletions(-) (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 870d190fe61..04a3fc3d139 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -6,21 +6,6 @@ be removed from this file. --------------------------- -What: USER_SCHED -When: 2.6.34 - -Why: USER_SCHED was implemented as a proof of concept for group scheduling. - The effect of USER_SCHED can already be achieved from userspace with - the help of libcgroup. The removal of USER_SCHED will also simplify - the scheduler code with the removal of one major ifdef. There are also - issues USER_SCHED has with USER_NS. A decision was taken not to fix - those and instead remove USER_SCHED. Also new group scheduling - features will not be implemented for USER_SCHED. - -Who: Dhaval Giani - ---------------------------- - What: PRISM54 When: 2.6.34 diff --git a/include/linux/sched.h b/include/linux/sched.h index 50d685cde70..8b079735ae5 100644 --- a/include/linux/sched.h +++ b/include/linux/sched.h @@ -731,14 +731,6 @@ struct user_struct { uid_t uid; struct user_namespace *user_ns; -#ifdef CONFIG_USER_SCHED - struct task_group *tg; -#ifdef CONFIG_SYSFS - struct kobject kobj; - struct delayed_work work; -#endif -#endif - #ifdef CONFIG_PERF_EVENTS atomic_long_t locked_vm; #endif @@ -2502,13 +2494,9 @@ extern long sched_getaffinity(pid_t pid, struct cpumask *mask); extern void normalize_rt_tasks(void); -#ifdef CONFIG_GROUP_SCHED +#ifdef CONFIG_CGROUP_SCHED extern struct task_group init_task_group; -#ifdef CONFIG_USER_SCHED -extern struct task_group root_task_group; -extern void set_tg_uid(struct user_struct *user); -#endif extern struct task_group *sched_create_group(struct task_group *parent); extern void sched_destroy_group(struct task_group *tg); diff --git a/init/Kconfig b/init/Kconfig index a23da9f0180..e9fa3007a6f 100644 --- a/init/Kconfig +++ b/init/Kconfig @@ -435,57 +435,6 @@ config LOG_BUF_SHIFT config HAVE_UNSTABLE_SCHED_CLOCK bool -config GROUP_SCHED - bool "Group CPU scheduler" - depends on EXPERIMENTAL - default n - help - This feature lets CPU scheduler recognize task groups and control CPU - bandwidth allocation to such task groups. - In order to create a group from arbitrary set of processes, use - CONFIG_CGROUPS. (See Control Group support.) - -config FAIR_GROUP_SCHED - bool "Group scheduling for SCHED_OTHER" - depends on GROUP_SCHED - default GROUP_SCHED - -config RT_GROUP_SCHED - bool "Group scheduling for SCHED_RR/FIFO" - depends on EXPERIMENTAL - depends on GROUP_SCHED - default n - help - This feature lets you explicitly allocate real CPU bandwidth - to users or control groups (depending on the "Basis for grouping tasks" - setting below. If enabled, it will also make it impossible to - schedule realtime tasks for non-root users until you allocate - realtime bandwidth for them. - See Documentation/scheduler/sched-rt-group.txt for more information. - -choice - depends on GROUP_SCHED - prompt "Basis for grouping tasks" - default USER_SCHED - -config USER_SCHED - bool "user id" - help - This option will choose userid as the basis for grouping - tasks, thus providing equal CPU bandwidth to each user. - -config CGROUP_SCHED - bool "Control groups" - depends on CGROUPS - help - This option allows you to create arbitrary task groups - using the "cgroup" pseudo filesystem and control - the cpu bandwidth allocated to each such task group. - Refer to Documentation/cgroups/cgroups.txt for more - information on "cgroup" pseudo filesystem. - -endchoice - menuconfig CGROUPS boolean "Control Group support" help @@ -606,6 +555,36 @@ config CGROUP_MEM_RES_CTLR_SWAP Now, memory usage of swap_cgroup is 2 bytes per entry. If swap page size is 4096bytes, 512k per 1Gbytes of swap. +menuconfig CGROUP_SCHED + bool "Group CPU scheduler" + depends on EXPERIMENTAL && CGROUPS + default n + help + This feature lets CPU scheduler recognize task groups and control CPU + bandwidth allocation to such task groups. It uses cgroups to group + tasks. + +if CGROUP_SCHED +config FAIR_GROUP_SCHED + bool "Group scheduling for SCHED_OTHER" + depends on CGROUP_SCHED + default CGROUP_SCHED + +config RT_GROUP_SCHED + bool "Group scheduling for SCHED_RR/FIFO" + depends on EXPERIMENTAL + depends on CGROUP_SCHED + default n + help + This feature lets you explicitly allocate real CPU bandwidth + to users or control groups (depending on the "Basis for grouping tasks" + setting below. If enabled, it will also make it impossible to + schedule realtime tasks for non-root users until you allocate + realtime bandwidth for them. + See Documentation/scheduler/sched-rt-group.txt for more information. + +endif #CGROUP_SCHED + endif # CGROUPS config MM_OWNER diff --git a/kernel/ksysfs.c b/kernel/ksysfs.c index 3feaf5a7451..6b1ccc3f020 100644 --- a/kernel/ksysfs.c +++ b/kernel/ksysfs.c @@ -197,16 +197,8 @@ static int __init ksysfs_init(void) goto group_exit; } - /* create the /sys/kernel/uids/ directory */ - error = uids_sysfs_init(); - if (error) - goto notes_exit; - return 0; -notes_exit: - if (notes_size > 0) - sysfs_remove_bin_file(kernel_kobj, ¬es_attr); group_exit: sysfs_remove_group(kernel_kobj, &kernel_attr_group); kset_exit: diff --git a/kernel/sched.c b/kernel/sched.c index c0be07932a8..41e76d32564 100644 --- a/kernel/sched.c +++ b/kernel/sched.c @@ -233,7 +233,7 @@ static void destroy_rt_bandwidth(struct rt_bandwidth *rt_b) */ static DEFINE_MUTEX(sched_domains_mutex); -#ifdef CONFIG_GROUP_SCHED +#ifdef CONFIG_CGROUP_SCHED #include @@ -243,13 +243,7 @@ static LIST_HEAD(task_groups); /* task group related information */ struct task_group { -#ifdef CONFIG_CGROUP_SCHED struct cgroup_subsys_state css; -#endif - -#ifdef CONFIG_USER_SCHED - uid_t uid; -#endif #ifdef CONFIG_FAIR_GROUP_SCHED /* schedulable entities of this group on each cpu */ @@ -274,35 +268,7 @@ struct task_group { struct list_head children; }; -#ifdef CONFIG_USER_SCHED - -/* Helper function to pass uid information to create_sched_user() */ -void set_tg_uid(struct user_struct *user) -{ - user->tg->uid = user->uid; -} - -/* - * Root task group. - * Every UID task group (including init_task_group aka UID-0) will - * be a child to this group. - */ -struct task_group root_task_group; - -#ifdef CONFIG_FAIR_GROUP_SCHED -/* Default task group's sched entity on each cpu */ -static DEFINE_PER_CPU(struct sched_entity, init_sched_entity); -/* Default task group's cfs_rq on each cpu */ -static DEFINE_PER_CPU_SHARED_ALIGNED(struct cfs_rq, init_tg_cfs_rq); -#endif /* CONFIG_FAIR_GROUP_SCHED */ - -#ifdef CONFIG_RT_GROUP_SCHED -static DEFINE_PER_CPU(struct sched_rt_entity, init_sched_rt_entity); -static DEFINE_PER_CPU_SHARED_ALIGNED(struct rt_rq, init_rt_rq_var); -#endif /* CONFIG_RT_GROUP_SCHED */ -#else /* !CONFIG_USER_SCHED */ #define root_task_group init_task_group -#endif /* CONFIG_USER_SCHED */ /* task_group_lock serializes add/remove of task groups and also changes to * a task group's cpu shares. @@ -318,11 +284,7 @@ static int root_task_group_empty(void) } #endif -#ifdef CONFIG_USER_SCHED -# define INIT_TASK_GROUP_LOAD (2*NICE_0_LOAD) -#else /* !CONFIG_USER_SCHED */ # define INIT_TASK_GROUP_LOAD NICE_0_LOAD -#endif /* CONFIG_USER_SCHED */ /* * A weight of 0 or 1 can cause arithmetics problems. @@ -348,11 +310,7 @@ static inline struct task_group *task_group(struct task_struct *p) { struct task_group *tg; -#ifdef CONFIG_USER_SCHED - rcu_read_lock(); - tg = __task_cred(p)->user->tg; - rcu_read_unlock(); -#elif defined(CONFIG_CGROUP_SCHED) +#ifdef CONFIG_CGROUP_SCHED tg = container_of(task_subsys_state(p, cpu_cgroup_subsys_id), struct task_group, css); #else @@ -383,7 +341,7 @@ static inline struct task_group *task_group(struct task_struct *p) return NULL; } -#endif /* CONFIG_GROUP_SCHED */ +#endif /* CONFIG_CGROUP_SCHED */ /* CFS-related fields in a runqueue */ struct cfs_rq { @@ -7678,9 +7636,6 @@ void __init sched_init(void) #ifdef CONFIG_RT_GROUP_SCHED alloc_size += 2 * nr_cpu_ids * sizeof(void **); #endif -#ifdef CONFIG_USER_SCHED - alloc_size *= 2; -#endif #ifdef CONFIG_CPUMASK_OFFSTACK alloc_size += num_possible_cpus() * cpumask_size(); #endif @@ -7694,13 +7649,6 @@ void __init sched_init(void) init_task_group.cfs_rq = (struct cfs_rq **)ptr; ptr += nr_cpu_ids * sizeof(void **); -#ifdef CONFIG_USER_SCHED - root_task_group.se = (struct sched_entity **)ptr; - ptr += nr_cpu_ids * sizeof(void **); - - root_task_group.cfs_rq = (struct cfs_rq **)ptr; - ptr += nr_cpu_ids * sizeof(void **); -#endif /* CONFIG_USER_SCHED */ #endif /* CONFIG_FAIR_GROUP_SCHED */ #ifdef CONFIG_RT_GROUP_SCHED init_task_group.rt_se = (struct sched_rt_entity **)ptr; @@ -7709,13 +7657,6 @@ void __init sched_init(void) init_task_group.rt_rq = (struct rt_rq **)ptr; ptr += nr_cpu_ids * sizeof(void **); -#ifdef CONFIG_USER_SCHED - root_task_group.rt_se = (struct sched_rt_entity **)ptr; - ptr += nr_cpu_ids * sizeof(void **); - - root_task_group.rt_rq = (struct rt_rq **)ptr; - ptr += nr_cpu_ids * sizeof(void **); -#endif /* CONFIG_USER_SCHED */ #endif /* CONFIG_RT_GROUP_SCHED */ #ifdef CONFIG_CPUMASK_OFFSTACK for_each_possible_cpu(i) { @@ -7735,22 +7676,13 @@ void __init sched_init(void) #ifdef CONFIG_RT_GROUP_SCHED init_rt_bandwidth(&init_task_group.rt_bandwidth, global_rt_period(), global_rt_runtime()); -#ifdef CONFIG_USER_SCHED - init_rt_bandwidth(&root_task_group.rt_bandwidth, - global_rt_period(), RUNTIME_INF); -#endif /* CONFIG_USER_SCHED */ #endif /* CONFIG_RT_GROUP_SCHED */ -#ifdef CONFIG_GROUP_SCHED +#ifdef CONFIG_CGROUP_SCHED list_add(&init_task_group.list, &task_groups); INIT_LIST_HEAD(&init_task_group.children); -#ifdef CONFIG_USER_SCHED - INIT_LIST_HEAD(&root_task_group.children); - init_task_group.parent = &root_task_group; - list_add(&init_task_group.siblings, &root_task_group.children); -#endif /* CONFIG_USER_SCHED */ -#endif /* CONFIG_GROUP_SCHED */ +#endif /* CONFIG_CGROUP_SCHED */ #if defined CONFIG_FAIR_GROUP_SCHED && defined CONFIG_SMP update_shares_data = __alloc_percpu(nr_cpu_ids * sizeof(unsigned long), @@ -7790,25 +7722,6 @@ void __init sched_init(void) * directly in rq->cfs (i.e init_task_group->se[] = NULL). */ init_tg_cfs_entry(&init_task_group, &rq->cfs, NULL, i, 1, NULL); -#elif defined CONFIG_USER_SCHED - root_task_group.shares = NICE_0_LOAD; - init_tg_cfs_entry(&root_task_group, &rq->cfs, NULL, i, 0, NULL); - /* - * In case of task-groups formed thr' the user id of tasks, - * init_task_group represents tasks belonging to root user. - * Hence it forms a sibling of all subsequent groups formed. - * In this case, init_task_group gets only a fraction of overall - * system cpu resource, based on the weight assigned to root - * user's cpu share (INIT_TASK_GROUP_LOAD). This is accomplished - * by letting tasks of init_task_group sit in a separate cfs_rq - * (init_tg_cfs_rq) and having one entity represent this group of - * tasks in rq->cfs (i.e init_task_group->se[] != NULL). - */ - init_tg_cfs_entry(&init_task_group, - &per_cpu(init_tg_cfs_rq, i), - &per_cpu(init_sched_entity, i), i, 1, - root_task_group.se[i]); - #endif #endif /* CONFIG_FAIR_GROUP_SCHED */ @@ -7817,12 +7730,6 @@ void __init sched_init(void) INIT_LIST_HEAD(&rq->leaf_rt_rq_list); #ifdef CONFIG_CGROUP_SCHED init_tg_rt_entry(&init_task_group, &rq->rt, NULL, i, 1, NULL); -#elif defined CONFIG_USER_SCHED - init_tg_rt_entry(&root_task_group, &rq->rt, NULL, i, 0, NULL); - init_tg_rt_entry(&init_task_group, - &per_cpu(init_rt_rq_var, i), - &per_cpu(init_sched_rt_entity, i), i, 1, - root_task_group.rt_se[i]); #endif #endif @@ -8218,7 +8125,7 @@ static inline void unregister_rt_sched_group(struct task_group *tg, int cpu) } #endif /* CONFIG_RT_GROUP_SCHED */ -#ifdef CONFIG_GROUP_SCHED +#ifdef CONFIG_CGROUP_SCHED static void free_sched_group(struct task_group *tg) { free_fair_sched_group(tg); @@ -8327,7 +8234,7 @@ void sched_move_task(struct task_struct *tsk) task_rq_unlock(rq, &flags); } -#endif /* CONFIG_GROUP_SCHED */ +#endif /* CONFIG_CGROUP_SCHED */ #ifdef CONFIG_FAIR_GROUP_SCHED static void __set_se_shares(struct sched_entity *se, unsigned long shares) @@ -8469,13 +8376,6 @@ static int tg_schedulable(struct task_group *tg, void *data) runtime = d->rt_runtime; } -#ifdef CONFIG_USER_SCHED - if (tg == &root_task_group) { - period = global_rt_period(); - runtime = global_rt_runtime(); - } -#endif - /* * Cannot have more runtime than the period. */ diff --git a/kernel/sys.c b/kernel/sys.c index 26a6b73a6b8..f75bf0936f4 100644 --- a/kernel/sys.c +++ b/kernel/sys.c @@ -569,11 +569,6 @@ static int set_user(struct cred *new) if (!new_user) return -EAGAIN; - if (!task_can_switch_user(new_user, current)) { - free_uid(new_user); - return -EINVAL; - } - if (atomic_read(&new_user->processes) >= current->signal->rlim[RLIMIT_NPROC].rlim_cur && new_user != INIT_USER) { diff --git a/kernel/user.c b/kernel/user.c index 46d0165ca70..766467b3bcb 100644 --- a/kernel/user.c +++ b/kernel/user.c @@ -56,9 +56,6 @@ struct user_struct root_user = { .sigpending = ATOMIC_INIT(0), .locked_shm = 0, .user_ns = &init_user_ns, -#ifdef CONFIG_USER_SCHED - .tg = &init_task_group, -#endif }; /* @@ -75,268 +72,6 @@ static void uid_hash_remove(struct user_struct *up) put_user_ns(up->user_ns); } -#ifdef CONFIG_USER_SCHED - -static void sched_destroy_user(struct user_struct *up) -{ - sched_destroy_group(up->tg); -} - -static int sched_create_user(struct user_struct *up) -{ - int rc = 0; - - up->tg = sched_create_group(&root_task_group); - if (IS_ERR(up->tg)) - rc = -ENOMEM; - - set_tg_uid(up); - - return rc; -} - -#else /* CONFIG_USER_SCHED */ - -static void sched_destroy_user(struct user_struct *up) { } -static int sched_create_user(struct user_struct *up) { return 0; } - -#endif /* CONFIG_USER_SCHED */ - -#if defined(CONFIG_USER_SCHED) && defined(CONFIG_SYSFS) - -static struct user_struct *uid_hash_find(uid_t uid, struct hlist_head *hashent) -{ - struct user_struct *user; - struct hlist_node *h; - - hlist_for_each_entry(user, h, hashent, uidhash_node) { - if (user->uid == uid) { - /* possibly resurrect an "almost deleted" object */ - if (atomic_inc_return(&user->__count) == 1) - cancel_delayed_work(&user->work); - return user; - } - } - - return NULL; -} - -static struct kset *uids_kset; /* represents the /sys/kernel/uids/ directory */ -static DEFINE_MUTEX(uids_mutex); - -static inline void uids_mutex_lock(void) -{ - mutex_lock(&uids_mutex); -} - -static inline void uids_mutex_unlock(void) -{ - mutex_unlock(&uids_mutex); -} - -/* uid directory attributes */ -#ifdef CONFIG_FAIR_GROUP_SCHED -static ssize_t cpu_shares_show(struct kobject *kobj, - struct kobj_attribute *attr, - char *buf) -{ - struct user_struct *up = container_of(kobj, struct user_struct, kobj); - - return sprintf(buf, "%lu\n", sched_group_shares(up->tg)); -} - -static ssize_t cpu_shares_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t size) -{ - struct user_struct *up = container_of(kobj, struct user_struct, kobj); - unsigned long shares; - int rc; - - sscanf(buf, "%lu", &shares); - - rc = sched_group_set_shares(up->tg, shares); - - return (rc ? rc : size); -} - -static struct kobj_attribute cpu_share_attr = - __ATTR(cpu_share, 0644, cpu_shares_show, cpu_shares_store); -#endif - -#ifdef CONFIG_RT_GROUP_SCHED -static ssize_t cpu_rt_runtime_show(struct kobject *kobj, - struct kobj_attribute *attr, - char *buf) -{ - struct user_struct *up = container_of(kobj, struct user_struct, kobj); - - return sprintf(buf, "%ld\n", sched_group_rt_runtime(up->tg)); -} - -static ssize_t cpu_rt_runtime_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t size) -{ - struct user_struct *up = container_of(kobj, struct user_struct, kobj); - unsigned long rt_runtime; - int rc; - - sscanf(buf, "%ld", &rt_runtime); - - rc = sched_group_set_rt_runtime(up->tg, rt_runtime); - - return (rc ? rc : size); -} - -static struct kobj_attribute cpu_rt_runtime_attr = - __ATTR(cpu_rt_runtime, 0644, cpu_rt_runtime_show, cpu_rt_runtime_store); - -static ssize_t cpu_rt_period_show(struct kobject *kobj, - struct kobj_attribute *attr, - char *buf) -{ - struct user_struct *up = container_of(kobj, struct user_struct, kobj); - - return sprintf(buf, "%lu\n", sched_group_rt_period(up->tg)); -} - -static ssize_t cpu_rt_period_store(struct kobject *kobj, - struct kobj_attribute *attr, - const char *buf, size_t size) -{ - struct user_struct *up = container_of(kobj, struct user_struct, kobj); - unsigned long rt_period; - int rc; - - sscanf(buf, "%lu", &rt_period); - - rc = sched_group_set_rt_period(up->tg, rt_period); - - return (rc ? rc : size); -} - -static struct kobj_attribute cpu_rt_period_attr = - __ATTR(cpu_rt_period, 0644, cpu_rt_period_show, cpu_rt_period_store); -#endif - -/* default attributes per uid directory */ -static struct attribute *uids_attributes[] = { -#ifdef CONFIG_FAIR_GROUP_SCHED - &cpu_share_attr.attr, -#endif -#ifdef CONFIG_RT_GROUP_SCHED - &cpu_rt_runtime_attr.attr, - &cpu_rt_period_attr.attr, -#endif - NULL -}; - -/* the lifetime of user_struct is not managed by the core (now) */ -static void uids_release(struct kobject *kobj) -{ - return; -} - -static struct kobj_type uids_ktype = { - .sysfs_ops = &kobj_sysfs_ops, - .default_attrs = uids_attributes, - .release = uids_release, -}; - -/* - * Create /sys/kernel/uids//cpu_share file for this user - * We do not create this file for users in a user namespace (until - * sysfs tagging is implemented). - * - * See Documentation/scheduler/sched-design-CFS.txt for ramifications. - */ -static int uids_user_create(struct user_struct *up) -{ - struct kobject *kobj = &up->kobj; - int error; - - memset(kobj, 0, sizeof(struct kobject)); - if (up->user_ns != &init_user_ns) - return 0; - kobj->kset = uids_kset; - error = kobject_init_and_add(kobj, &uids_ktype, NULL, "%d", up->uid); - if (error) { - kobject_put(kobj); - goto done; - } - - kobject_uevent(kobj, KOBJ_ADD); -done: - return error; -} - -/* create these entries in sysfs: - * "/sys/kernel/uids" directory - * "/sys/kernel/uids/0" directory (for root user) - * "/sys/kernel/uids/0/cpu_share" file (for root user) - */ -int __init uids_sysfs_init(void) -{ - uids_kset = kset_create_and_add("uids", NULL, kernel_kobj); - if (!uids_kset) - return -ENOMEM; - - return uids_user_create(&root_user); -} - -/* delayed work function to remove sysfs directory for a user and free up - * corresponding structures. - */ -static void cleanup_user_struct(struct work_struct *w) -{ - struct user_struct *up = container_of(w, struct user_struct, work.work); - unsigned long flags; - int remove_user = 0; - - /* Make uid_hash_remove() + sysfs_remove_file() + kobject_del() - * atomic. - */ - uids_mutex_lock(); - - spin_lock_irqsave(&uidhash_lock, flags); - if (atomic_read(&up->__count) == 0) { - uid_hash_remove(up); - remove_user = 1; - } - spin_unlock_irqrestore(&uidhash_lock, flags); - - if (!remove_user) - goto done; - - if (up->user_ns == &init_user_ns) { - kobject_uevent(&up->kobj, KOBJ_REMOVE); - kobject_del(&up->kobj); - kobject_put(&up->kobj); - } - - sched_destroy_user(up); - key_put(up->uid_keyring); - key_put(up->session_keyring); - kmem_cache_free(uid_cachep, up); - -done: - uids_mutex_unlock(); -} - -/* IRQs are disabled and uidhash_lock is held upon function entry. - * IRQ state (as stored in flags) is restored and uidhash_lock released - * upon function exit. - */ -static void free_user(struct user_struct *up, unsigned long flags) -{ - INIT_DELAYED_WORK(&up->work, cleanup_user_struct); - schedule_delayed_work(&up->work, msecs_to_jiffies(1000)); - spin_unlock_irqrestore(&uidhash_lock, flags); -} - -#else /* CONFIG_USER_SCHED && CONFIG_SYSFS */ - static struct user_struct *uid_hash_find(uid_t uid, struct hlist_head *hashent) { struct user_struct *user; @@ -352,11 +87,6 @@ static struct user_struct *uid_hash_find(uid_t uid, struct hlist_head *hashent) return NULL; } -int uids_sysfs_init(void) { return 0; } -static inline int uids_user_create(struct user_struct *up) { return 0; } -static inline void uids_mutex_lock(void) { } -static inline void uids_mutex_unlock(void) { } - /* IRQs are disabled and uidhash_lock is held upon function entry. * IRQ state (as stored in flags) is restored and uidhash_lock released * upon function exit. @@ -365,32 +95,11 @@ static void free_user(struct user_struct *up, unsigned long flags) { uid_hash_remove(up); spin_unlock_irqrestore(&uidhash_lock, flags); - sched_destroy_user(up); key_put(up->uid_keyring); key_put(up->session_keyring); kmem_cache_free(uid_cachep, up); } -#endif - -#if defined(CONFIG_RT_GROUP_SCHED) && defined(CONFIG_USER_SCHED) -/* - * We need to check if a setuid can take place. This function should be called - * before successfully completing the setuid. - */ -int task_can_switch_user(struct user_struct *up, struct task_struct *tsk) -{ - - return sched_rt_can_attach(up->tg, tsk); - -} -#else -int task_can_switch_user(struct user_struct *up, struct task_struct *tsk) -{ - return 1; -} -#endif - /* * Locate the user_struct for the passed UID. If found, take a ref on it. The * caller must undo that ref with free_uid(). @@ -431,8 +140,6 @@ struct user_struct *alloc_uid(struct user_namespace *ns, uid_t uid) /* Make uid_hash_find() + uids_user_create() + uid_hash_insert() * atomic. */ - uids_mutex_lock(); - spin_lock_irq(&uidhash_lock); up = uid_hash_find(uid, hashent); spin_unlock_irq(&uidhash_lock); @@ -445,14 +152,8 @@ struct user_struct *alloc_uid(struct user_namespace *ns, uid_t uid) new->uid = uid; atomic_set(&new->__count, 1); - if (sched_create_user(new) < 0) - goto out_free_user; - new->user_ns = get_user_ns(ns); - if (uids_user_create(new)) - goto out_destoy_sched; - /* * Before adding this, check whether we raced * on adding the same user already.. @@ -475,17 +176,11 @@ struct user_struct *alloc_uid(struct user_namespace *ns, uid_t uid) spin_unlock_irq(&uidhash_lock); } - uids_mutex_unlock(); - return up; -out_destoy_sched: - sched_destroy_user(new); put_user_ns(new->user_ns); -out_free_user: kmem_cache_free(uid_cachep, new); out_unlock: - uids_mutex_unlock(); return NULL; } -- cgit v1.2.3-70-g09d2 From 5f6c3de6a79820de124fa2bb1b77d43a09410e42 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 23 Jan 2010 22:19:29 +0100 Subject: ALSA: hda - Minor fixes for Compaq Presario F700 quirk Minor fixes for HP Compaq Presario F700 quirks with Cxt5051 codec: - changed the capture mixer elements to the standard name. - fixed the quirk name string without a space - sorted the quirk list - updated the documentation Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio-Models.txt | 1 + sound/pci/hda/patch_conexant.c | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio-Models.txt b/Documentation/sound/alsa/HD-Audio-Models.txt index e72cee9e2a7..cb46eb259d6 100644 --- a/Documentation/sound/alsa/HD-Audio-Models.txt +++ b/Documentation/sound/alsa/HD-Audio-Models.txt @@ -279,6 +279,7 @@ Conexant 5051 laptop Basic Laptop config (default) hp HP Spartan laptop hp-dv6736 HP dv6736 + hp-f700 HP Compaq Presario F700 lenovo-x200 Lenovo X200 laptop Conexant 5066 diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 685015a5329..084600e4082 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -1742,8 +1742,8 @@ static struct snd_kcontrol_new cxt5051_hp_dv6736_mixers[] = { }; static struct snd_kcontrol_new cxt5051_f700_mixers[] = { - HDA_CODEC_VOLUME("Mic Volume", 0x14, 0x01, HDA_INPUT), - HDA_CODEC_MUTE("Mic Switch", 0x14, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Capture Volume", 0x14, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("Capture Switch", 0x14, 0x01, HDA_INPUT), HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT), { .iface = SNDRV_CTL_ELEM_IFACE_MIXER, @@ -1901,17 +1901,17 @@ static const char *cxt5051_models[CXT5051_MODELS] = { [CXT5051_HP] = "hp", [CXT5051_HP_DV6736] = "hp-dv6736", [CXT5051_LENOVO_X200] = "lenovo-x200", - [CXT5051_F700] = "hp 700" + [CXT5051_F700] = "hp-700", }; static struct snd_pci_quirk cxt5051_cfg_tbl[] = { SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV6736", CXT5051_HP_DV6736), SND_PCI_QUIRK(0x103c, 0x360b, "Compaq Presario CQ60", CXT5051_HP), + SND_PCI_QUIRK(0x103c, 0x30ea, "Compaq Presario F700", CXT5051_F700), SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board", CXT5051_LAPTOP), SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP), SND_PCI_QUIRK(0x17aa, 0x20f2, "Lenovo X200", CXT5051_LENOVO_X200), - SND_PCI_QUIRK(0x103c, 0x30ea, "Compaq Presario F700", CXT5051_F700), {} }; -- cgit v1.2.3-70-g09d2 From faddaa5d1c0cd29629c9c7e7a9d41ecb3149a064 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Sat, 23 Jan 2010 22:31:36 +0100 Subject: ALSA: hda - Add support for Toshiba Satellite M300 Added the support for Toshiba Satellite M300 with Conexant 5051 codec. Since the laptop has no port C connection and the pin reports always the jack sense true, we need to ignore port-C unsol event. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio-Models.txt | 1 + sound/pci/hda/patch_conexant.c | 38 ++++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio-Models.txt b/Documentation/sound/alsa/HD-Audio-Models.txt index cb46eb259d6..8f06f20096f 100644 --- a/Documentation/sound/alsa/HD-Audio-Models.txt +++ b/Documentation/sound/alsa/HD-Audio-Models.txt @@ -281,6 +281,7 @@ Conexant 5051 hp-dv6736 HP dv6736 hp-f700 HP Compaq Presario F700 lenovo-x200 Lenovo X200 laptop + toshiba Toshiba Satellite M300 Conexant 5066 ============= diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 08c5b32dcd6..56dda9c7f89 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -46,6 +46,8 @@ #define CXT5051_PORTB_EVENT 0x38 #define CXT5051_PORTC_EVENT 0x39 +#define AUTO_MIC_PORTB (1 << 1) +#define AUTO_MIC_PORTC (1 << 2) struct conexant_jack { @@ -74,7 +76,7 @@ struct conexant_spec { */ unsigned int cur_eapd; unsigned int hp_present; - unsigned int no_auto_mic; + unsigned int auto_mic; unsigned int need_dac_fix; /* capture */ @@ -1626,7 +1628,7 @@ static void cxt5051_portb_automic(struct hda_codec *codec) struct conexant_spec *spec = codec->spec; unsigned int present; - if (spec->no_auto_mic) + if (!(spec->auto_mic & AUTO_MIC_PORTB)) return; present = snd_hda_jack_detect(codec, 0x17); snd_hda_codec_write(codec, 0x14, 0, @@ -1641,7 +1643,7 @@ static void cxt5051_portc_automic(struct hda_codec *codec) unsigned int present; hda_nid_t new_adc; - if (spec->no_auto_mic) + if (!(spec->auto_mic & AUTO_MIC_PORTC)) return; present = snd_hda_jack_detect(codec, 0x18); if (present) @@ -1757,6 +1759,24 @@ static struct snd_kcontrol_new cxt5051_f700_mixers[] = { {} }; +static struct snd_kcontrol_new cxt5051_toshiba_mixers[] = { + HDA_CODEC_VOLUME("Internal Mic Volume", 0x14, 0x00, HDA_INPUT), + HDA_CODEC_MUTE("Internal Mic Switch", 0x14, 0x00, HDA_INPUT), + HDA_CODEC_VOLUME("External Mic Volume", 0x14, 0x01, HDA_INPUT), + HDA_CODEC_MUTE("External Mic Switch", 0x14, 0x01, HDA_INPUT), + HDA_CODEC_VOLUME("Master Playback Volume", 0x10, 0x00, HDA_OUTPUT), + { + .iface = SNDRV_CTL_ELEM_IFACE_MIXER, + .name = "Master Playback Switch", + .info = cxt_eapd_info, + .get = cxt_eapd_get, + .put = cxt5051_hp_master_sw_put, + .private_value = 0x1a, + }, + + {} +}; + static struct hda_verb cxt5051_init_verbs[] = { /* Line in, Mic */ {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x03}, @@ -1893,6 +1913,7 @@ enum { CXT5051_HP_DV6736, /* HP without mic switch */ CXT5051_LENOVO_X200, /* Lenovo X200 laptop */ CXT5051_F700, /* HP Compaq Presario F700 */ + CXT5051_TOSHIBA, /* Toshiba M300 & co */ CXT5051_MODELS }; @@ -1902,12 +1923,14 @@ static const char *cxt5051_models[CXT5051_MODELS] = { [CXT5051_HP_DV6736] = "hp-dv6736", [CXT5051_LENOVO_X200] = "lenovo-x200", [CXT5051_F700] = "hp-700", + [CXT5051_TOSHIBA] = "toshiba", }; static struct snd_pci_quirk cxt5051_cfg_tbl[] = { SND_PCI_QUIRK(0x103c, 0x30cf, "HP DV6736", CXT5051_HP_DV6736), SND_PCI_QUIRK(0x103c, 0x360b, "Compaq Presario CQ60", CXT5051_HP), SND_PCI_QUIRK(0x103c, 0x30ea, "Compaq Presario F700", CXT5051_F700), + SND_PCI_QUIRK(0x1179, 0xff50, "Toshiba M30x", CXT5051_TOSHIBA), SND_PCI_QUIRK(0x14f1, 0x0101, "Conexant Reference board", CXT5051_LAPTOP), SND_PCI_QUIRK(0x14f1, 0x5051, "HP Spartan 1.1", CXT5051_HP), @@ -1950,6 +1973,7 @@ static int patch_cxt5051(struct hda_codec *codec) board_config = snd_hda_check_board_config(codec, CXT5051_MODELS, cxt5051_models, cxt5051_cfg_tbl); + spec->auto_mic = AUTO_MIC_PORTB | AUTO_MIC_PORTC; switch (board_config) { case CXT5051_HP: spec->mixers[0] = cxt5051_hp_mixers; @@ -1957,7 +1981,7 @@ static int patch_cxt5051(struct hda_codec *codec) case CXT5051_HP_DV6736: spec->init_verbs[0] = cxt5051_hp_dv6736_init_verbs; spec->mixers[0] = cxt5051_hp_dv6736_mixers; - spec->no_auto_mic = 1; + spec->auto_mic = 0; break; case CXT5051_LENOVO_X200: spec->init_verbs[0] = cxt5051_lenovo_x200_init_verbs; @@ -1965,7 +1989,11 @@ static int patch_cxt5051(struct hda_codec *codec) case CXT5051_F700: spec->init_verbs[0] = cxt5051_f700_init_verbs; spec->mixers[0] = cxt5051_f700_mixers; - spec->no_auto_mic = 1; + spec->auto_mic = 0; + break; + case CXT5051_TOSHIBA: + spec->mixers[0] = cxt5051_toshiba_mixers; + spec->auto_mic = AUTO_MIC_PORTB; break; } -- cgit v1.2.3-70-g09d2 From 9df5f74194871ebd0e51ef5ad2eca5084acaaaba Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Mon, 25 Jan 2010 11:42:20 -0600 Subject: mm: add coherence API for DMA to vmalloc/vmap areas On Virtually Indexed architectures (which don't do automatic alias resolution in their caches), we have to flush via the correct virtual address to prepare pages for DMA. On some architectures (like arm) we cannot prevent the CPU from doing data movein along the alias (and thus giving stale read data), so we not only have to introduce a flush API to push dirty cache lines out, but also an invalidate API to kill inconsistent cache lines that may have moved in before DMA changed the data Signed-off-by: James Bottomley --- Documentation/cachetlb.txt | 24 ++++++++++++++++++++++++ include/linux/highmem.h | 6 ++++++ 2 files changed, 30 insertions(+) (limited to 'Documentation') diff --git a/Documentation/cachetlb.txt b/Documentation/cachetlb.txt index da42ab414c4..b231414bb8b 100644 --- a/Documentation/cachetlb.txt +++ b/Documentation/cachetlb.txt @@ -377,3 +377,27 @@ maps this page at its virtual address. All the functionality of flush_icache_page can be implemented in flush_dcache_page and update_mmu_cache. In 2.7 the hope is to remove this interface completely. + +The final category of APIs is for I/O to deliberately aliased address +ranges inside the kernel. Such aliases are set up by use of the +vmap/vmalloc API. Since kernel I/O goes via physical pages, the I/O +subsystem assumes that the user mapping and kernel offset mapping are +the only aliases. This isn't true for vmap aliases, so anything in +the kernel trying to do I/O to vmap areas must manually manage +coherency. It must do this by flushing the vmap range before doing +I/O and invalidating it after the I/O returns. + + void flush_kernel_vmap_range(void *vaddr, int size) + flushes the kernel cache for a given virtual address range in + the vmap area. This is to make sure that any data the kernel + modified in the vmap range is made visible to the physical + page. The design is to make this area safe to perform I/O on. + Note that this API does *not* also flush the offset map alias + of the area. + + void invalidate_kernel_vmap_range(void *vaddr, int size) invalidates + the cache for a given virtual address range in the vmap area + which prevents the processor from making the cache stale by + speculatively reading data while the I/O was occurring to the + physical pages. This is only necessary for data reads into the + vmap area. diff --git a/include/linux/highmem.h b/include/linux/highmem.h index 211ff449726..adfe1013b2b 100644 --- a/include/linux/highmem.h +++ b/include/linux/highmem.h @@ -17,6 +17,12 @@ static inline void flush_anon_page(struct vm_area_struct *vma, struct page *page static inline void flush_kernel_dcache_page(struct page *page) { } +static inline void flush_kernel_vmap_range(void *vaddr, int size) +{ +} +static inline void invalidate_kernel_vmap_range(void *vaddr, int size) +{ +} #endif #include -- cgit v1.2.3-70-g09d2 From b09f3e78ee7bb69171411b75bd9e771fc7f24749 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Thu, 28 Jan 2010 00:01:53 +0100 Subject: ALSA: hda - Allow override more fields via patch loader Allow the override of vendor-id, subsystem-id, revision-id and chip name via patch loading. Updated the document, too. Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio.txt | 27 ++++++++++++++++++ sound/pci/hda/hda_hwdep.c | 53 +++++++++++++++++++++++++---------- 2 files changed, 65 insertions(+), 15 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio.txt b/Documentation/sound/alsa/HD-Audio.txt index 6325bec06a7..f4dd3bf99d1 100644 --- a/Documentation/sound/alsa/HD-Audio.txt +++ b/Documentation/sound/alsa/HD-Audio.txt @@ -452,6 +452,33 @@ Similarly, the lines after `[verb]` are parsed as `init_verbs` sysfs entries, and the lines after `[hint]` are parsed as `hints` sysfs entries, respectively. +Another example to override the codec vendor id from 0x12345678 to +0xdeadbeef is like below: +------------------------------------------------------------------------ + [codec] + 0x12345678 0xabcd1234 2 + + [vendor_id] + 0xdeadbeef +------------------------------------------------------------------------ + +In the similar way, you can override the codec subsystem_id via +`[subsystem_id]`, the revision id via `[revision_id]` line. +Also, the codec chip name can be rewritten via `[chip_name]` line. +------------------------------------------------------------------------ + [codec] + 0x12345678 0xabcd1234 2 + + [subsystem_id] + 0xffff1111 + + [revision_id] + 0x10 + + [chip_name] + My-own NEWS-0002 +------------------------------------------------------------------------ + The hd-audio driver reads the file via request_firmware(). Thus, a patch file has to be located on the appropriate firmware path, typically, /lib/firmware. For example, when you pass the option diff --git a/sound/pci/hda/hda_hwdep.c b/sound/pci/hda/hda_hwdep.c index b36919c0d36..a1fc83753cc 100644 --- a/sound/pci/hda/hda_hwdep.c +++ b/sound/pci/hda/hda_hwdep.c @@ -625,6 +625,10 @@ enum { LINE_MODE_PINCFG, LINE_MODE_VERB, LINE_MODE_HINT, + LINE_MODE_VENDOR_ID, + LINE_MODE_SUBSYSTEM_ID, + LINE_MODE_REVISION_ID, + LINE_MODE_CHIP_NAME, NUM_LINE_MODES, }; @@ -654,53 +658,71 @@ static void parse_codec_mode(char *buf, struct hda_bus *bus, } /* parse the contents after the other command tags, [pincfg], [verb], - * [hint] and [model] + * [vendor_id], [subsystem_id], [revision_id], [chip_name], [hint] and [model] * just pass to the sysfs helper (only when any codec was specified) */ static void parse_pincfg_mode(char *buf, struct hda_bus *bus, struct hda_codec **codecp) { - if (!*codecp) - return; parse_user_pin_configs(*codecp, buf); } static void parse_verb_mode(char *buf, struct hda_bus *bus, struct hda_codec **codecp) { - if (!*codecp) - return; parse_init_verbs(*codecp, buf); } static void parse_hint_mode(char *buf, struct hda_bus *bus, struct hda_codec **codecp) { - if (!*codecp) - return; parse_hints(*codecp, buf); } static void parse_model_mode(char *buf, struct hda_bus *bus, struct hda_codec **codecp) { - if (!*codecp) - return; kfree((*codecp)->modelname); (*codecp)->modelname = kstrdup(buf, GFP_KERNEL); } +static void parse_chip_name_mode(char *buf, struct hda_bus *bus, + struct hda_codec **codecp) +{ + kfree((*codecp)->chip_name); + (*codecp)->chip_name = kstrdup(buf, GFP_KERNEL); +} + +#define DEFINE_PARSE_ID_MODE(name) \ +static void parse_##name##_mode(char *buf, struct hda_bus *bus, \ + struct hda_codec **codecp) \ +{ \ + unsigned long val; \ + if (!strict_strtoul(buf, 0, &val)) \ + (*codecp)->name = val; \ +} + +DEFINE_PARSE_ID_MODE(vendor_id); +DEFINE_PARSE_ID_MODE(subsystem_id); +DEFINE_PARSE_ID_MODE(revision_id); + + struct hda_patch_item { const char *tag; void (*parser)(char *buf, struct hda_bus *bus, struct hda_codec **retc); + int need_codec; }; static struct hda_patch_item patch_items[NUM_LINE_MODES] = { - [LINE_MODE_CODEC] = { "[codec]", parse_codec_mode }, - [LINE_MODE_MODEL] = { "[model]", parse_model_mode }, - [LINE_MODE_VERB] = { "[verb]", parse_verb_mode }, - [LINE_MODE_PINCFG] = { "[pincfg]", parse_pincfg_mode }, - [LINE_MODE_HINT] = { "[hint]", parse_hint_mode }, + [LINE_MODE_CODEC] = { "[codec]", parse_codec_mode, 0 }, + [LINE_MODE_MODEL] = { "[model]", parse_model_mode, 1 }, + [LINE_MODE_VERB] = { "[verb]", parse_verb_mode, 1 }, + [LINE_MODE_PINCFG] = { "[pincfg]", parse_pincfg_mode, 1 }, + [LINE_MODE_HINT] = { "[hint]", parse_hint_mode, 1 }, + [LINE_MODE_VENDOR_ID] = { "[vendor_id]", parse_vendor_id_mode, 1 }, + [LINE_MODE_SUBSYSTEM_ID] = { "[subsystem_id]", parse_subsystem_id_mode, 1 }, + [LINE_MODE_REVISION_ID] = { "[revision_id]", parse_revision_id_mode, 1 }, + [LINE_MODE_CHIP_NAME] = { "[chip_name]", parse_chip_name_mode, 1 }, }; /* check the line starting with '[' -- change the parser mode accodingly */ @@ -783,7 +805,8 @@ int snd_hda_load_patch(struct hda_bus *bus, const char *patch) continue; if (*buf == '[') line_mode = parse_line_mode(buf, bus); - else if (patch_items[line_mode].parser) + else if (patch_items[line_mode].parser && + (codec || !patch_items[line_mode].need_codec)) patch_items[line_mode].parser(buf, bus, &codec); } release_firmware(fw); -- cgit v1.2.3-70-g09d2 From 488991e28e55b4fbca8067edf0259f69d1a6f92c Mon Sep 17 00:00:00 2001 From: "Alan D. Brunelle" Date: Fri, 29 Jan 2010 09:04:08 +0100 Subject: block: Added in stricter no merge semantics for block I/O Updated 'nomerges' tunable to accept a value of '2' - indicating that _no_ merges at all are to be attempted (not even the simple one-hit cache). The following table illustrates the additional benefit - 5 minute runs of a random I/O load were applied to a dozen devices on a 16-way x86_64 system. nomerges Throughput %System Improvement (tput / %sys) -------- ------------ ----------- ------------------------- 0 12.45 MB/sec 0.669365609 1 12.50 MB/sec 0.641519199 0.40% / 2.71% 2 12.52 MB/sec 0.639849750 0.56% / 2.96% Signed-off-by: Alan D. Brunelle Signed-off-by: Jens Axboe --- Documentation/ABI/testing/sysfs-block | 14 ++++++++++++++ Documentation/block/queue-sysfs.txt | 10 +++++----- block/blk-sysfs.c | 11 +++++++---- block/elevator.c | 11 ++++++++++- include/linux/blkdev.h | 3 +++ 5 files changed, 39 insertions(+), 10 deletions(-) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-block b/Documentation/ABI/testing/sysfs-block index d2f90334bb9..4873c759d53 100644 --- a/Documentation/ABI/testing/sysfs-block +++ b/Documentation/ABI/testing/sysfs-block @@ -128,3 +128,17 @@ Description: preferred request size for workloads where sustained throughput is desired. If no optimal I/O size is reported this file contains 0. + +What: /sys/block//queue/nomerges +Date: January 2010 +Contact: +Description: + Standard I/O elevator operations include attempts to + merge contiguous I/Os. For known random I/O loads these + attempts will always fail and result in extra cycles + being spent in the kernel. This allows one to turn off + this behavior on one of two ways: When set to 1, complex + merge checks are disabled, but the simple one-shot merges + with the previous I/O request are enabled. When set to 2, + all merge tries are disabled. The default value is 0 - + which enables all types of merge tries. diff --git a/Documentation/block/queue-sysfs.txt b/Documentation/block/queue-sysfs.txt index e164403f60e..f65274081c8 100644 --- a/Documentation/block/queue-sysfs.txt +++ b/Documentation/block/queue-sysfs.txt @@ -25,11 +25,11 @@ size allowed by the hardware. nomerges (RW) ------------- -This enables the user to disable the lookup logic involved with IO merging -requests in the block layer. Merging may still occur through a direct -1-hit cache, since that comes for (almost) free. The IO scheduler will not -waste cycles doing tree/hash lookups for merges if nomerges is 1. Defaults -to 0, enabling all merges. +This enables the user to disable the lookup logic involved with IO +merging requests in the block layer. By default (0) all merges are +enabled. When set to 1 only simple one-hit merges will be tried. When +set to 2 no merge algorithms will be tried (including one-hit or more +complex tree/hash lookups). nr_requests (RW) ---------------- diff --git a/block/blk-sysfs.c b/block/blk-sysfs.c index 8606c9543fd..e85442415db 100644 --- a/block/blk-sysfs.c +++ b/block/blk-sysfs.c @@ -189,7 +189,8 @@ static ssize_t queue_nonrot_store(struct request_queue *q, const char *page, static ssize_t queue_nomerges_show(struct request_queue *q, char *page) { - return queue_var_show(blk_queue_nomerges(q), page); + return queue_var_show((blk_queue_nomerges(q) << 1) | + blk_queue_noxmerges(q), page); } static ssize_t queue_nomerges_store(struct request_queue *q, const char *page, @@ -199,10 +200,12 @@ static ssize_t queue_nomerges_store(struct request_queue *q, const char *page, ssize_t ret = queue_var_store(&nm, page, count); spin_lock_irq(q->queue_lock); - if (nm) + queue_flag_clear(QUEUE_FLAG_NOMERGES, q); + queue_flag_clear(QUEUE_FLAG_NOXMERGES, q); + if (nm == 2) queue_flag_set(QUEUE_FLAG_NOMERGES, q); - else - queue_flag_clear(QUEUE_FLAG_NOMERGES, q); + else if (nm) + queue_flag_set(QUEUE_FLAG_NOXMERGES, q); spin_unlock_irq(q->queue_lock); return ret; diff --git a/block/elevator.c b/block/elevator.c index 9ad5ccc4c5e..ee3a883840f 100644 --- a/block/elevator.c +++ b/block/elevator.c @@ -473,6 +473,15 @@ int elv_merge(struct request_queue *q, struct request **req, struct bio *bio) struct request *__rq; int ret; + /* + * Levels of merges: + * nomerges: No merges at all attempted + * noxmerges: Only simple one-hit cache try + * merges: All merge tries attempted + */ + if (blk_queue_nomerges(q)) + return ELEVATOR_NO_MERGE; + /* * First try one-hit cache. */ @@ -484,7 +493,7 @@ int elv_merge(struct request_queue *q, struct request **req, struct bio *bio) } } - if (blk_queue_nomerges(q)) + if (blk_queue_noxmerges(q)) return ELEVATOR_NO_MERGE; /* diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index ffb13ad3571..f71f5c58620 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -463,6 +463,7 @@ struct request_queue #define QUEUE_FLAG_IO_STAT 15 /* do IO stats */ #define QUEUE_FLAG_CQ 16 /* hardware does queuing */ #define QUEUE_FLAG_DISCARD 17 /* supports DISCARD */ +#define QUEUE_FLAG_NOXMERGES 18 /* No extended merges */ #define QUEUE_FLAG_DEFAULT ((1 << QUEUE_FLAG_IO_STAT) | \ (1 << QUEUE_FLAG_CLUSTER) | \ @@ -589,6 +590,8 @@ enum { #define blk_queue_queuing(q) test_bit(QUEUE_FLAG_CQ, &(q)->queue_flags) #define blk_queue_stopped(q) test_bit(QUEUE_FLAG_STOPPED, &(q)->queue_flags) #define blk_queue_nomerges(q) test_bit(QUEUE_FLAG_NOMERGES, &(q)->queue_flags) +#define blk_queue_noxmerges(q) \ + test_bit(QUEUE_FLAG_NOXMERGES, &(q)->queue_flags) #define blk_queue_nonrot(q) test_bit(QUEUE_FLAG_NONROT, &(q)->queue_flags) #define blk_queue_io_stat(q) test_bit(QUEUE_FLAG_IO_STAT, &(q)->queue_flags) #define blk_queue_flushing(q) ((q)->ordseq) -- cgit v1.2.3-70-g09d2 From b0f83b280fcfa9cdd66b748f0c12fb26ce69e391 Mon Sep 17 00:00:00 2001 From: Brian Haley Date: Thu, 4 Feb 2010 13:36:50 -0800 Subject: doc: document IPv6 parameters Update documentation to describe IPv6 parameters. Reported by . Signed-off-by: Brian Haley Signed-off-by: David S. Miller --- Documentation/kernel-parameters.txt | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 736d4560288..3ca7f8f5652 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -54,6 +54,7 @@ parameter is applicable: IMA Integrity measurement architecture is enabled. IOSCHED More than one I/O scheduler is enabled. IP_PNP IP DHCP, BOOTP, or RARP is enabled. + IPV6 IPv6 support is enabled. ISAPNP ISA PnP code is enabled. ISDN Appropriate ISDN support is enabled. JOY Appropriate joystick support is enabled. @@ -347,6 +348,9 @@ and is between 256 and 4096 characters. It is defined in the file Change the amount of debugging information output when initialising the APIC and IO-APIC components. + autoconf= [IPV6] + See Documentation/networking/ipv6.txt. + show_lapic= [APIC,X86] Advanced Programmable Interrupt Controller Limit apic dumping. The parameter defines the maximal number of local apics being dumped. Also it is possible @@ -629,6 +633,12 @@ and is between 256 and 4096 characters. It is defined in the file See drivers/char/README.epca and Documentation/serial/digiepca.txt. + disable= [IPV6] + See Documentation/networking/ipv6.txt. + + disable_ipv6= [IPV6] + See Documentation/networking/ipv6.txt. + disable_mtrr_cleanup [X86] The kernel tries to adjust MTRR layout from continuous to discrete, to make X server driver able to add WB -- cgit v1.2.3-70-g09d2 From 889b8f964f2f226b7cd5a0a515109e3d8d9d1613 Mon Sep 17 00:00:00 2001 From: "David S. Miller" Date: Fri, 5 Feb 2010 16:29:48 -0800 Subject: packet: Kill CONFIG_PACKET_MMAP. Early on this was an experimental facility that few people other than Alexey Kuznetsov played with. Now it's a pretty fundamental thing and as people add more features to AF_PACKET sockets this config options creates ifdef spaghetti. So kill it off. Signed-off-by: David S. Miller --- Documentation/networking/packet_mmap.txt | 8 ++++---- net/packet/Kconfig | 10 ---------- net/packet/af_packet.c | 29 ----------------------------- 3 files changed, 4 insertions(+), 43 deletions(-) (limited to 'Documentation') diff --git a/Documentation/networking/packet_mmap.txt b/Documentation/networking/packet_mmap.txt index a22fd85e379..09ab0d29032 100644 --- a/Documentation/networking/packet_mmap.txt +++ b/Documentation/networking/packet_mmap.txt @@ -2,7 +2,7 @@ + ABSTRACT -------------------------------------------------------------------------------- -This file documents the CONFIG_PACKET_MMAP option available with the PACKET +This file documents the mmap() facility available with the PACKET socket interface on 2.4 and 2.6 kernels. This type of sockets is used for capture network traffic with utilities like tcpdump or any other that needs raw access to network interface. @@ -44,7 +44,7 @@ enabled. For transmission, check the MTU (Maximum Transmission Unit) used and supported by devices of your network. -------------------------------------------------------------------------------- -+ How to use CONFIG_PACKET_MMAP to improve capture process ++ How to use mmap() to improve capture process -------------------------------------------------------------------------------- From the user standpoint, you should use the higher level libpcap library, which @@ -64,7 +64,7 @@ the low level details or want to improve libpcap by including PACKET_MMAP support. -------------------------------------------------------------------------------- -+ How to use CONFIG_PACKET_MMAP directly to improve capture process ++ How to use mmap() directly to improve capture process -------------------------------------------------------------------------------- From the system calls stand point, the use of PACKET_MMAP involves @@ -105,7 +105,7 @@ also the mapping of the circular buffer in the user process and the use of this buffer. -------------------------------------------------------------------------------- -+ How to use CONFIG_PACKET_MMAP directly to improve transmission process ++ How to use mmap() directly to improve transmission process -------------------------------------------------------------------------------- Transmission process is similar to capture as shown below. diff --git a/net/packet/Kconfig b/net/packet/Kconfig index 34ff93ff894..0060e3b396b 100644 --- a/net/packet/Kconfig +++ b/net/packet/Kconfig @@ -14,13 +14,3 @@ config PACKET be called af_packet. If unsure, say Y. - -config PACKET_MMAP - bool "Packet socket: mmapped IO" - depends on PACKET - help - If you say Y here, the Packet protocol driver will use an IO - mechanism that results in faster communication. - - If unsure, say N. - diff --git a/net/packet/af_packet.c b/net/packet/af_packet.c index 178e2937bba..6ecb426bc0c 100644 --- a/net/packet/af_packet.c +++ b/net/packet/af_packet.c @@ -157,7 +157,6 @@ struct packet_mreq_max { unsigned char mr_address[MAX_ADDR_LEN]; }; -#ifdef CONFIG_PACKET_MMAP static int packet_set_ring(struct sock *sk, struct tpacket_req *req, int closing, int tx_ring); @@ -177,7 +176,6 @@ struct packet_ring_buffer { struct packet_sock; static int tpacket_snd(struct packet_sock *po, struct msghdr *msg); -#endif static void packet_flush_mclist(struct sock *sk); @@ -185,11 +183,9 @@ struct packet_sock { /* struct sock has to be the first member of packet_sock */ struct sock sk; struct tpacket_stats stats; -#ifdef CONFIG_PACKET_MMAP struct packet_ring_buffer rx_ring; struct packet_ring_buffer tx_ring; int copy_thresh; -#endif spinlock_t bind_lock; struct mutex pg_vec_lock; unsigned int running:1, /* prot_hook is attached*/ @@ -199,13 +195,11 @@ struct packet_sock { int ifindex; /* bound device */ __be16 num; struct packet_mclist *mclist; -#ifdef CONFIG_PACKET_MMAP atomic_t mapped; enum tpacket_versions tp_version; unsigned int tp_hdrlen; unsigned int tp_reserve; unsigned int tp_loss:1; -#endif struct packet_type prot_hook ____cacheline_aligned_in_smp; }; @@ -219,8 +213,6 @@ struct packet_skb_cb { #define PACKET_SKB_CB(__skb) ((struct packet_skb_cb *)((__skb)->cb)) -#ifdef CONFIG_PACKET_MMAP - static void __packet_set_status(struct packet_sock *po, void *frame, int status) { union { @@ -315,8 +307,6 @@ static inline void packet_increment_head(struct packet_ring_buffer *buff) buff->head = buff->head != buff->frame_max ? buff->head+1 : 0; } -#endif - static inline struct packet_sock *pkt_sk(struct sock *sk) { return (struct packet_sock *)sk; @@ -640,7 +630,6 @@ drop: return 0; } -#ifdef CONFIG_PACKET_MMAP static int tpacket_rcv(struct sk_buff *skb, struct net_device *dev, struct packet_type *pt, struct net_device *orig_dev) { @@ -1056,7 +1045,6 @@ out: mutex_unlock(&po->pg_vec_lock); return err; } -#endif static inline struct sk_buff *packet_alloc_skb(struct sock *sk, size_t prepad, size_t reserve, size_t len, @@ -1248,13 +1236,11 @@ out: static int packet_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t len) { -#ifdef CONFIG_PACKET_MMAP struct sock *sk = sock->sk; struct packet_sock *po = pkt_sk(sk); if (po->tx_ring.pg_vec) return tpacket_snd(po, msg); else -#endif return packet_snd(sock, msg, len); } @@ -1268,9 +1254,7 @@ static int packet_release(struct socket *sock) struct sock *sk = sock->sk; struct packet_sock *po; struct net *net; -#ifdef CONFIG_PACKET_MMAP struct tpacket_req req; -#endif if (!sk) return 0; @@ -1299,7 +1283,6 @@ static int packet_release(struct socket *sock) packet_flush_mclist(sk); -#ifdef CONFIG_PACKET_MMAP memset(&req, 0, sizeof(req)); if (po->rx_ring.pg_vec) @@ -1307,7 +1290,6 @@ static int packet_release(struct socket *sock) if (po->tx_ring.pg_vec) packet_set_ring(sk, &req, 1, 1); -#endif /* * Now the socket is dead. No more input will appear. @@ -1872,7 +1854,6 @@ packet_setsockopt(struct socket *sock, int level, int optname, char __user *optv return ret; } -#ifdef CONFIG_PACKET_MMAP case PACKET_RX_RING: case PACKET_TX_RING: { @@ -1943,7 +1924,6 @@ packet_setsockopt(struct socket *sock, int level, int optname, char __user *optv po->tp_loss = !!val; return 0; } -#endif case PACKET_AUXDATA: { int val; @@ -2041,7 +2021,6 @@ static int packet_getsockopt(struct socket *sock, int level, int optname, data = &val; break; -#ifdef CONFIG_PACKET_MMAP case PACKET_VERSION: if (len > sizeof(int)) len = sizeof(int); @@ -2077,7 +2056,6 @@ static int packet_getsockopt(struct socket *sock, int level, int optname, val = po->tp_loss; data = &val; break; -#endif default: return -ENOPROTOOPT; } @@ -2197,11 +2175,6 @@ static int packet_ioctl(struct socket *sock, unsigned int cmd, return 0; } -#ifndef CONFIG_PACKET_MMAP -#define packet_mmap sock_no_mmap -#define packet_poll datagram_poll -#else - static unsigned int packet_poll(struct file *file, struct socket *sock, poll_table *wait) { @@ -2483,8 +2456,6 @@ out: mutex_unlock(&po->pg_vec_lock); return err; } -#endif - static const struct proto_ops packet_ops_spkt = { .family = PF_PACKET, -- cgit v1.2.3-70-g09d2 From 349e6b7289f8a3d3d5d3b859e00b41f27d1211df Mon Sep 17 00:00:00 2001 From: Kalle Valo Date: Sun, 7 Feb 2010 10:22:01 +0200 Subject: mac80211: remove get_tx_stats() driver op get_tx_stats() driver operation is not currently used anywhere in mac80211 and there are no plans to use it in the not-so-near future. So it can go without anyone missing it. Signed-off-by: Kalle Valo Acked-by: Johannes Berg Signed-off-by: John W. Linville --- Documentation/DocBook/mac80211.tmpl | 1 - include/net/mac80211.h | 22 ---------------------- net/mac80211/driver-ops.h | 8 -------- net/mac80211/driver-trace.h | 23 ----------------------- 4 files changed, 54 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/mac80211.tmpl b/Documentation/DocBook/mac80211.tmpl index 971d1c0c83e..affb15a344a 100644 --- a/Documentation/DocBook/mac80211.tmpl +++ b/Documentation/DocBook/mac80211.tmpl @@ -234,7 +234,6 @@ usage should require reading the full document. Multiple queues and QoS support TBD !Finclude/net/mac80211.h ieee80211_tx_queue_params -!Finclude/net/mac80211.h ieee80211_tx_queue_stats diff --git a/include/net/mac80211.h b/include/net/mac80211.h index a19fac35259..414d774028b 100644 --- a/include/net/mac80211.h +++ b/include/net/mac80211.h @@ -117,19 +117,6 @@ struct ieee80211_tx_queue_params { bool uapsd; }; -/** - * struct ieee80211_tx_queue_stats - transmit queue statistics - * - * @len: number of packets in queue - * @limit: queue length limit - * @count: number of frames sent - */ -struct ieee80211_tx_queue_stats { - unsigned int len; - unsigned int limit; - unsigned int count; -}; - struct ieee80211_low_level_stats { unsigned int dot11ACKFailureCount; unsigned int dot11RTSFailureCount; @@ -1548,13 +1535,6 @@ enum ieee80211_ampdu_mlme_action { * Returns a negative error code on failure. * The callback can sleep. * - * @get_tx_stats: Get statistics of the current TX queue status. This is used - * to get number of currently queued packets (queue length), maximum queue - * size (limit), and total number of packets sent using each TX queue - * (count). The 'stats' pointer points to an array that has hw->queues - * items. - * The callback must be atomic. - * * @get_tsf: Get the current TSF timer value from firmware/hardware. Currently, * this is only used for IBSS mode BSSID merging and debugging. Is not a * required function. @@ -1648,8 +1628,6 @@ struct ieee80211_ops { enum sta_notify_cmd, struct ieee80211_sta *sta); int (*conf_tx)(struct ieee80211_hw *hw, u16 queue, const struct ieee80211_tx_queue_params *params); - int (*get_tx_stats)(struct ieee80211_hw *hw, - struct ieee80211_tx_queue_stats *stats); u64 (*get_tsf)(struct ieee80211_hw *hw); void (*set_tsf)(struct ieee80211_hw *hw, u64 tsf); void (*reset_tsf)(struct ieee80211_hw *hw); diff --git a/net/mac80211/driver-ops.h b/net/mac80211/driver-ops.h index 855e85b5506..c3d844093a2 100644 --- a/net/mac80211/driver-ops.h +++ b/net/mac80211/driver-ops.h @@ -290,14 +290,6 @@ static inline int drv_conf_tx(struct ieee80211_local *local, u16 queue, return ret; } -static inline int drv_get_tx_stats(struct ieee80211_local *local, - struct ieee80211_tx_queue_stats *stats) -{ - int ret = local->ops->get_tx_stats(&local->hw, stats); - trace_drv_get_tx_stats(local, stats, ret); - return ret; -} - static inline u64 drv_get_tsf(struct ieee80211_local *local) { u64 ret = -1ULL; diff --git a/net/mac80211/driver-trace.h b/net/mac80211/driver-trace.h index c984910bf27..41baf730a5c 100644 --- a/net/mac80211/driver-trace.h +++ b/net/mac80211/driver-trace.h @@ -630,29 +630,6 @@ TRACE_EVENT(drv_conf_tx, ) ); -TRACE_EVENT(drv_get_tx_stats, - TP_PROTO(struct ieee80211_local *local, - struct ieee80211_tx_queue_stats *stats, - int ret), - - TP_ARGS(local, stats, ret), - - TP_STRUCT__entry( - LOCAL_ENTRY - __field(int, ret) - ), - - TP_fast_assign( - LOCAL_ASSIGN; - __entry->ret = ret; - ), - - TP_printk( - LOCAL_PR_FMT " ret:%d", - LOCAL_PR_ARG, __entry->ret - ) -); - TRACE_EVENT(drv_get_tsf, TP_PROTO(struct ieee80211_local *local, u64 ret), -- cgit v1.2.3-70-g09d2 From 74e7e725e4766a2045708d274d4d07cd4fae8adc Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Sun, 7 Feb 2010 23:10:05 -0800 Subject: Input: mark {corgi,spitz,tosa}kbd drivers deprecated Provided that now keyboards on these devices are fully supported by generic GPIO based matrix keypad driver, mark these hardcoded and difficult to maintain drivers as deprecated. Signed-off-by: Eric Miao Acked-by: Pavel Machek Signed-off-by: Dmitry Torokhov --- Documentation/feature-removal-schedule.txt | 11 +++++++++++ drivers/input/keyboard/Kconfig | 18 ++++++++++++------ 2 files changed, 23 insertions(+), 6 deletions(-) (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 0a46833c1b7..f8dc4d92e11 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -542,3 +542,14 @@ Why: Duplicate functionality with the gspca_zc3xx driver, zc0301 only sensors) wich are also supported by the gspca_zc3xx driver (which supports 53 USB-ID's in total) Who: Hans de Goede + +---------------------------- + +What: corgikbd, spitzkbd, tosakbd driver +When: 2.6.35 +Files: drivers/input/keyboard/{corgi,spitz,tosa}kbd.c +Why: We now have a generic GPIO based matrix keyboard driver that + are fully capable of handling all the keys on these devices. + The original drivers manipulate the GPIO registers directly + and so are difficult to maintain. +Who: Eric Miao diff --git a/drivers/input/keyboard/Kconfig b/drivers/input/keyboard/Kconfig index 616a3916d18..1ad9435d30a 100644 --- a/drivers/input/keyboard/Kconfig +++ b/drivers/input/keyboard/Kconfig @@ -144,13 +144,15 @@ config KEYBOARD_BFIN module will be called bf54x-keys. config KEYBOARD_CORGI - tristate "Corgi keyboard" + tristate "Corgi keyboard (deprecated)" depends on PXA_SHARPSL - default y help Say Y here to enable the keyboard on the Sharp Zaurus SL-C7xx series of PDAs. + This driver is now deprecated, use generic GPIO based matrix + keyboard driver instead. + To compile this driver as a module, choose M here: the module will be called corgikbd. @@ -338,13 +340,15 @@ config KEYBOARD_PXA930_ROTARY module will be called pxa930_rotary. config KEYBOARD_SPITZ - tristate "Spitz keyboard" + tristate "Spitz keyboard (deprecated)" depends on PXA_SHARPSL - default y help Say Y here to enable the keyboard on the Sharp Zaurus SL-C1000, SL-C3000 and Sl-C3100 series of PDAs. + This driver is now deprecated, use generic GPIO based matrix + keyboard driver instead. + To compile this driver as a module, choose M here: the module will be called spitzkbd. @@ -411,12 +415,14 @@ config KEYBOARD_TWL4030 module will be called twl4030_keypad. config KEYBOARD_TOSA - tristate "Tosa keyboard" + tristate "Tosa keyboard (deprecated)" depends on MACH_TOSA - default y help Say Y here to enable the keyboard on the Sharp Zaurus SL-6000x (Tosa) + This driver is now deprecated, use generic GPIO based matrix + keyboard driver instead. + To compile this driver as a module, choose M here: the module will be called tosakbd. -- cgit v1.2.3-70-g09d2 From b63de38591605916fff5d483e0bedc65dfe3d395 Mon Sep 17 00:00:00 2001 From: Eric Miao Date: Sun, 7 Feb 2010 23:10:05 -0800 Subject: Input: schedule corgi_ssp and corgi_ts to be removed Signed-off-by: Eric Miao Acked-by: Richard Purdie Acked-by: Pavel Machek Signed-off-by: Dmitry Torokhov --- Documentation/feature-removal-schedule.txt | 12 ++++++++++++ drivers/input/touchscreen/Kconfig | 1 - 2 files changed, 12 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index f8dc4d92e11..54d2d779554 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -553,3 +553,15 @@ Why: We now have a generic GPIO based matrix keyboard driver that The original drivers manipulate the GPIO registers directly and so are difficult to maintain. Who: Eric Miao + +---------------------------- + +What: corgi_ssp and corgi_ts driver +When: 2.6.35 +Files: arch/arm/mach-pxa/corgi_ssp.c, drivers/input/touchscreen/corgi_ts.c +Why: The corgi touchscreen is now deprecated in favour of the generic + ads7846.c driver. The noise reduction technique used in corgi_ts.c, + that's to wait till vsync before ADC sampling, is also integrated into + ads7846 driver now. Provided that the original driver is not generic + and is difficult to maintain, it will be removed later. +Who: Eric Miao diff --git a/drivers/input/touchscreen/Kconfig b/drivers/input/touchscreen/Kconfig index a1e2d845f68..6457e060ae4 100644 --- a/drivers/input/touchscreen/Kconfig +++ b/drivers/input/touchscreen/Kconfig @@ -90,7 +90,6 @@ config TOUCHSCREEN_CORGI tristate "SharpSL (Corgi and Spitz series) touchscreen driver (DEPRECATED)" depends on PXA_SHARPSL select CORGI_SSP_DEPRECATED - default y help Say Y here to enable the driver for the touchscreen on the Sharp SL-C7xx and SL-Cxx00 series of PDAs. -- cgit v1.2.3-70-g09d2 From 69a6a0b38a139ccceef32222108caca8a9b0b795 Mon Sep 17 00:00:00 2001 From: Gerrit Renker Date: Sun, 7 Feb 2010 20:20:28 +0000 Subject: dccp: allow probing of CCID-array length This fixes a problem in the DCCP getsockopt() API: currently there is no way for a user to a priori know the number of built-in CCIDs, other than trying DCCP_SOCKOPT_AVAILABLE_CCIDS in a loop, incrementing the option length until EINVAL is no longer returned. This patch truncates the array to the user-provided length. No copy is made when the length is <= 0. Due to the length restriction in do_dccp_getsockopt() to sizeof(int), the minimum array length remains 4, which is a reasonable default (only 3 CCIDs, CCID-2..4, are currently defined). Signed-off-by: Gerrit Renker Signed-off-by: David S. Miller --- Documentation/networking/dccp.txt | 6 ++++-- net/dccp/ccid.c | 9 ++++----- 2 files changed, 8 insertions(+), 7 deletions(-) (limited to 'Documentation') diff --git a/Documentation/networking/dccp.txt b/Documentation/networking/dccp.txt index b132e4a3cf0..a62fdf7a6bf 100644 --- a/Documentation/networking/dccp.txt +++ b/Documentation/networking/dccp.txt @@ -58,8 +58,10 @@ DCCP_SOCKOPT_GET_CUR_MPS is read-only and retrieves the current maximum packet size (application payload size) in bytes, see RFC 4340, section 14. DCCP_SOCKOPT_AVAILABLE_CCIDS is also read-only and returns the list of CCIDs -supported by the endpoint (see include/linux/dccp.h for symbolic constants). -The caller needs to provide a sufficiently large (> 2) array of type uint8_t. +supported by the endpoint. The option value is an array of type uint8_t whose +size is passed as option length. The minimum array size is 4 elements, the +value returned in the optlen argument always reflects the true number of +built-in CCIDs. DCCP_SOCKOPT_CCID is write-only and sets both the TX and RX CCIDs at the same time, combining the operation of the next two socket options. This option is diff --git a/net/dccp/ccid.c b/net/dccp/ccid.c index ff16e9df196..49d27c556be 100644 --- a/net/dccp/ccid.c +++ b/net/dccp/ccid.c @@ -63,14 +63,13 @@ int ccid_getsockopt_builtin_ccids(struct sock *sk, int len, u8 *ccid_array, array_len; int err = 0; - if (len < ARRAY_SIZE(ccids)) - return -EINVAL; - if (ccid_get_builtin_ccids(&ccid_array, &array_len)) return -ENOBUFS; - if (put_user(array_len, optlen) || - copy_to_user(optval, ccid_array, array_len)) + if (put_user(array_len, optlen)) + err = -EFAULT; + else if (len > 0 && copy_to_user(optval, ccid_array, + len > array_len ? array_len : len)) err = -EFAULT; kfree(ccid_array); -- cgit v1.2.3-70-g09d2 From cfd3d8dcf7b4fc783db0806ac3936a7b44735bf7 Mon Sep 17 00:00:00 2001 From: Greg Alexander Date: Sat, 13 Feb 2010 02:02:25 -0500 Subject: ALSA: hda - Add support for Lenovo IdeaPad U150 Add patch for the Conexant 5066 HDA codec to support the Lenovo IdeaPad U150 Signed-off-by: Greg Alexander Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio-Models.txt | 1 + sound/pci/hda/patch_conexant.c | 130 ++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio-Models.txt b/Documentation/sound/alsa/HD-Audio-Models.txt index 8f06f20096f..0c7ebef6289 100644 --- a/Documentation/sound/alsa/HD-Audio-Models.txt +++ b/Documentation/sound/alsa/HD-Audio-Models.txt @@ -288,6 +288,7 @@ Conexant 5066 laptop Basic Laptop config (default) dell-laptop Dell laptops olpc-xo-1_5 OLPC XO 1.5 + ideapad Lenovo IdeaPad U150 STAC9200 ======== diff --git a/sound/pci/hda/patch_conexant.c b/sound/pci/hda/patch_conexant.c index 745e3599214..194a28c5499 100644 --- a/sound/pci/hda/patch_conexant.c +++ b/sound/pci/hda/patch_conexant.c @@ -113,7 +113,8 @@ struct conexant_spec { unsigned int dell_automute; unsigned int port_d_mode; - unsigned int dell_vostro; + unsigned int dell_vostro:1; + unsigned int ideapad:1; unsigned int ext_mic_present; unsigned int recording; @@ -2167,6 +2168,34 @@ static void cxt5066_vostro_automic(struct hda_codec *codec) } } +/* toggle input of built-in digital mic and mic jack appropriately */ +static void cxt5066_ideapad_automic(struct hda_codec *codec) +{ + unsigned int present; + + struct hda_verb ext_mic_present[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 0}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {} + }; + static struct hda_verb ext_mic_absent[] = { + {0x14, AC_VERB_SET_CONNECT_SEL, 2}, + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, 0}, + {} + }; + + present = snd_hda_jack_detect(codec, 0x1b); + if (present) { + snd_printdd("CXT5066: external microphone detected\n"); + snd_hda_sequence_write(codec, ext_mic_present); + } else { + snd_printdd("CXT5066: external microphone absent\n"); + snd_hda_sequence_write(codec, ext_mic_absent); + } +} + /* mute internal speaker if HP is plugged */ static void cxt5066_hp_automute(struct hda_codec *codec) { @@ -2216,6 +2245,20 @@ static void cxt5066_vostro_event(struct hda_codec *codec, unsigned int res) } } +/* unsolicited event for jack sensing */ +static void cxt5066_ideapad_event(struct hda_codec *codec, unsigned int res) +{ + snd_printdd("CXT5066_ideapad: unsol event %x (%x)\n", res, res >> 26); + switch (res >> 26) { + case CONEXANT_HP_EVENT: + cxt5066_hp_automute(codec); + break; + case CONEXANT_MIC_EVENT: + cxt5066_ideapad_automic(codec); + break; + } +} + static const struct hda_input_mux cxt5066_analog_mic_boost = { .num_items = 5, .items = { @@ -2227,13 +2270,21 @@ static const struct hda_input_mux cxt5066_analog_mic_boost = { }, }; -static int cxt5066_set_mic_boost(struct hda_codec *codec) +static void cxt5066_set_mic_boost(struct hda_codec *codec) { struct conexant_spec *spec = codec->spec; - return snd_hda_codec_write_cache(codec, 0x17, 0, + snd_hda_codec_write_cache(codec, 0x17, 0, AC_VERB_SET_AMP_GAIN_MUTE, AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_OUTPUT | cxt5066_analog_mic_boost.items[spec->mic_boost].index); + if (spec->ideapad) { + /* adjust the internal mic as well...it is not through 0x17 */ + snd_hda_codec_write_cache(codec, 0x23, 0, + AC_VERB_SET_AMP_GAIN_MUTE, + AC_AMP_SET_RIGHT | AC_AMP_SET_LEFT | AC_AMP_SET_INPUT | + cxt5066_analog_mic_boost. + items[spec->mic_boost].index); + } } static int cxt5066_mic_boost_mux_enum_info(struct snd_kcontrol *kcontrol, @@ -2664,6 +2715,56 @@ static struct hda_verb cxt5066_init_verbs_vostro[] = { { } /* end */ }; +static struct hda_verb cxt5066_init_verbs_ideapad[] = { + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port B */ + {0x1b, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_VREF80}, /* Port C */ + {0x1e, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port F */ + {0x1d, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* Port E */ + + /* Speakers */ + {0x1f, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x1f, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + + /* HP, Amp */ + {0x19, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x19, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + + {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_HP}, + {0x1c, AC_VERB_SET_CONNECT_SEL, 0x00}, /* DAC1 */ + + /* DAC1 */ + {0x10, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + + /* Node 14 connections: 0x17 0x18 0x23 0x24 0x27 */ + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(0) | 0x50}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(2) | 0x50}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + {0x14, AC_VERB_SET_CONNECT_SEL, 2}, /* default to internal mic */ + + /* Audio input selector */ + {0x17, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE | 0x2}, + {0x17, AC_VERB_SET_CONNECT_SEL, 1}, /* route ext mic */ + + /* SPDIF route: PCM */ + {0x20, AC_VERB_SET_CONNECT_SEL, 0x0}, + {0x22, AC_VERB_SET_CONNECT_SEL, 0x0}, + + {0x20, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x22, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + + /* internal microphone */ + {0x23, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, /* enable int mic */ + + /* EAPD */ + {0x1d, AC_VERB_SET_EAPD_BTLENABLE, 0x2}, /* default on */ + + {0x19, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_HP_EVENT}, + {0x1b, AC_VERB_SET_UNSOLICITED_ENABLE, AC_USRSP_EN | CONEXANT_MIC_EVENT}, + { } /* end */ +}; + static struct hda_verb cxt5066_init_verbs_portd_lo[] = { {0x1c, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, { } /* end */ @@ -2680,6 +2781,8 @@ static int cxt5066_init(struct hda_codec *codec) cxt5066_hp_automute(codec); if (spec->dell_vostro) cxt5066_vostro_automic(codec); + else if (spec->ideapad) + cxt5066_ideapad_automic(codec); } cxt5066_set_mic_boost(codec); return 0; @@ -2705,6 +2808,7 @@ enum { CXT5066_DELL_LAPTOP, /* Dell Laptop */ CXT5066_OLPC_XO_1_5, /* OLPC XO 1.5 */ CXT5066_DELL_VOSTO, /* Dell Vostro 1015i */ + CXT5066_IDEAPAD, /* Lenovo IdeaPad U150 */ CXT5066_MODELS }; @@ -2712,7 +2816,8 @@ static const char *cxt5066_models[CXT5066_MODELS] = { [CXT5066_LAPTOP] = "laptop", [CXT5066_DELL_LAPTOP] = "dell-laptop", [CXT5066_OLPC_XO_1_5] = "olpc-xo-1_5", - [CXT5066_DELL_VOSTO] = "dell-vostro" + [CXT5066_DELL_VOSTO] = "dell-vostro", + [CXT5066_IDEAPAD] = "ideapad", }; static struct snd_pci_quirk cxt5066_cfg_tbl[] = { @@ -2722,6 +2827,7 @@ static struct snd_pci_quirk cxt5066_cfg_tbl[] = { CXT5066_DELL_LAPTOP), SND_PCI_QUIRK(0x152d, 0x0833, "OLPC XO-1.5", CXT5066_OLPC_XO_1_5), SND_PCI_QUIRK(0x1028, 0x0402, "Dell Vostro", CXT5066_DELL_VOSTO), + SND_PCI_QUIRK(0x17aa, 0x3a0d, "ideapad", CXT5066_IDEAPAD), {} }; @@ -2810,6 +2916,22 @@ static int patch_cxt5066(struct hda_codec *codec) /* no S/PDIF out */ spec->multiout.dig_out_nid = 0; + /* input source automatically selected */ + spec->input_mux = NULL; + break; + case CXT5066_IDEAPAD: + codec->patch_ops.init = cxt5066_init; + codec->patch_ops.unsol_event = cxt5066_ideapad_event; + spec->mixers[spec->num_mixers++] = cxt5066_mixer_master; + spec->mixers[spec->num_mixers++] = cxt5066_mixers; + spec->init_verbs[0] = cxt5066_init_verbs_ideapad; + spec->port_d_mode = 0; + spec->ideapad = 1; + spec->mic_boost = 2; /* default 20dB gain */ + + /* no S/PDIF out */ + spec->multiout.dig_out_nid = 0; + /* input source automatically selected */ spec->input_mux = NULL; break; -- cgit v1.2.3-70-g09d2 From 18fe1cad889fe6c1acbf4dfc576e82a13cf1a30c Mon Sep 17 00:00:00 2001 From: Fenkart/Bostandzhyan Date: Sun, 7 Feb 2010 21:48:38 +0100 Subject: ARM: 5930/1: Add PKMAP area description to memory.txt. Also adapts delimiters of neighbouring modules area. Tested-by: H Hartley Sweeten Signed-off-by: Andreas Fenkart Signed-off-by: Russell King --- Documentation/arm/memory.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/arm/memory.txt b/Documentation/arm/memory.txt index 9d58c7c5edd..eb0fae18ffb 100644 --- a/Documentation/arm/memory.txt +++ b/Documentation/arm/memory.txt @@ -59,7 +59,11 @@ PAGE_OFFSET high_memory-1 Kernel direct-mapped RAM region. This maps the platforms RAM, and typically maps all platform RAM in a 1:1 relationship. -TASK_SIZE PAGE_OFFSET-1 Kernel module space +PKMAP_BASE PAGE_OFFSET-1 Permanent kernel mappings + One way of mapping HIGHMEM pages into kernel + space. + +MODULES_VADDR MODULES_END-1 Kernel module space Kernel modules inserted via insmod are placed here using dynamic mappings. -- cgit v1.2.3-70-g09d2 From 8df5bb34defd685fe86f60746bbf3d47d1c6f033 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 15 Feb 2010 13:43:30 -0800 Subject: x86, numa: Add fixed node size option for numa emulation numa=fake=N specifies the number of fake nodes, N, to partition the system into and then allocates them by interleaving over physical nodes. This requires knowledge of the system capacity when attempting to allocate nodes of a certain size: either very large nodes to benchmark scalability of code that operates on individual nodes, or very small nodes to find bugs in the VM. This patch introduces numa=fake=[MG] so it is possible to specify the size of each node to allocate. When used, nodes of the size specified will be allocated and interleaved over the set of physical nodes. FAKE_NODE_MIN_SIZE was also moved to the more-appropriate include/asm/numa_64.h. Signed-off-by: David Rientjes LKML-Reference: Signed-off-by: H. Peter Anvin --- Documentation/x86/x86_64/boot-options.txt | 4 + arch/x86/include/asm/mmzone_64.h | 6 -- arch/x86/include/asm/numa_64.h | 5 ++ arch/x86/mm/numa_64.c | 117 ++++++++++++++++++++++++++++-- 4 files changed, 118 insertions(+), 14 deletions(-) (limited to 'Documentation') diff --git a/Documentation/x86/x86_64/boot-options.txt b/Documentation/x86/x86_64/boot-options.txt index 29a6ff8bc7d..01150c64aa7 100644 --- a/Documentation/x86/x86_64/boot-options.txt +++ b/Documentation/x86/x86_64/boot-options.txt @@ -166,6 +166,10 @@ NUMA numa=noacpi Don't parse the SRAT table for NUMA setup + numa=fake=[MG] + If given as a memory unit, fills all system RAM with nodes of + size interleaved over physical nodes. + numa=fake=CMDLINE If a number, fakes CMDLINE nodes and ignores NUMA setup of the actual machine. Otherwise, system memory is configured diff --git a/arch/x86/include/asm/mmzone_64.h b/arch/x86/include/asm/mmzone_64.h index a29f48c2a32..288b96f815a 100644 --- a/arch/x86/include/asm/mmzone_64.h +++ b/arch/x86/include/asm/mmzone_64.h @@ -39,11 +39,5 @@ static inline __attribute__((pure)) int phys_to_nid(unsigned long addr) #define node_start_pfn(nid) (NODE_DATA(nid)->node_start_pfn) #define node_end_pfn(nid) (NODE_DATA(nid)->node_start_pfn + \ NODE_DATA(nid)->node_spanned_pages) - -#ifdef CONFIG_NUMA_EMU -#define FAKE_NODE_MIN_SIZE (64 * 1024 * 1024) -#define FAKE_NODE_MIN_HASH_MASK (~(FAKE_NODE_MIN_SIZE - 1UL)) -#endif - #endif #endif /* _ASM_X86_MMZONE_64_H */ diff --git a/arch/x86/include/asm/numa_64.h b/arch/x86/include/asm/numa_64.h index c4ae822e415..823e070e7c2 100644 --- a/arch/x86/include/asm/numa_64.h +++ b/arch/x86/include/asm/numa_64.h @@ -36,6 +36,11 @@ extern void __cpuinit numa_set_node(int cpu, int node); extern void __cpuinit numa_clear_node(int cpu); extern void __cpuinit numa_add_cpu(int cpu); extern void __cpuinit numa_remove_cpu(int cpu); + +#ifdef CONFIG_NUMA_EMU +#define FAKE_NODE_MIN_SIZE ((u64)64 << 20) +#define FAKE_NODE_MIN_HASH_MASK (~(FAKE_NODE_MIN_SIZE - 1UL)) +#endif /* CONFIG_NUMA_EMU */ #else static inline void init_cpu_to_node(void) { } static inline void numa_set_node(int cpu, int node) { } diff --git a/arch/x86/mm/numa_64.c b/arch/x86/mm/numa_64.c index 2ecbe0ca0df..c47c78ba3ac 100644 --- a/arch/x86/mm/numa_64.c +++ b/arch/x86/mm/numa_64.c @@ -501,6 +501,102 @@ static int __init split_nodes_interleave(u64 addr, u64 max_addr, return ret; } +/* + * Returns the end address of a node so that there is at least `size' amount of + * non-reserved memory or `max_addr' is reached. + */ +static u64 __init find_end_of_node(u64 start, u64 max_addr, u64 size) +{ + u64 end = start + size; + + while (end - start - e820_hole_size(start, end) < size) { + end += FAKE_NODE_MIN_SIZE; + if (end > max_addr) { + end = max_addr; + break; + } + } + return end; +} + +/* + * Sets up fake nodes of `size' interleaved over physical nodes ranging from + * `addr' to `max_addr'. The return value is the number of nodes allocated. + */ +static int __init split_nodes_size_interleave(u64 addr, u64 max_addr, u64 size) +{ + nodemask_t physnode_mask = NODE_MASK_NONE; + u64 min_size; + int ret = 0; + int i; + + if (!size) + return -1; + /* + * The limit on emulated nodes is MAX_NUMNODES, so the size per node is + * increased accordingly if the requested size is too small. This + * creates a uniform distribution of node sizes across the entire + * machine (but not necessarily over physical nodes). + */ + min_size = (max_addr - addr - e820_hole_size(addr, max_addr)) / + MAX_NUMNODES; + min_size = max(min_size, FAKE_NODE_MIN_SIZE); + if ((min_size & FAKE_NODE_MIN_HASH_MASK) < min_size) + min_size = (min_size + FAKE_NODE_MIN_SIZE) & + FAKE_NODE_MIN_HASH_MASK; + if (size < min_size) { + pr_err("Fake node size %LuMB too small, increasing to %LuMB\n", + size >> 20, min_size >> 20); + size = min_size; + } + size &= FAKE_NODE_MIN_HASH_MASK; + + for (i = 0; i < MAX_NUMNODES; i++) + if (physnodes[i].start != physnodes[i].end) + node_set(i, physnode_mask); + /* + * Fill physical nodes with fake nodes of size until there is no memory + * left on any of them. + */ + while (nodes_weight(physnode_mask)) { + for_each_node_mask(i, physnode_mask) { + u64 dma32_end = MAX_DMA32_PFN << PAGE_SHIFT; + u64 end; + + end = find_end_of_node(physnodes[i].start, + physnodes[i].end, size); + /* + * If there won't be at least FAKE_NODE_MIN_SIZE of + * non-reserved memory in ZONE_DMA32 for the next node, + * this one must extend to the boundary. + */ + if (end < dma32_end && dma32_end - end - + e820_hole_size(end, dma32_end) < FAKE_NODE_MIN_SIZE) + end = dma32_end; + + /* + * If there won't be enough non-reserved memory for the + * next node, this one must extend to the end of the + * physical node. + */ + if (physnodes[i].end - end - + e820_hole_size(end, physnodes[i].end) < size) + end = physnodes[i].end; + + /* + * Setup the fake node that will be allocated as bootmem + * later. If setup_node_range() returns non-zero, there + * is no more memory available on this physical node. + */ + if (setup_node_range(ret++, &physnodes[i].start, + end - physnodes[i].start, + physnodes[i].end) < 0) + node_clear(i, physnode_mask); + } + } + return ret; +} + /* * Splits num_nodes nodes up equally starting at node_start. The return value * is the number of nodes split up and addr is adjusted to be at the end of the @@ -546,14 +642,7 @@ static int __init split_nodes_equally(u64 *addr, u64 max_addr, int node_start, if (i == num_nodes + node_start - 1) end = max_addr; else - while (end - *addr - e820_hole_size(*addr, end) < - size) { - end += FAKE_NODE_MIN_SIZE; - if (end > max_addr) { - end = max_addr; - break; - } - } + end = find_end_of_node(*addr, max_addr, size); if (setup_node_range(i, addr, end - *addr, max_addr) < 0) break; } @@ -588,6 +677,18 @@ static int __init numa_emulation(unsigned long start_pfn, int num_phys_nodes; num_phys_nodes = setup_physnodes(addr, max_addr, acpi, k8); + /* + * If the numa=fake command-line contains a 'M' or 'G', it represents + * the fixed node size. + */ + if (strchr(cmdline, 'M') || strchr(cmdline, 'G')) { + size = memparse(cmdline, &cmdline); + num_nodes = split_nodes_size_interleave(addr, max_addr, size); + if (num_nodes < 0) + return num_nodes; + goto out; + } + /* * If the numa=fake command-line is just a single number N, split the * system RAM into N fake nodes. -- cgit v1.2.3-70-g09d2 From ca2107c9d6cf44fb915402d6f12b9d9ff3925cd7 Mon Sep 17 00:00:00 2001 From: David Rientjes Date: Mon, 15 Feb 2010 13:43:33 -0800 Subject: x86, numa: Remove configurable node size support for numa emulation Now that numa=fake=[MG] is implemented, it is possible to remove configurable node size support. The command-line parsing was already broken (numa=fake=*128, for example, would not work) and since fake nodes are now interleaved over physical nodes, this support is no longer required. Signed-off-by: David Rientjes LKML-Reference: Signed-off-by: H. Peter Anvin --- Documentation/x86/x86_64/boot-options.txt | 16 +-- arch/x86/mm/numa_64.c | 160 +++--------------------------- 2 files changed, 16 insertions(+), 160 deletions(-) (limited to 'Documentation') diff --git a/Documentation/x86/x86_64/boot-options.txt b/Documentation/x86/x86_64/boot-options.txt index 01150c64aa7..7fbbaf85f5b 100644 --- a/Documentation/x86/x86_64/boot-options.txt +++ b/Documentation/x86/x86_64/boot-options.txt @@ -170,19 +170,9 @@ NUMA If given as a memory unit, fills all system RAM with nodes of size interleaved over physical nodes. - numa=fake=CMDLINE - If a number, fakes CMDLINE nodes and ignores NUMA setup of the - actual machine. Otherwise, system memory is configured - depending on the sizes and coefficients listed. For example: - numa=fake=2*512,1024,4*256,*128 - gives two 512M nodes, a 1024M node, four 256M nodes, and the - rest split into 128M chunks. If the last character of CMDLINE - is a *, the remaining memory is divided up equally among its - coefficient: - numa=fake=2*512,2* - gives two 512M nodes and the rest split into two nodes. - Otherwise, the remaining system RAM is allocated to an - additional node. + numa=fake= + If given as an integer, fills all system RAM with N fake nodes + interleaved over physical nodes. ACPI diff --git a/arch/x86/mm/numa_64.c b/arch/x86/mm/numa_64.c index c47c78ba3ac..3307ea8bd43 100644 --- a/arch/x86/mm/numa_64.c +++ b/arch/x86/mm/numa_64.c @@ -597,73 +597,6 @@ static int __init split_nodes_size_interleave(u64 addr, u64 max_addr, u64 size) return ret; } -/* - * Splits num_nodes nodes up equally starting at node_start. The return value - * is the number of nodes split up and addr is adjusted to be at the end of the - * last node allocated. - */ -static int __init split_nodes_equally(u64 *addr, u64 max_addr, int node_start, - int num_nodes) -{ - unsigned int big; - u64 size; - int i; - - if (num_nodes <= 0) - return -1; - if (num_nodes > MAX_NUMNODES) - num_nodes = MAX_NUMNODES; - size = (max_addr - *addr - e820_hole_size(*addr, max_addr)) / - num_nodes; - /* - * Calculate the number of big nodes that can be allocated as a result - * of consolidating the leftovers. - */ - big = ((size & ~FAKE_NODE_MIN_HASH_MASK) * num_nodes) / - FAKE_NODE_MIN_SIZE; - - /* Round down to nearest FAKE_NODE_MIN_SIZE. */ - size &= FAKE_NODE_MIN_HASH_MASK; - if (!size) { - printk(KERN_ERR "Not enough memory for each node. " - "NUMA emulation disabled.\n"); - return -1; - } - - for (i = node_start; i < num_nodes + node_start; i++) { - u64 end = *addr + size; - - if (i < big) - end += FAKE_NODE_MIN_SIZE; - /* - * The final node can have the remaining system RAM. Other - * nodes receive roughly the same amount of available pages. - */ - if (i == num_nodes + node_start - 1) - end = max_addr; - else - end = find_end_of_node(*addr, max_addr, size); - if (setup_node_range(i, addr, end - *addr, max_addr) < 0) - break; - } - return i - node_start + 1; -} - -/* - * Splits the remaining system RAM into chunks of size. The remaining memory is - * always assigned to a final node and can be asymmetric. Returns the number of - * nodes split. - */ -static int __init split_nodes_by_size(u64 *addr, u64 max_addr, int node_start, - u64 size) -{ - int i = node_start; - size = (size << 20) & FAKE_NODE_MIN_HASH_MASK; - while (!setup_node_range(i++, addr, size, max_addr)) - ; - return i - node_start; -} - /* * Sets up the system RAM area from start_pfn to last_pfn according to the * numa=fake command-line option. @@ -671,99 +604,32 @@ static int __init split_nodes_by_size(u64 *addr, u64 max_addr, int node_start, static int __init numa_emulation(unsigned long start_pfn, unsigned long last_pfn, int acpi, int k8) { - u64 size, addr = start_pfn << PAGE_SHIFT; + u64 addr = start_pfn << PAGE_SHIFT; u64 max_addr = last_pfn << PAGE_SHIFT; - int num_nodes = 0, num = 0, coeff_flag, coeff = -1, i; int num_phys_nodes; + int num_nodes; + int i; num_phys_nodes = setup_physnodes(addr, max_addr, acpi, k8); /* * If the numa=fake command-line contains a 'M' or 'G', it represents - * the fixed node size. + * the fixed node size. Otherwise, if it is just a single number N, + * split the system RAM into N fake nodes. */ if (strchr(cmdline, 'M') || strchr(cmdline, 'G')) { + u64 size; + size = memparse(cmdline, &cmdline); num_nodes = split_nodes_size_interleave(addr, max_addr, size); - if (num_nodes < 0) - return num_nodes; - goto out; - } + } else { + unsigned long n; - /* - * If the numa=fake command-line is just a single number N, split the - * system RAM into N fake nodes. - */ - if (!strchr(cmdline, '*') && !strchr(cmdline, ',')) { - long n = simple_strtol(cmdline, NULL, 0); - - num_nodes = split_nodes_interleave(addr, max_addr, - num_phys_nodes, n); - if (num_nodes < 0) - return num_nodes; - goto out; + n = simple_strtoul(cmdline, NULL, 0); + num_nodes = split_nodes_interleave(addr, max_addr, num_phys_nodes, n); } - /* Parse the command line. */ - for (coeff_flag = 0; ; cmdline++) { - if (*cmdline && isdigit(*cmdline)) { - num = num * 10 + *cmdline - '0'; - continue; - } - if (*cmdline == '*') { - if (num > 0) - coeff = num; - coeff_flag = 1; - } - if (!*cmdline || *cmdline == ',') { - if (!coeff_flag) - coeff = 1; - /* - * Round down to the nearest FAKE_NODE_MIN_SIZE. - * Command-line coefficients are in megabytes. - */ - size = ((u64)num << 20) & FAKE_NODE_MIN_HASH_MASK; - if (size) - for (i = 0; i < coeff; i++, num_nodes++) - if (setup_node_range(num_nodes, &addr, - size, max_addr) < 0) - goto done; - if (!*cmdline) - break; - coeff_flag = 0; - coeff = -1; - } - num = 0; - } -done: - if (!num_nodes) - return -1; - /* Fill remainder of system RAM, if appropriate. */ - if (addr < max_addr) { - if (coeff_flag && coeff < 0) { - /* Split remaining nodes into num-sized chunks */ - num_nodes += split_nodes_by_size(&addr, max_addr, - num_nodes, num); - goto out; - } - switch (*(cmdline - 1)) { - case '*': - /* Split remaining nodes into coeff chunks */ - if (coeff <= 0) - break; - num_nodes += split_nodes_equally(&addr, max_addr, - num_nodes, coeff); - break; - case ',': - /* Do not allocate remaining system RAM */ - break; - default: - /* Give one final node */ - setup_node_range(num_nodes, &addr, max_addr - addr, - max_addr); - num_nodes++; - } - } -out: + if (num_nodes < 0) + return num_nodes; memnode_shift = compute_hash_shift(nodes, num_nodes, NULL); if (memnode_shift < 0) { memnode_shift = 0; -- cgit v1.2.3-70-g09d2 From 79e95f4717767e9ddc4dbec5d778f0c9970eed34 Mon Sep 17 00:00:00 2001 From: Jan Kiszka Date: Mon, 8 Feb 2010 10:12:44 +0000 Subject: CAPI: Schedule capifs for removal With dynamic TTY nodes and the help of udev, we no longer need this special filesystem. Schedule it for removal in one year from now. As a last duty to this feature, move its help to right option so that users can read the rationale. Signed-off-by: Jan Kiszka Signed-off-by: David S. Miller --- Documentation/feature-removal-schedule.txt | 10 ++++++++++ drivers/isdn/capi/Kconfig | 13 +++++++------ 2 files changed, 17 insertions(+), 6 deletions(-) (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 2f93ac06c41..721a2aa80a1 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -521,3 +521,13 @@ Why: Duplicate functionality with the gspca_zc3xx driver, zc0301 only sensors) wich are also supported by the gspca_zc3xx driver (which supports 53 USB-ID's in total) Who: Hans de Goede + +---------------------------- + +What: capifs +When: February 2011 +Files: drivers/isdn/capi/capifs.* +Why: udev fully replaces this special file system that only contains CAPI + NCCI TTY device nodes. User space (pppdcapiplugin) works without + noticing the difference. +Who: Jan Kiszka diff --git a/drivers/isdn/capi/Kconfig b/drivers/isdn/capi/Kconfig index b2a04755c96..d607d465786 100644 --- a/drivers/isdn/capi/Kconfig +++ b/drivers/isdn/capi/Kconfig @@ -35,18 +35,19 @@ config ISDN_CAPI_CAPI20 Y/M here. config ISDN_CAPI_CAPIFS_BOOL - bool "CAPI2.0 filesystem support" + bool "CAPI2.0 filesystem support (DEPRECATED)" depends on ISDN_CAPI_MIDDLEWARE && ISDN_CAPI_CAPI20 + help + This option provides a special file system, similar to /dev/pts with + device nodes for the special ttys established by using the + middleware extension above. + You no longer need this, udev fully replaces it. This feature is + scheduled for removal. config ISDN_CAPI_CAPIFS tristate depends on ISDN_CAPI_CAPIFS_BOOL default ISDN_CAPI_CAPI20 - help - This option provides a special file system, similar to /dev/pts with - device nodes for the special ttys established by using the - middleware extension above. If you want to use pppd with - pppdcapiplugin to dial up to your ISP, say Y here. config ISDN_CAPI_CAPIDRV tristate "CAPI2.0 capidrv interface support" -- cgit v1.2.3-70-g09d2 From 3162d92dfb79a0b5fc03380b8819fa5f870ebf1e Mon Sep 17 00:00:00 2001 From: Dave Kleikamp Date: Mon, 8 Feb 2010 11:51:05 +0000 Subject: powerpc: Extended ptrace interface powerpc: Extended ptrace interface From: Dave Kleikamp Based on patches originally written by Torez Smith. Add a new extended ptrace interface so that user-space has a single interface for powerpc, without having to know the specific layout of the debug registers. Implement: PPC_PTRACE_GETHWDEBUGINFO PPC_PTRACE_SETHWDEBUG PPC_PTRACE_DELHWDEBUG Signed-off-by: Dave Kleikamp Acked-by: David Gibson Cc: Torez Smith Cc: Benjamin Herrenschmidt Cc: Josh Boyer Cc: Kumar Gala Cc: Sergio Durigan Junior Cc: Thiago Jung Bauermann Cc: linuxppc-dev list Signed-off-by: Benjamin Herrenschmidt --- Documentation/powerpc/ptrace.txt | 134 ++++++++++++++++++++++++++++++++++++++ arch/powerpc/include/asm/ptrace.h | 77 ++++++++++++++++++++++ arch/powerpc/kernel/ptrace.c | 90 +++++++++++++++++++++++++ 3 files changed, 301 insertions(+) create mode 100644 Documentation/powerpc/ptrace.txt (limited to 'Documentation') diff --git a/Documentation/powerpc/ptrace.txt b/Documentation/powerpc/ptrace.txt new file mode 100644 index 00000000000..f4a5499b7bc --- /dev/null +++ b/Documentation/powerpc/ptrace.txt @@ -0,0 +1,134 @@ +GDB intends to support the following hardware debug features of BookE +processors: + +4 hardware breakpoints (IAC) +2 hardware watchpoints (read, write and read-write) (DAC) +2 value conditions for the hardware watchpoints (DVC) + +For that, we need to extend ptrace so that GDB can query and set these +resources. Since we're extending, we're trying to create an interface +that's extendable and that covers both BookE and server processors, so +that GDB doesn't need to special-case each of them. We added the +following 3 new ptrace requests. + +1. PTRACE_PPC_GETHWDEBUGINFO + +Query for GDB to discover the hardware debug features. The main info to +be returned here is the minimum alignment for the hardware watchpoints. +BookE processors don't have restrictions here, but server processors have +an 8-byte alignment restriction for hardware watchpoints. We'd like to avoid +adding special cases to GDB based on what it sees in AUXV. + +Since we're at it, we added other useful info that the kernel can return to +GDB: this query will return the number of hardware breakpoints, hardware +watchpoints and whether it supports a range of addresses and a condition. +The query will fill the following structure provided by the requesting process: + +struct ppc_debug_info { + unit32_t version; + unit32_t num_instruction_bps; + unit32_t num_data_bps; + unit32_t num_condition_regs; + unit32_t data_bp_alignment; + unit32_t sizeof_condition; /* size of the DVC register */ + uint64_t features; /* bitmask of the individual flags */ +}; + +features will have bits indicating whether there is support for: + +#define PPC_DEBUG_FEATURE_INSN_BP_RANGE 0x1 +#define PPC_DEBUG_FEATURE_INSN_BP_MASK 0x2 +#define PPC_DEBUG_FEATURE_DATA_BP_RANGE 0x4 +#define PPC_DEBUG_FEATURE_DATA_BP_MASK 0x8 + +2. PTRACE_SETHWDEBUG + +Sets a hardware breakpoint or watchpoint, according to the provided structure: + +struct ppc_hw_breakpoint { + uint32_t version; +#define PPC_BREAKPOINT_TRIGGER_EXECUTE 0x1 +#define PPC_BREAKPOINT_TRIGGER_READ 0x2 +#define PPC_BREAKPOINT_TRIGGER_WRITE 0x4 + uint32_t trigger_type; /* only some combinations allowed */ +#define PPC_BREAKPOINT_MODE_EXACT 0x0 +#define PPC_BREAKPOINT_MODE_RANGE_INCLUSIVE 0x1 +#define PPC_BREAKPOINT_MODE_RANGE_EXCLUSIVE 0x2 +#define PPC_BREAKPOINT_MODE_MASK 0x3 + uint32_t addr_mode; /* address match mode */ + +#define PPC_BREAKPOINT_CONDITION_MODE 0x3 +#define PPC_BREAKPOINT_CONDITION_NONE 0x0 +#define PPC_BREAKPOINT_CONDITION_AND 0x1 +#define PPC_BREAKPOINT_CONDITION_EXACT 0x1 /* different name for the same thing as above */ +#define PPC_BREAKPOINT_CONDITION_OR 0x2 +#define PPC_BREAKPOINT_CONDITION_AND_OR 0x3 +#define PPC_BREAKPOINT_CONDITION_BE_ALL 0x00ff0000 /* byte enable bits */ +#define PPC_BREAKPOINT_CONDITION_BE(n) (1<<((n)+16)) + uint32_t condition_mode; /* break/watchpoint condition flags */ + + uint64_t addr; + uint64_t addr2; + uint64_t condition_value; +}; + +A request specifies one event, not necessarily just one register to be set. +For instance, if the request is for a watchpoint with a condition, both the +DAC and DVC registers will be set in the same request. + +With this GDB can ask for all kinds of hardware breakpoints and watchpoints +that the BookE supports. COMEFROM breakpoints available in server processors +are not contemplated, but that is out of the scope of this work. + +ptrace will return an integer (handle) uniquely identifying the breakpoint or +watchpoint just created. This integer will be used in the PTRACE_DELHWDEBUG +request to ask for its removal. Return -ENOSPC if the requested breakpoint +can't be allocated on the registers. + +Some examples of using the structure to: + +- set a breakpoint in the first breakpoint register + + p.version = PPC_DEBUG_CURRENT_VERSION; + p.trigger_type = PPC_BREAKPOINT_TRIGGER_EXECUTE; + p.addr_mode = PPC_BREAKPOINT_MODE_EXACT; + p.condition_mode = PPC_BREAKPOINT_CONDITION_NONE; + p.addr = (uint64_t) address; + p.addr2 = 0; + p.condition_value = 0; + +- set a watchpoint which triggers on reads in the second watchpoint register + + p.version = PPC_DEBUG_CURRENT_VERSION; + p.trigger_type = PPC_BREAKPOINT_TRIGGER_READ; + p.addr_mode = PPC_BREAKPOINT_MODE_EXACT; + p.condition_mode = PPC_BREAKPOINT_CONDITION_NONE; + p.addr = (uint64_t) address; + p.addr2 = 0; + p.condition_value = 0; + +- set a watchpoint which triggers only with a specific value + + p.version = PPC_DEBUG_CURRENT_VERSION; + p.trigger_type = PPC_BREAKPOINT_TRIGGER_READ; + p.addr_mode = PPC_BREAKPOINT_MODE_EXACT; + p.condition_mode = PPC_BREAKPOINT_CONDITION_AND | PPC_BREAKPOINT_CONDITION_BE_ALL; + p.addr = (uint64_t) address; + p.addr2 = 0; + p.condition_value = (uint64_t) condition; + +- set a ranged hardware breakpoint + + p.version = PPC_DEBUG_CURRENT_VERSION; + p.trigger_type = PPC_BREAKPOINT_TRIGGER_EXECUTE; + p.addr_mode = PPC_BREAKPOINT_MODE_RANGE_INCLUSIVE; + p.condition_mode = PPC_BREAKPOINT_CONDITION_NONE; + p.addr = (uint64_t) begin_range; + p.addr2 = (uint64_t) end_range; + p.condition_value = 0; + +3. PTRACE_DELHWDEBUG + +Takes an integer which identifies an existing breakpoint or watchpoint +(i.e., the value returned from PTRACE_SETHWDEBUG), and deletes the +corresponding breakpoint or watchpoint.. diff --git a/arch/powerpc/include/asm/ptrace.h b/arch/powerpc/include/asm/ptrace.h index cbd759e3cd7..b4510812656 100644 --- a/arch/powerpc/include/asm/ptrace.h +++ b/arch/powerpc/include/asm/ptrace.h @@ -24,6 +24,12 @@ * 2 of the License, or (at your option) any later version. */ +#ifdef __KERNEL__ +#include +#else +#include +#endif + #ifndef __ASSEMBLY__ struct pt_regs { @@ -294,4 +300,75 @@ extern void user_disable_single_step(struct task_struct *); #define PTRACE_SINGLEBLOCK 0x100 /* resume execution until next branch */ +#define PPC_PTRACE_GETHWDBGINFO 0x89 +#define PPC_PTRACE_SETHWDEBUG 0x88 +#define PPC_PTRACE_DELHWDEBUG 0x87 + +#ifndef __ASSEMBLY__ + +struct ppc_debug_info { + uint32_t version; /* Only version 1 exists to date */ + uint32_t num_instruction_bps; + uint32_t num_data_bps; + uint32_t num_condition_regs; + uint32_t data_bp_alignment; + uint32_t sizeof_condition; /* size of the DVC register */ + uint64_t features; +}; + +#endif /* __ASSEMBLY__ */ + +/* + * features will have bits indication whether there is support for: + */ +#define PPC_DEBUG_FEATURE_INSN_BP_RANGE 0x0000000000000001 +#define PPC_DEBUG_FEATURE_INSN_BP_MASK 0x0000000000000002 +#define PPC_DEBUG_FEATURE_DATA_BP_RANGE 0x0000000000000004 +#define PPC_DEBUG_FEATURE_DATA_BP_MASK 0x0000000000000008 + +#ifndef __ASSEMBLY__ + +struct ppc_hw_breakpoint { + uint32_t version; /* currently, version must be 1 */ + uint32_t trigger_type; /* only some combinations allowed */ + uint32_t addr_mode; /* address match mode */ + uint32_t condition_mode; /* break/watchpoint condition flags */ + uint64_t addr; /* break/watchpoint address */ + uint64_t addr2; /* range end or mask */ + uint64_t condition_value; /* contents of the DVC register */ +}; + +#endif /* __ASSEMBLY__ */ + +/* + * Trigger Type + */ +#define PPC_BREAKPOINT_TRIGGER_EXECUTE 0x00000001 +#define PPC_BREAKPOINT_TRIGGER_READ 0x00000002 +#define PPC_BREAKPOINT_TRIGGER_WRITE 0x00000004 +#define PPC_BREAKPOINT_TRIGGER_RW \ + (PPC_BREAKPOINT_TRIGGER_READ | PPC_BREAKPOINT_TRIGGER_WRITE) + +/* + * Address Mode + */ +#define PPC_BREAKPOINT_MODE_EXACT 0x00000000 +#define PPC_BREAKPOINT_MODE_RANGE_INCLUSIVE 0x00000001 +#define PPC_BREAKPOINT_MODE_RANGE_EXCLUSIVE 0x00000002 +#define PPC_BREAKPOINT_MODE_MASK 0x00000003 + +/* + * Condition Mode + */ +#define PPC_BREAKPOINT_CONDITION_MODE 0x00000003 +#define PPC_BREAKPOINT_CONDITION_NONE 0x00000000 +#define PPC_BREAKPOINT_CONDITION_AND 0x00000001 +#define PPC_BREAKPOINT_CONDITION_EXACT PPC_BREAKPOINT_CONDITION_AND +#define PPC_BREAKPOINT_CONDITION_OR 0x00000002 +#define PPC_BREAKPOINT_CONDITION_AND_OR 0x00000003 +#define PPC_BREAKPOINT_CONDITION_BE_ALL 0x00ff0000 +#define PPC_BREAKPOINT_CONDITION_BE_SHIFT 16 +#define PPC_BREAKPOINT_CONDITION_BE(n) \ + (1<<((n)+PPC_BREAKPOINT_CONDITION_BE_SHIFT)) + #endif /* _ASM_POWERPC_PTRACE_H */ diff --git a/arch/powerpc/kernel/ptrace.c b/arch/powerpc/kernel/ptrace.c index 292c8143201..8847bd618ce 100644 --- a/arch/powerpc/kernel/ptrace.c +++ b/arch/powerpc/kernel/ptrace.c @@ -835,6 +835,52 @@ void ptrace_disable(struct task_struct *child) user_disable_single_step(child); } +static long ppc_set_hwdebug(struct task_struct *child, + struct ppc_hw_breakpoint *bp_info) +{ + /* + * We currently support one data breakpoint + */ + if (((bp_info->trigger_type & PPC_BREAKPOINT_TRIGGER_RW) == 0) || + ((bp_info->trigger_type & ~PPC_BREAKPOINT_TRIGGER_RW) != 0) || + (bp_info->trigger_type != PPC_BREAKPOINT_TRIGGER_WRITE) || + (bp_info->addr_mode != PPC_BREAKPOINT_MODE_EXACT) || + (bp_info->condition_mode != PPC_BREAKPOINT_CONDITION_NONE)) + return -EINVAL; + + if (child->thread.dabr) + return -ENOSPC; + + if ((unsigned long)bp_info->addr >= TASK_SIZE) + return -EIO; + + child->thread.dabr = (unsigned long)bp_info->addr; +#ifdef CONFIG_PPC_ADV_DEBUG_REGS + child->thread.dbcr0 = DBCR0_IDM; + if (bp_info->trigger_type & PPC_BREAKPOINT_TRIGGER_READ) + child->thread.dbcr0 |= DBSR_DAC1R; + if (bp_info->trigger_type & PPC_BREAKPOINT_TRIGGER_WRITE) + child->thread.dbcr0 |= DBSR_DAC1W; + child->thread.regs->msr |= MSR_DE; +#endif + return 1; +} + +static long ppc_del_hwdebug(struct task_struct *child, long addr, long data) +{ + if (data != 1) + return -EINVAL; + if (child->thread.dabr == 0) + return -ENOENT; + + child->thread.dabr = 0; +#ifdef CONFIG_PPC_ADV_DEBUG_REGS + child->thread.dbcr0 &= ~(DBSR_DAC1R | DBSR_DAC1W | DBCR0_IDM); + child->thread.regs->msr &= ~MSR_DE; +#endif + return 0; +} + /* * Here are the old "legacy" powerpc specific getregs/setregs ptrace calls, * we mark them as obsolete now, they will be removed in a future version @@ -928,6 +974,50 @@ long arch_ptrace(struct task_struct *child, long request, long addr, long data) break; } + case PPC_PTRACE_GETHWDBGINFO: { + struct ppc_debug_info dbginfo; + + dbginfo.version = 1; + dbginfo.num_instruction_bps = 0; + dbginfo.num_data_bps = 1; + dbginfo.num_condition_regs = 0; +#ifdef CONFIG_PPC64 + dbginfo.data_bp_alignment = 8; +#else + dbginfo.data_bp_alignment = 4; +#endif + dbginfo.sizeof_condition = 0; + dbginfo.features = 0; + + if (!access_ok(VERIFY_WRITE, data, + sizeof(struct ppc_debug_info))) + return -EFAULT; + ret = __copy_to_user((struct ppc_debug_info __user *)data, + &dbginfo, sizeof(struct ppc_debug_info)) ? + -EFAULT : 0; + break; + } + + case PPC_PTRACE_SETHWDEBUG: { + struct ppc_hw_breakpoint bp_info; + + if (!access_ok(VERIFY_READ, data, + sizeof(struct ppc_hw_breakpoint))) + return -EFAULT; + ret = __copy_from_user(&bp_info, + (struct ppc_hw_breakpoint __user *)data, + sizeof(struct ppc_hw_breakpoint)) ? + -EFAULT : 0; + if (!ret) + ret = ppc_set_hwdebug(child, &bp_info); + break; + } + + case PPC_PTRACE_DELHWDEBUG: { + ret = ppc_del_hwdebug(child, addr, data); + break; + } + case PTRACE_GET_DEBUGREG: { ret = -EINVAL; /* We only support one DABR and no IABRS at the moment */ -- cgit v1.2.3-70-g09d2 From 4f35e23eb0d62c5cd8b857933a0b6bd56ebdb010 Mon Sep 17 00:00:00 2001 From: Anatolij Gustschin Date: Tue, 16 Feb 2010 22:30:04 -0700 Subject: powerpc: doc/dts-bindings: document mpc5121 psc uart dts-bindings Support for MPC5121 PSC UART in the mpc52xx_uart driver added new DTS properties for FSL MPC5121 PSC FIFO Controller. Provide documentation of the new properties and some examples. Signed-off-by: Anatolij Gustschin Acked-by: Greg Kroah-Hartman Signed-off-by: Grant Likely --- .../powerpc/dts-bindings/fsl/mpc5121-psc.txt | 70 ++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 Documentation/powerpc/dts-bindings/fsl/mpc5121-psc.txt (limited to 'Documentation') diff --git a/Documentation/powerpc/dts-bindings/fsl/mpc5121-psc.txt b/Documentation/powerpc/dts-bindings/fsl/mpc5121-psc.txt new file mode 100644 index 00000000000..8832e879891 --- /dev/null +++ b/Documentation/powerpc/dts-bindings/fsl/mpc5121-psc.txt @@ -0,0 +1,70 @@ +MPC5121 PSC Device Tree Bindings + +PSC in UART mode +---------------- + +For PSC in UART mode the needed PSC serial devices +are specified by fsl,mpc5121-psc-uart nodes in the +fsl,mpc5121-immr SoC node. Additionally the PSC FIFO +Controller node fsl,mpc5121-psc-fifo is requered there: + +fsl,mpc5121-psc-uart nodes +-------------------------- + +Required properties : + - compatible : Should contain "fsl,mpc5121-psc-uart" and "fsl,mpc5121-psc" + - cell-index : Index of the PSC in hardware + - reg : Offset and length of the register set for the PSC device + - interrupts : where a is the interrupt number of the + PSC FIFO Controller and b is a field that represents an + encoding of the sense and level information for the interrupt. + - interrupt-parent : the phandle for the interrupt controller that + services interrupts for this device. + +Recommended properties : + - fsl,rx-fifo-size : the size of the RX fifo slice (a multiple of 4) + - fsl,tx-fifo-size : the size of the TX fifo slice (a multiple of 4) + + +fsl,mpc5121-psc-fifo node +------------------------- + +Required properties : + - compatible : Should be "fsl,mpc5121-psc-fifo" + - reg : Offset and length of the register set for the PSC + FIFO Controller + - interrupts : where a is the interrupt number of the + PSC FIFO Controller and b is a field that represents an + encoding of the sense and level information for the interrupt. + - interrupt-parent : the phandle for the interrupt controller that + services interrupts for this device. + + +Example for a board using PSC0 and PSC1 devices in serial mode: + +serial@11000 { + compatible = "fsl,mpc5121-psc-uart", "fsl,mpc5121-psc"; + cell-index = <0>; + reg = <0x11000 0x100>; + interrupts = <40 0x8>; + interrupt-parent = < &ipic >; + fsl,rx-fifo-size = <16>; + fsl,tx-fifo-size = <16>; +}; + +serial@11100 { + compatible = "fsl,mpc5121-psc-uart", "fsl,mpc5121-psc"; + cell-index = <1>; + reg = <0x11100 0x100>; + interrupts = <40 0x8>; + interrupt-parent = < &ipic >; + fsl,rx-fifo-size = <16>; + fsl,tx-fifo-size = <16>; +}; + +pscfifo@11f00 { + compatible = "fsl,mpc5121-psc-fifo"; + reg = <0x11f00 0x100>; + interrupts = <40 0x8>; + interrupt-parent = < &ipic >; +}; -- cgit v1.2.3-70-g09d2 From 85cd7467f3a66800c20815d3fca45d33038b9279 Mon Sep 17 00:00:00 2001 From: Ernst Schwab Date: Wed, 17 Feb 2010 07:33:02 -0700 Subject: powerpc/of: added documentation for spi chipselects Added devicetree binding documentation for gpios used as chipselect. The code to evaluate these is already present in spi_mpc8xxx.c. Signed-off-by: Ernst Schwab Reviewed-by: Wolfram Sang Signed-off-by: Grant Likely --- Documentation/powerpc/dts-bindings/fsl/spi.txt | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'Documentation') diff --git a/Documentation/powerpc/dts-bindings/fsl/spi.txt b/Documentation/powerpc/dts-bindings/fsl/spi.txt index e7d9a344c4f..80510c018ee 100644 --- a/Documentation/powerpc/dts-bindings/fsl/spi.txt +++ b/Documentation/powerpc/dts-bindings/fsl/spi.txt @@ -13,6 +13,11 @@ Required properties: - interrupt-parent : the phandle for the interrupt controller that services interrupts for this device. +Optional properties: +- gpios : specifies the gpio pins to be used for chipselects. + The gpios will be referred to as reg = in the SPI child nodes. + If unspecified, a single SPI device without a chip select can be used. + Example: spi@4c0 { cell-index = <0>; @@ -21,4 +26,6 @@ Example: interrupts = <82 0>; interrupt-parent = <700>; mode = "cpu"; + gpios = <&gpio 18 1 // device reg=<0> + &gpio 19 1>; // device reg=<1> }; -- cgit v1.2.3-70-g09d2 From 3d068261854b00c930df4516cd617900935e7706 Mon Sep 17 00:00:00 2001 From: Dominik Brodowski Date: Sat, 16 Jan 2010 09:17:10 +0100 Subject: pcmcia: add locking documentation Tested-by: Wolfram Sang Signed-off-by: Dominik Brodowski --- Documentation/pcmcia/locking.txt | 92 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 Documentation/pcmcia/locking.txt (limited to 'Documentation') diff --git a/Documentation/pcmcia/locking.txt b/Documentation/pcmcia/locking.txt new file mode 100644 index 00000000000..5f25de4cdb4 --- /dev/null +++ b/Documentation/pcmcia/locking.txt @@ -0,0 +1,92 @@ +This file explains the locking and exclusion scheme used in the PCCARD +and PCMCIA subsystems. + + +A) Overview, Locking Hierarchy: +=============================== + +pcmcia_socket_list_rwsem - protects only the list of sockets +- skt_mutex - serializes card insert / ejection + - ops_mutex - serializes socket operation + + +B) Exclusion +============ + +The following functions and callbacks to struct pcmcia_socket must +be called with "skt_mutex" held: + + socket_detect_change() + send_event() + socket_reset() + socket_shutdown() + socket_setup() + socket_remove() + socket_insert() + socket_early_resume() + socket_late_resume() + socket_resume() + socket_suspend() + + struct pcmcia_callback *callback + +The following functions and callbacks to struct pcmcia_socket must +be called with "ops_mutex" held: + + socket_reset() + socket_setup() + + struct pccard_operations *ops + +Note that send_event() and struct pcmcia_callback *callback must not be +called with "ops_mutex" held. + + +C) Protection +============= + +1. Global Data: +--------------- +struct list_head pcmcia_socket_list; + +protected by pcmcia_socket_list_rwsem; + + +2. Per-Socket Data: +------------------- +The resource_ops are on their own to provide proper locking. + +The "main" struct pcmcia_socket is protected as follows (read-only fields +or single-use fields not mentioned): + +- by pcmcia_socket_list_rwsem: + struct list_head socket_list; + +- by thread_lock: + unsigned int thread_events; + +- by skt_mutex: + u_int suspended_state; + void (*tune_bridge); + struct pcmcia_callback *callback; + int resume_status; + +- by ops_mutex: + socket_state_t socket; + u_int state; + u_short lock_count; + pccard_mem_map cis_mem; + void __iomem *cis_virt; + struct { } irq; + io_window_t io[]; + pccard_mem_map win[]; + struct list_head cis_cache; + size_t fake_cis_len; + u8 *fake_cis; + u_int irq_mask; + void (*zoom_video); + int (*power_hook); + u8 resource...; + struct list_head devices_list; + u8 device_count; + struct pcmcia_state; -- cgit v1.2.3-70-g09d2 From 94a819f80297e1f635a7cde4ed5317612e512ba7 Mon Sep 17 00:00:00 2001 From: Dominik Brodowski Date: Sun, 17 Jan 2010 18:31:34 +0100 Subject: pcmcia: assert locking to struct pcmcia_device Tested-by: Wolfram Sang Signed-off-by: Dominik Brodowski --- Documentation/pcmcia/locking.txt | 25 ++++++++ drivers/pcmcia/ds.c | 38 ++++++++---- drivers/pcmcia/pcmcia_resource.c | 122 ++++++++++++++++++++++++--------------- 3 files changed, 129 insertions(+), 56 deletions(-) (limited to 'Documentation') diff --git a/Documentation/pcmcia/locking.txt b/Documentation/pcmcia/locking.txt index 5f25de4cdb4..d6251056128 100644 --- a/Documentation/pcmcia/locking.txt +++ b/Documentation/pcmcia/locking.txt @@ -90,3 +90,28 @@ or single-use fields not mentioned): struct list_head devices_list; u8 device_count; struct pcmcia_state; + + +3. Per PCMCIA-device Data: +-------------------------- + +The "main" struct pcmcia_devie is protected as follows (read-only fields +or single-use fields not mentioned): + + +- by pcmcia_socket->ops_mutex: + struct list_head socket_device_list; + struct config_t *function_config; + u16 _irq:1; + u16 _io:1; + u16 _win:4; + u16 _locked:1; + u16 allow_func_id_match:1; + u16 suspended:1; + u16 _removed:1; + +- by the PCMCIA driver: + io_req_t io; + irq_req_t irq; + config_req_t conf; + window_handle_t win; diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c index 4c40db8889d..83b51ddd3da 100644 --- a/drivers/pcmcia/ds.c +++ b/drivers/pcmcia/ds.c @@ -835,6 +835,8 @@ static inline int pcmcia_devmatch(struct pcmcia_device *dev, } if (did->match_flags & PCMCIA_DEV_ID_MATCH_FUNC_ID) { + int ret; + if ((!dev->has_func_id) || (dev->func_id != did->func_id)) return 0; @@ -849,10 +851,15 @@ static inline int pcmcia_devmatch(struct pcmcia_device *dev, * after it has re-checked that there is no possible module * with a prod_id/manf_id/card_id match. */ - dev_dbg(&dev->dev, - "skipping FUNC_ID match until userspace interaction\n"); - if (!dev->allow_func_id_match) + mutex_lock(&dev->socket->ops_mutex); + ret = dev->allow_func_id_match; + mutex_unlock(&dev->socket->ops_mutex); + + if (!ret) { + dev_dbg(&dev->dev, + "skipping FUNC_ID match until userspace ACK\n"); return 0; + } } if (did->match_flags & PCMCIA_DEV_ID_MATCH_FAKE_CIS) { @@ -1079,9 +1086,9 @@ static ssize_t pcmcia_store_allow_func_id_match(struct device *dev, if (!count) return -EINVAL; - mutex_lock(&p_dev->socket->skt_mutex); + mutex_lock(&p_dev->socket->ops_mutex); p_dev->allow_func_id_match = 1; - mutex_unlock(&p_dev->socket->skt_mutex); + mutex_unlock(&p_dev->socket->ops_mutex); ret = bus_rescan_devices(&pcmcia_bus_type); if (ret) @@ -1114,8 +1121,13 @@ static int pcmcia_dev_suspend(struct device *dev, pm_message_t state) struct pcmcia_driver *p_drv = NULL; int ret = 0; - if (p_dev->suspended) + mutex_lock(&p_dev->socket->ops_mutex); + if (p_dev->suspended) { + mutex_unlock(&p_dev->socket->ops_mutex); return 0; + } + p_dev->suspended = 1; + mutex_unlock(&p_dev->socket->ops_mutex); dev_dbg(dev, "suspending\n"); @@ -1132,6 +1144,9 @@ static int pcmcia_dev_suspend(struct device *dev, pm_message_t state) "pcmcia: device %s (driver %s) did " "not want to go to sleep (%d)\n", p_dev->devname, p_drv->drv.name, ret); + mutex_lock(&p_dev->socket->ops_mutex); + p_dev->suspended = 0; + mutex_unlock(&p_dev->socket->ops_mutex); goto out; } } @@ -1142,8 +1157,6 @@ static int pcmcia_dev_suspend(struct device *dev, pm_message_t state) } out: - if (!ret) - p_dev->suspended = 1; return ret; } @@ -1154,8 +1167,13 @@ static int pcmcia_dev_resume(struct device *dev) struct pcmcia_driver *p_drv = NULL; int ret = 0; - if (!p_dev->suspended) + mutex_lock(&p_dev->socket->ops_mutex); + if (!p_dev->suspended) { + mutex_unlock(&p_dev->socket->ops_mutex); return 0; + } + p_dev->suspended = 0; + mutex_unlock(&p_dev->socket->ops_mutex); dev_dbg(dev, "resuming\n"); @@ -1176,8 +1194,6 @@ static int pcmcia_dev_resume(struct device *dev) ret = p_drv->resume(p_dev); out: - if (!ret) - p_dev->suspended = 0; return ret; } diff --git a/drivers/pcmcia/pcmcia_resource.c b/drivers/pcmcia/pcmcia_resource.c index f0de7b8b123..b2df04199a2 100644 --- a/drivers/pcmcia/pcmcia_resource.c +++ b/drivers/pcmcia/pcmcia_resource.c @@ -191,14 +191,18 @@ int pcmcia_access_configuration_register(struct pcmcia_device *p_dev, return -EINVAL; s = p_dev->socket; + + mutex_lock(&s->ops_mutex); c = p_dev->function_config; if (!(c->state & CONFIG_LOCKED)) { dev_dbg(&s->dev, "Configuration isnt't locked\n"); + mutex_unlock(&s->ops_mutex); return -EACCES; } addr = (c->ConfigBase + reg->Offset) >> 1; + mutex_unlock(&s->ops_mutex); switch (reg->Action) { case CS_READ: @@ -254,19 +258,22 @@ int pcmcia_modify_configuration(struct pcmcia_device *p_dev, config_t *c; s = p_dev->socket; + + mutex_lock(&s->ops_mutex); c = p_dev->function_config; if (!(s->state & SOCKET_PRESENT)) { dev_dbg(&s->dev, "No card present\n"); + mutex_unlock(&s->ops_mutex); return -ENODEV; } if (!(c->state & CONFIG_LOCKED)) { dev_dbg(&s->dev, "Configuration isnt't locked\n"); + mutex_unlock(&s->ops_mutex); return -EACCES; } if (mod->Attributes & CONF_IRQ_CHANGE_VALID) { - mutex_lock(&s->ops_mutex); if (mod->Attributes & CONF_ENABLE_IRQ) { c->Attributes |= CONF_ENABLE_IRQ; s->socket.io_irq = s->irq.AssignedIRQ; @@ -275,7 +282,6 @@ int pcmcia_modify_configuration(struct pcmcia_device *p_dev, s->socket.io_irq = 0; } s->ops->set_socket(s, &s->socket); - mutex_unlock(&s->ops_mutex); } if (mod->Attributes & CONF_VCC_CHANGE_VALID) { @@ -288,9 +294,9 @@ int pcmcia_modify_configuration(struct pcmcia_device *p_dev, (mod->Attributes & CONF_VPP2_CHANGE_VALID)) { if (mod->Vpp1 != mod->Vpp2) { dev_dbg(&s->dev, "Vpp1 and Vpp2 must be the same\n"); + mutex_unlock(&s->ops_mutex); return -EINVAL; } - mutex_lock(&s->ops_mutex); s->socket.Vpp = mod->Vpp1; if (s->ops->set_socket(s, &s->socket)) { mutex_unlock(&s->ops_mutex); @@ -298,10 +304,10 @@ int pcmcia_modify_configuration(struct pcmcia_device *p_dev, "Unable to set VPP\n"); return -EIO; } - mutex_unlock(&s->ops_mutex); } else if ((mod->Attributes & CONF_VPP1_CHANGE_VALID) || (mod->Attributes & CONF_VPP2_CHANGE_VALID)) { dev_dbg(&s->dev, "changing Vcc is not allowed at this time\n"); + mutex_unlock(&s->ops_mutex); return -EINVAL; } @@ -311,7 +317,6 @@ int pcmcia_modify_configuration(struct pcmcia_device *p_dev, int i; io_on.speed = io_speed; - mutex_lock(&s->ops_mutex); for (i = 0; i < MAX_IO_WIN; i++) { if (!s->io[i].res) continue; @@ -326,8 +331,8 @@ int pcmcia_modify_configuration(struct pcmcia_device *p_dev, mdelay(40); s->ops->set_io_map(s, &io_on); } - mutex_unlock(&s->ops_mutex); } + mutex_unlock(&s->ops_mutex); return 0; } /* modify_configuration */ @@ -338,10 +343,11 @@ int pcmcia_release_configuration(struct pcmcia_device *p_dev) { pccard_io_map io = { 0, 0, 0, 0, 1 }; struct pcmcia_socket *s = p_dev->socket; - config_t *c = p_dev->function_config; + config_t *c; int i; mutex_lock(&s->ops_mutex); + c = p_dev->function_config; if (p_dev->_locked) { p_dev->_locked = 0; if (--(s->lock_count) == 0) { @@ -381,10 +387,14 @@ int pcmcia_release_configuration(struct pcmcia_device *p_dev) static int pcmcia_release_io(struct pcmcia_device *p_dev, io_req_t *req) { struct pcmcia_socket *s = p_dev->socket; - config_t *c = p_dev->function_config; + int ret = -EINVAL; + config_t *c; + + mutex_lock(&s->ops_mutex); + c = p_dev->function_config; if (!p_dev->_io) - return -EINVAL; + goto out; p_dev->_io = 0; @@ -392,7 +402,7 @@ static int pcmcia_release_io(struct pcmcia_device *p_dev, io_req_t *req) (c->io.NumPorts1 != req->NumPorts1) || (c->io.BasePort2 != req->BasePort2) || (c->io.NumPorts2 != req->NumPorts2)) - return -EINVAL; + goto out; c->state &= ~CONFIG_IO_REQ; @@ -400,30 +410,38 @@ static int pcmcia_release_io(struct pcmcia_device *p_dev, io_req_t *req) if (req->NumPorts2) release_io_space(s, req->BasePort2, req->NumPorts2); - return 0; +out: + mutex_unlock(&s->ops_mutex); + + return ret; } /* pcmcia_release_io */ static int pcmcia_release_irq(struct pcmcia_device *p_dev, irq_req_t *req) { struct pcmcia_socket *s = p_dev->socket; - config_t *c = p_dev->function_config; + config_t *c; + int ret = -EINVAL; + + mutex_lock(&s->ops_mutex); + + c = p_dev->function_config; if (!p_dev->_irq) - return -EINVAL; + goto out; + p_dev->_irq = 0; if (c->state & CONFIG_LOCKED) - return -EACCES; + goto out; + if (c->irq.Attributes != req->Attributes) { dev_dbg(&s->dev, "IRQ attributes must match assigned ones\n"); - return -EINVAL; + goto out; } - mutex_lock(&s->ops_mutex); if (s->irq.AssignedIRQ != req->AssignedIRQ) { - mutex_unlock(&s->ops_mutex); dev_dbg(&s->dev, "IRQ must match assigned one\n"); - return -EINVAL; + goto out; } if (--s->irq.Config == 0) { c->state &= ~CONFIG_IRQ_REQ; @@ -436,9 +454,12 @@ static int pcmcia_release_irq(struct pcmcia_device *p_dev, irq_req_t *req) #ifdef CONFIG_PCMCIA_PROBE pcmcia_used_irq[req->AssignedIRQ]--; #endif + ret = 0; + +out: mutex_unlock(&s->ops_mutex); - return 0; + return ret; } /* pcmcia_release_irq */ @@ -495,13 +516,15 @@ int pcmcia_request_configuration(struct pcmcia_device *p_dev, dev_dbg(&s->dev, "IntType may not be INT_CARDBUS\n"); return -EINVAL; } + + mutex_lock(&s->ops_mutex); c = p_dev->function_config; if (c->state & CONFIG_LOCKED) { + mutex_unlock(&s->ops_mutex); dev_dbg(&s->dev, "Configuration is locked\n"); return -EACCES; } - mutex_lock(&s->ops_mutex); /* Do power control. We don't allow changes in Vcc. */ s->socket.Vpp = req->Vpp; if (s->ops->set_socket(s, &s->socket)) { @@ -615,58 +638,65 @@ int pcmcia_request_io(struct pcmcia_device *p_dev, io_req_t *req) { struct pcmcia_socket *s = p_dev->socket; config_t *c; + int ret = -EINVAL; + + mutex_lock(&s->ops_mutex); if (!(s->state & SOCKET_PRESENT)) { dev_dbg(&s->dev, "No card present\n"); - return -ENODEV; + goto out; } if (!req) - return -EINVAL; + goto out; + c = p_dev->function_config; if (c->state & CONFIG_LOCKED) { dev_dbg(&s->dev, "Configuration is locked\n"); - return -EACCES; + goto out; } if (c->state & CONFIG_IO_REQ) { dev_dbg(&s->dev, "IO already configured\n"); - return -EBUSY; + goto out; } if (req->Attributes1 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS)) { dev_dbg(&s->dev, "bad attribute setting for IO region 1\n"); - return -EINVAL; + goto out; } if ((req->NumPorts2 > 0) && (req->Attributes2 & (IO_SHARED | IO_FORCE_ALIAS_ACCESS))) { dev_dbg(&s->dev, "bad attribute setting for IO region 2\n"); - return -EINVAL; + goto out; } - mutex_lock(&s->ops_mutex); dev_dbg(&s->dev, "trying to allocate resource 1\n"); - if (alloc_io_space(s, req->Attributes1, &req->BasePort1, - req->NumPorts1, req->IOAddrLines)) { + ret = alloc_io_space(s, req->Attributes1, &req->BasePort1, + req->NumPorts1, req->IOAddrLines); + if (ret) { dev_dbg(&s->dev, "allocation of resource 1 failed\n"); - mutex_unlock(&s->ops_mutex); - return -EBUSY; + goto out; } if (req->NumPorts2) { dev_dbg(&s->dev, "trying to allocate resource 2\n"); - if (alloc_io_space(s, req->Attributes2, &req->BasePort2, - req->NumPorts2, req->IOAddrLines)) { + ret = alloc_io_space(s, req->Attributes2, &req->BasePort2, + req->NumPorts2, req->IOAddrLines); + if (ret) { dev_dbg(&s->dev, "allocation of resource 2 failed\n"); release_io_space(s, req->BasePort1, req->NumPorts1); - mutex_unlock(&s->ops_mutex); - return -EBUSY; + goto out; } } - mutex_unlock(&s->ops_mutex); c->io = *req; c->state |= CONFIG_IO_REQ; p_dev->_io = 1; - return 0; + dev_dbg(&s->dev, "allocating resources succeeded: %d\n", ret); + +out: + mutex_unlock(&s->ops_mutex); + + return ret; } /* pcmcia_request_io */ EXPORT_SYMBOL(pcmcia_request_io); @@ -695,21 +725,22 @@ int pcmcia_request_irq(struct pcmcia_device *p_dev, irq_req_t *req) int ret = -EINVAL, irq = 0; int type; + mutex_lock(&s->ops_mutex); + if (!(s->state & SOCKET_PRESENT)) { dev_dbg(&s->dev, "No card present\n"); - return -ENODEV; + goto out; } c = p_dev->function_config; if (c->state & CONFIG_LOCKED) { dev_dbg(&s->dev, "Configuration is locked\n"); - return -EACCES; + goto out; } if (c->state & CONFIG_IRQ_REQ) { dev_dbg(&s->dev, "IRQ already configured\n"); - return -EBUSY; + goto out; } - mutex_lock(&s->ops_mutex); /* Decide what type of interrupt we are registering */ type = 0; if (s->functions > 1) /* All of this ought to be handled higher up */ @@ -768,7 +799,7 @@ int pcmcia_request_irq(struct pcmcia_device *p_dev, irq_req_t *req) if (ret && !s->irq.AssignedIRQ) { if (!s->pci_irq) { dev_printk(KERN_INFO, &s->dev, "no IRQ found\n"); - return ret; + goto out; } type = IRQF_SHARED; irq = s->pci_irq; @@ -780,7 +811,7 @@ int pcmcia_request_irq(struct pcmcia_device *p_dev, irq_req_t *req) if (ret) { dev_printk(KERN_INFO, &s->dev, "request_irq() failed\n"); - return ret; + goto out; } } @@ -803,9 +834,10 @@ int pcmcia_request_irq(struct pcmcia_device *p_dev, irq_req_t *req) pcmcia_used_irq[irq]++; #endif + ret = 0; +out: mutex_unlock(&s->ops_mutex); - - return 0; + return ret; } /* pcmcia_request_irq */ EXPORT_SYMBOL(pcmcia_request_irq); -- cgit v1.2.3-70-g09d2 From cfe5d809518eda3d5e2da87c5ccbe8647143573a Mon Sep 17 00:00:00 2001 From: Dominik Brodowski Date: Sun, 17 Jan 2010 19:31:45 +0100 Subject: pcmcia: use ops_mutex for rsrc_{mgr,nonstatic} locking Tested-by: Wolfram Sang Signed-off-by: Dominik Brodowski --- Documentation/pcmcia/locking.txt | 5 +++-- drivers/pcmcia/cs.c | 7 ++++++- drivers/pcmcia/ds.c | 6 +++++- drivers/pcmcia/pcmcia_ioctl.c | 4 +--- drivers/pcmcia/rsrc_mgr.c | 2 -- drivers/pcmcia/rsrc_nonstatic.c | 35 ++++++++--------------------------- 6 files changed, 23 insertions(+), 36 deletions(-) (limited to 'Documentation') diff --git a/Documentation/pcmcia/locking.txt b/Documentation/pcmcia/locking.txt index d6251056128..68f622bc406 100644 --- a/Documentation/pcmcia/locking.txt +++ b/Documentation/pcmcia/locking.txt @@ -36,7 +36,8 @@ be called with "ops_mutex" held: socket_reset() socket_setup() - struct pccard_operations *ops + struct pccard_operations *ops + struct pccard_resource_ops *resource_ops; Note that send_event() and struct pcmcia_callback *callback must not be called with "ops_mutex" held. @@ -54,7 +55,7 @@ protected by pcmcia_socket_list_rwsem; 2. Per-Socket Data: ------------------- -The resource_ops are on their own to provide proper locking. +The resource_ops and their data are protected by ops_mutex. The "main" struct pcmcia_socket is protected as follows (read-only fields or single-use fields not mentioned): diff --git a/drivers/pcmcia/cs.c b/drivers/pcmcia/cs.c index 13277eebe46..7ba45b0cca6 100644 --- a/drivers/pcmcia/cs.c +++ b/drivers/pcmcia/cs.c @@ -224,7 +224,9 @@ int pcmcia_register_socket(struct pcmcia_socket *socket) spin_lock_init(&socket->thread_lock); if (socket->resource_ops->init) { + mutex_lock(&socket->ops_mutex); ret = socket->resource_ops->init(socket); + mutex_unlock(&socket->ops_mutex); if (ret) goto err; } @@ -282,8 +284,11 @@ void pcmcia_unregister_socket(struct pcmcia_socket *socket) up_write(&pcmcia_socket_list_rwsem); /* wait for sysfs to drop all references */ - if (socket->resource_ops->exit) + if (socket->resource_ops->exit) { + mutex_lock(&socket->ops_mutex); socket->resource_ops->exit(socket); + mutex_unlock(&socket->ops_mutex); + } wait_for_completion(&socket->socket_released); } /* pcmcia_unregister_socket */ EXPORT_SYMBOL(pcmcia_unregister_socket); diff --git a/drivers/pcmcia/ds.c b/drivers/pcmcia/ds.c index 253d9aca5f7..76a21638291 100644 --- a/drivers/pcmcia/ds.c +++ b/drivers/pcmcia/ds.c @@ -607,19 +607,23 @@ static int pcmcia_card_add(struct pcmcia_socket *s) { cistpl_longlink_mfc_t mfc; unsigned int no_funcs, i, no_chains; - int ret = 0; + int ret = -EAGAIN; + mutex_lock(&s->ops_mutex); if (!(s->resource_setup_done)) { dev_dbg(&s->dev, "no resources available, delaying card_add\n"); + mutex_unlock(&s->ops_mutex); return -EAGAIN; /* try again, but later... */ } if (pcmcia_validate_mem(s)) { dev_dbg(&s->dev, "validating mem resources failed, " "delaying card_add\n"); + mutex_unlock(&s->ops_mutex); return -EAGAIN; /* try again, but later... */ } + mutex_unlock(&s->ops_mutex); ret = pccard_validate_cis(s, &no_chains); if (ret || !no_chains) { diff --git a/drivers/pcmcia/pcmcia_ioctl.c b/drivers/pcmcia/pcmcia_ioctl.c index db2e3db8008..96fd236f52a 100644 --- a/drivers/pcmcia/pcmcia_ioctl.c +++ b/drivers/pcmcia/pcmcia_ioctl.c @@ -187,7 +187,6 @@ static int pcmcia_adjust_resource_info(adjust_t *adj) continue; } else if (!(s->resource_setup_old)) s->resource_setup_old = 1; - mutex_unlock(&s->ops_mutex); switch (adj->Resource) { case RES_MEMORY_RANGE: @@ -206,10 +205,9 @@ static int pcmcia_adjust_resource_info(adjust_t *adj) * last call to adjust_resource_info, we * always need to assume this is the latest * one... */ - mutex_lock(&s->ops_mutex); s->resource_setup_done = 1; - mutex_unlock(&s->ops_mutex); } + mutex_unlock(&s->ops_mutex); } } up_read(&pcmcia_socket_list_rwsem); diff --git a/drivers/pcmcia/rsrc_mgr.c b/drivers/pcmcia/rsrc_mgr.c index b81586622d0..aca2cfd02ca 100644 --- a/drivers/pcmcia/rsrc_mgr.c +++ b/drivers/pcmcia/rsrc_mgr.c @@ -26,9 +26,7 @@ static int static_init(struct pcmcia_socket *s) /* the good thing about SS_CAP_STATIC_MAP sockets is * that they don't need a resource database */ - mutex_lock(&s->ops_mutex); s->resource_setup_done = 1; - mutex_unlock(&s->ops_mutex); return 0; } diff --git a/drivers/pcmcia/rsrc_nonstatic.c b/drivers/pcmcia/rsrc_nonstatic.c index 1de46cf2772..c13424f7b47 100644 --- a/drivers/pcmcia/rsrc_nonstatic.c +++ b/drivers/pcmcia/rsrc_nonstatic.c @@ -59,7 +59,6 @@ struct socket_data { unsigned int rsrc_mem_probe; }; -static DEFINE_MUTEX(rsrc_mutex); #define MEM_PROBE_LOW (1 << 0) #define MEM_PROBE_HIGH (1 << 1) @@ -274,7 +273,6 @@ static int readable(struct pcmcia_socket *s, struct resource *res, { int ret = -EINVAL; - mutex_lock(&s->ops_mutex); s->cis_mem.res = res; s->cis_virt = ioremap(res->start, s->map_size); if (s->cis_virt) { @@ -288,7 +286,6 @@ static int readable(struct pcmcia_socket *s, struct resource *res, s->cis_virt = NULL; } s->cis_mem.res = NULL; - mutex_unlock(&s->ops_mutex); if ((ret) || (*count == 0)) return -EINVAL; return 0; @@ -304,8 +301,6 @@ static int checksum(struct pcmcia_socket *s, struct resource *res, int i, a = 0, b = -1, d; void __iomem *virt; - mutex_lock(&s->ops_mutex); - virt = ioremap(res->start, s->map_size); if (virt) { map.map = 0; @@ -328,8 +323,6 @@ static int checksum(struct pcmcia_socket *s, struct resource *res, iounmap(virt); } - mutex_unlock(&s->ops_mutex); - if (b == -1) return -EINVAL; @@ -570,8 +563,6 @@ static int pcmcia_nonstatic_validate_mem(struct pcmcia_socket *s) if (!probe_mem) return 0; - mutex_lock(&rsrc_mutex); - if (s->features & SS_CAP_PAGE_REGS) probe_mask = MEM_PROBE_HIGH; @@ -583,8 +574,6 @@ static int pcmcia_nonstatic_validate_mem(struct pcmcia_socket *s) } } - mutex_unlock(&rsrc_mutex); - return ret; } @@ -661,7 +650,6 @@ static int nonstatic_adjust_io_region(struct resource *res, unsigned long r_star struct socket_data *s_data = s->resource_data; int ret = -ENOMEM; - mutex_lock(&rsrc_mutex); for (m = s_data->io_db.next; m != &s_data->io_db; m = m->next) { unsigned long start = m->base; unsigned long end = m->base + m->num - 1; @@ -672,7 +660,6 @@ static int nonstatic_adjust_io_region(struct resource *res, unsigned long r_star ret = adjust_resource(res, r_start, r_end - r_start + 1); break; } - mutex_unlock(&rsrc_mutex); return ret; } @@ -706,7 +693,6 @@ static struct resource *nonstatic_find_io_region(unsigned long base, int num, data.offset = base & data.mask; data.map = &s_data->io_db; - mutex_lock(&rsrc_mutex); #ifdef CONFIG_PCI if (s->cb_dev) { ret = pci_bus_alloc_resource(s->cb_dev->bus, res, num, 1, @@ -715,7 +701,6 @@ static struct resource *nonstatic_find_io_region(unsigned long base, int num, #endif ret = allocate_resource(&ioport_resource, res, num, min, ~0UL, 1, pcmcia_align, &data); - mutex_unlock(&rsrc_mutex); if (ret != 0) { kfree(res); @@ -748,7 +733,6 @@ static struct resource *nonstatic_find_mem_region(u_long base, u_long num, min = 0x100000UL + base; } - mutex_lock(&rsrc_mutex); #ifdef CONFIG_PCI if (s->cb_dev) { ret = pci_bus_alloc_resource(s->cb_dev->bus, res, num, @@ -758,7 +742,6 @@ static struct resource *nonstatic_find_mem_region(u_long base, u_long num, #endif ret = allocate_resource(&iomem_resource, res, num, min, max, 1, pcmcia_align, &data); - mutex_unlock(&rsrc_mutex); if (ret == 0 || low) break; low = 1; @@ -781,7 +764,6 @@ static int adjust_memory(struct pcmcia_socket *s, unsigned int action, unsigned if (end < start) return -EINVAL; - mutex_lock(&rsrc_mutex); switch (action) { case ADD_MANAGED_RESOURCE: ret = add_interval(&data->mem_db, start, size); @@ -794,7 +776,6 @@ static int adjust_memory(struct pcmcia_socket *s, unsigned int action, unsigned default: ret = -EINVAL; } - mutex_unlock(&rsrc_mutex); return ret; } @@ -812,7 +793,6 @@ static int adjust_io(struct pcmcia_socket *s, unsigned int action, unsigned long if (end > IO_SPACE_LIMIT) return -EINVAL; - mutex_lock(&rsrc_mutex); switch (action) { case ADD_MANAGED_RESOURCE: if (add_interval(&data->io_db, start, size) != 0) { @@ -831,7 +811,6 @@ static int adjust_io(struct pcmcia_socket *s, unsigned int action, unsigned long ret = -EINVAL; break; } - mutex_unlock(&rsrc_mutex); return ret; } @@ -929,7 +908,6 @@ static void nonstatic_release_resource_db(struct pcmcia_socket *s) struct socket_data *data = s->resource_data; struct resource_map *p, *q; - mutex_lock(&rsrc_mutex); for (p = data->mem_db.next; p != &data->mem_db; p = q) { q = p->next; kfree(p); @@ -938,7 +916,6 @@ static void nonstatic_release_resource_db(struct pcmcia_socket *s) q = p->next; kfree(p); } - mutex_unlock(&rsrc_mutex); } @@ -965,7 +942,7 @@ static ssize_t show_io_db(struct device *dev, struct resource_map *p; ssize_t ret = 0; - mutex_lock(&rsrc_mutex); + mutex_lock(&s->ops_mutex); data = s->resource_data; for (p = data->io_db.next; p != &data->io_db; p = p->next) { @@ -977,7 +954,7 @@ static ssize_t show_io_db(struct device *dev, ((unsigned long) p->base + p->num - 1)); } - mutex_unlock(&rsrc_mutex); + mutex_unlock(&s->ops_mutex); return ret; } @@ -1005,9 +982,11 @@ static ssize_t store_io_db(struct device *dev, if (end_addr < start_addr) return -EINVAL; + mutex_lock(&s->ops_mutex); ret = adjust_io(s, add, start_addr, end_addr); if (!ret) s->resource_setup_new = 1; + mutex_unlock(&s->ops_mutex); return ret ? ret : count; } @@ -1021,7 +1000,7 @@ static ssize_t show_mem_db(struct device *dev, struct resource_map *p; ssize_t ret = 0; - mutex_lock(&rsrc_mutex); + mutex_lock(&s->ops_mutex); data = s->resource_data; for (p = data->mem_db.next; p != &data->mem_db; p = p->next) { @@ -1033,7 +1012,7 @@ static ssize_t show_mem_db(struct device *dev, ((unsigned long) p->base + p->num - 1)); } - mutex_unlock(&rsrc_mutex); + mutex_unlock(&s->ops_mutex); return ret; } @@ -1061,9 +1040,11 @@ static ssize_t store_mem_db(struct device *dev, if (end_addr < start_addr) return -EINVAL; + mutex_lock(&s->ops_mutex); ret = adjust_memory(s, add, start_addr, end_addr); if (!ret) s->resource_setup_new = 1; + mutex_unlock(&s->ops_mutex); return ret ? ret : count; } -- cgit v1.2.3-70-g09d2 From 63bad45db1d4e8aafe0633c5741c9720641f336a Mon Sep 17 00:00:00 2001 From: "Yang, Bo" Date: Sun, 6 Dec 2009 08:42:28 -0700 Subject: [SCSI] megaraid_sas: version and documentation update Signed-off-by Bo Yang Signed-off-by: James Bottomley --- Documentation/scsi/ChangeLog.megaraid_sas | 16 ++++++++++++++++ drivers/scsi/megaraid/megaraid_sas.c | 2 +- drivers/scsi/megaraid/megaraid_sas.h | 6 +++--- 3 files changed, 20 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/scsi/ChangeLog.megaraid_sas b/Documentation/scsi/ChangeLog.megaraid_sas index 17ffa060771..30023568805 100644 --- a/Documentation/scsi/ChangeLog.megaraid_sas +++ b/Documentation/scsi/ChangeLog.megaraid_sas @@ -1,3 +1,19 @@ +1 Release Date : Thur. Oct 29, 2009 09:12:45 PST 2009 - + (emaild-id:megaraidlinux@lsi.com) + Bo Yang + +2 Current Version : 00.00.04.17.1-rc1 +3 Older Version : 00.00.04.12 + +1. Add the pad_0 in mfi frame structure to 0 to fix the + context value larger than 32bit value issue. + +2. Add the logic drive list to the driver. Driver will + keep the logic drive list internal after driver load. + +3. driver fixed the device update issue after get the AEN + PD delete/ADD, LD add/delete from FW. + 1 Release Date : Tues. July 28, 2009 10:12:45 PST 2009 - (emaild-id:megaraidlinux@lsi.com) Bo Yang diff --git a/drivers/scsi/megaraid/megaraid_sas.c b/drivers/scsi/megaraid/megaraid_sas.c index 9ad274cf73a..8dc5a7d9fc8 100644 --- a/drivers/scsi/megaraid/megaraid_sas.c +++ b/drivers/scsi/megaraid/megaraid_sas.c @@ -10,7 +10,7 @@ * 2 of the License, or (at your option) any later version. * * FILE : megaraid_sas.c - * Version : v00.00.04.12-rc1 + * Version : v00.00.04.17.1-rc1 * * Authors: * (email-id : megaraidlinux@lsi.com) diff --git a/drivers/scsi/megaraid/megaraid_sas.h b/drivers/scsi/megaraid/megaraid_sas.h index 1135c94bedb..9d8b6bf605a 100644 --- a/drivers/scsi/megaraid/megaraid_sas.h +++ b/drivers/scsi/megaraid/megaraid_sas.h @@ -18,9 +18,9 @@ /* * MegaRAID SAS Driver meta data */ -#define MEGASAS_VERSION "00.00.04.12-rc1" -#define MEGASAS_RELDATE "Sep. 17, 2009" -#define MEGASAS_EXT_VERSION "Thu Sep. 17 11:41:51 PST 2009" +#define MEGASAS_VERSION "00.00.04.17.1-rc1" +#define MEGASAS_RELDATE "Oct. 29, 2009" +#define MEGASAS_EXT_VERSION "Thu. Oct. 29, 11:41:51 PST 2009" /* * Device IDs -- cgit v1.2.3-70-g09d2 From 5aa4b32fc86408705337e941ed716880c63d1590 Mon Sep 17 00:00:00 2001 From: Andreas Petlund Date: Thu, 18 Feb 2010 02:45:45 +0000 Subject: net: TCP thin-stream detection Inline function to dynamically detect thin streams based on the number of packets in flight. Used to dynamically trigger thin-stream mechanisms if enabled by ioctl or sysctl. Signed-off-by: Andreas Petlund Signed-off-by: David S. Miller --- Documentation/networking/tcp-thin.txt | 47 +++++++++++++++++++++++++++++++++++ include/net/tcp.h | 8 ++++++ 2 files changed, 55 insertions(+) create mode 100644 Documentation/networking/tcp-thin.txt (limited to 'Documentation') diff --git a/Documentation/networking/tcp-thin.txt b/Documentation/networking/tcp-thin.txt new file mode 100644 index 00000000000..151e229980f --- /dev/null +++ b/Documentation/networking/tcp-thin.txt @@ -0,0 +1,47 @@ +Thin-streams and TCP +==================== +A wide range of Internet-based services that use reliable transport +protocols display what we call thin-stream properties. This means +that the application sends data with such a low rate that the +retransmission mechanisms of the transport protocol are not fully +effective. In time-dependent scenarios (like online games, control +systems, stock trading etc.) where the user experience depends +on the data delivery latency, packet loss can be devastating for +the service quality. Extreme latencies are caused by TCP's +dependency on the arrival of new data from the application to trigger +retransmissions effectively through fast retransmit instead of +waiting for long timeouts. + +After analysing a large number of time-dependent interactive +applications, we have seen that they often produce thin streams +and also stay with this traffic pattern throughout its entire +lifespan. The combination of time-dependency and the fact that the +streams provoke high latencies when using TCP is unfortunate. + +In order to reduce application-layer latency when packets are lost, +a set of mechanisms has been made, which address these latency issues +for thin streams. In short, if the kernel detects a thin stream, +the retransmission mechanisms are modified in the following manner: + +1) If the stream is thin, fast retransmit on the first dupACK. +2) If the stream is thin, do not apply exponential backoff. + +These enhancements are applied only if the stream is detected as +thin. This is accomplished by defining a threshold for the number +of packets in flight. If there are less than 4 packets in flight, +fast retransmissions can not be triggered, and the stream is prone +to experience high retransmission latencies. + +Since these mechanisms are targeted at time-dependent applications, +they must be specifically activated by the application using the +TCP_THIN_LINEAR_TIMEOUTS and TCP_THIN_DUPACK IOCTLS or the +tcp_thin_linear_timeouts and tcp_thin_dupack sysctls. Both +modifications are turned off by default. + +References +========== +More information on the modifications, as well as a wide range of +experimental data can be found here: +"Improving latency for interactive, thin-stream applications over +reliable transport" +http://simula.no/research/nd/publications/Simula.nd.477/simula_pdf_file diff --git a/include/net/tcp.h b/include/net/tcp.h index 75a00c80bdd..0bdc3f64024 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -1386,6 +1386,14 @@ static inline void tcp_highest_sack_combine(struct sock *sk, tcp_sk(sk)->highest_sack = new; } +/* Determines whether this is a thin stream (which may suffer from + * increased latency). Used to trigger latency-reducing mechanisms. + */ +static inline unsigned int tcp_stream_is_thin(struct tcp_sock *tp) +{ + return tp->packets_out < 4 && !tcp_in_initial_slowstart(tp); +} + /* /proc */ enum tcp_seq_states { TCP_SEQ_STATE_LISTENING, -- cgit v1.2.3-70-g09d2 From 36e31b0af58728071e8023cf8e20c5166b700717 Mon Sep 17 00:00:00 2001 From: Andreas Petlund Date: Thu, 18 Feb 2010 02:47:01 +0000 Subject: net: TCP thin linear timeouts This patch will make TCP use only linear timeouts if the stream is thin. This will help to avoid the very high latencies that thin stream suffer because of exponential backoff. This mechanism is only active if enabled by iocontrol or syscontrol and the stream is identified as thin. A maximum of 6 linear timeouts is tried before exponential backoff is resumed. Signed-off-by: Andreas Petlund Signed-off-by: David S. Miller --- Documentation/networking/ip-sysctl.txt | 12 ++++++++++++ include/linux/tcp.h | 5 ++++- include/net/tcp.h | 4 ++++ net/ipv4/sysctl_net_ipv4.c | 7 +++++++ net/ipv4/tcp.c | 7 +++++++ net/ipv4/tcp_timer.c | 21 ++++++++++++++++++++- 6 files changed, 54 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt index 2dc7a1d9768..f147310d9af 100644 --- a/Documentation/networking/ip-sysctl.txt +++ b/Documentation/networking/ip-sysctl.txt @@ -487,6 +487,18 @@ tcp_dma_copybreak - INTEGER and CONFIG_NET_DMA is enabled. Default: 4096 +tcp_thin_linear_timeouts - BOOLEAN + Enable dynamic triggering of linear timeouts for thin streams. + If set, a check is performed upon retransmission by timeout to + determine if the stream is thin (less than 4 packets in flight). + As long as the stream is found to be thin, up to 6 linear + timeouts may be performed before exponential backoff mode is + initiated. This improves retransmission latency for + non-aggressive thin streams, often found to be time-dependent. + For more information on thin streams, see + Documentation/networking/tcp-thin.txt + Default: 0 + UDP variables: udp_mem - vector of 3 INTEGERs: min, pressure, max diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 7fee8a4df93..3ba8b074612 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -103,6 +103,7 @@ enum { #define TCP_CONGESTION 13 /* Congestion control algorithm */ #define TCP_MD5SIG 14 /* TCP MD5 Signature (RFC2385) */ #define TCP_COOKIE_TRANSACTIONS 15 /* TCP Cookie Transactions */ +#define TCP_THIN_LINEAR_TIMEOUTS 16 /* Use linear timeouts for thin streams*/ /* for TCP_INFO socket option */ #define TCPI_OPT_TIMESTAMPS 1 @@ -340,7 +341,9 @@ struct tcp_sock { u32 frto_highmark; /* snd_nxt when RTO occurred */ u16 advmss; /* Advertised MSS */ u8 frto_counter; /* Number of new acks after RTO */ - u8 nonagle; /* Disable Nagle algorithm? */ + u8 nonagle : 4,/* Disable Nagle algorithm? */ + thin_lto : 1,/* Use linear timeouts for thin streams */ + unused : 3; /* RTT measurement */ u32 srtt; /* smoothed round trip time << 3 */ diff --git a/include/net/tcp.h b/include/net/tcp.h index 0bdc3f64024..6278fc734ab 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -196,6 +196,9 @@ extern void tcp_time_wait(struct sock *sk, int state, int timeo); #define TCP_NAGLE_CORK 2 /* Socket is corked */ #define TCP_NAGLE_PUSH 4 /* Cork is overridden for already queued data */ +/* TCP thin-stream limits */ +#define TCP_THIN_LINEAR_RETRIES 6 /* After 6 linear retries, do exp. backoff */ + extern struct inet_timewait_death_row tcp_death_row; /* sysctl variables for tcp */ @@ -241,6 +244,7 @@ extern int sysctl_tcp_workaround_signed_windows; extern int sysctl_tcp_slow_start_after_idle; extern int sysctl_tcp_max_ssthresh; extern int sysctl_tcp_cookie_size; +extern int sysctl_tcp_thin_linear_timeouts; extern atomic_t tcp_memory_allocated; extern struct percpu_counter tcp_sockets_allocated; diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index 7e3712ce399..e6a2460587d 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -575,6 +575,13 @@ static struct ctl_table ipv4_table[] = { .mode = 0644, .proc_handler = proc_dointvec }, + { + .procname = "tcp_thin_linear_timeouts", + .data = &sysctl_tcp_thin_linear_timeouts, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec + }, { .procname = "udp_mem", .data = &sysctl_udp_mem, diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index e471d037fcc..21bae9afefe 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -2229,6 +2229,13 @@ static int do_tcp_setsockopt(struct sock *sk, int level, } break; + case TCP_THIN_LINEAR_TIMEOUTS: + if (val < 0 || val > 1) + err = -EINVAL; + else + tp->thin_lto = val; + break; + case TCP_CORK: /* When set indicates to always queue non-full frames. * Later the user clears this option and we transmit diff --git a/net/ipv4/tcp_timer.c b/net/ipv4/tcp_timer.c index de7d1bf9114..a17629b8912 100644 --- a/net/ipv4/tcp_timer.c +++ b/net/ipv4/tcp_timer.c @@ -29,6 +29,7 @@ int sysctl_tcp_keepalive_intvl __read_mostly = TCP_KEEPALIVE_INTVL; int sysctl_tcp_retries1 __read_mostly = TCP_RETR1; int sysctl_tcp_retries2 __read_mostly = TCP_RETR2; int sysctl_tcp_orphan_retries __read_mostly; +int sysctl_tcp_thin_linear_timeouts __read_mostly; static void tcp_write_timer(unsigned long); static void tcp_delack_timer(unsigned long); @@ -415,7 +416,25 @@ void tcp_retransmit_timer(struct sock *sk) icsk->icsk_retransmits++; out_reset_timer: - icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX); + /* If stream is thin, use linear timeouts. Since 'icsk_backoff' is + * used to reset timer, set to 0. Recalculate 'icsk_rto' as this + * might be increased if the stream oscillates between thin and thick, + * thus the old value might already be too high compared to the value + * set by 'tcp_set_rto' in tcp_input.c which resets the rto without + * backoff. Limit to TCP_THIN_LINEAR_RETRIES before initiating + * exponential backoff behaviour to avoid continue hammering + * linear-timeout retransmissions into a black hole + */ + if (sk->sk_state == TCP_ESTABLISHED && + (tp->thin_lto || sysctl_tcp_thin_linear_timeouts) && + tcp_stream_is_thin(tp) && + icsk->icsk_retransmits <= TCP_THIN_LINEAR_RETRIES) { + icsk->icsk_backoff = 0; + icsk->icsk_rto = min(__tcp_set_rto(tp), TCP_RTO_MAX); + } else { + /* Use normal (exponential) backoff */ + icsk->icsk_rto = min(icsk->icsk_rto << 1, TCP_RTO_MAX); + } inet_csk_reset_xmit_timer(sk, ICSK_TIME_RETRANS, icsk->icsk_rto, TCP_RTO_MAX); if (retransmits_timed_out(sk, sysctl_tcp_retries1 + 1)) __sk_dst_reset(sk); -- cgit v1.2.3-70-g09d2 From 7e38017557bc0b87434d184f8804cadb102bb903 Mon Sep 17 00:00:00 2001 From: Andreas Petlund Date: Thu, 18 Feb 2010 04:48:19 +0000 Subject: net: TCP thin dupack This patch enables fast retransmissions after one dupACK for TCP if the stream is identified as thin. This will reduce latencies for thin streams that are not able to trigger fast retransmissions due to high packet interarrival time. This mechanism is only active if enabled by iocontrol or syscontrol and the stream is identified as thin. Signed-off-by: Andreas Petlund Signed-off-by: David S. Miller --- Documentation/networking/ip-sysctl.txt | 12 ++++++++++++ include/linux/tcp.h | 4 +++- include/net/tcp.h | 1 + net/ipv4/sysctl_net_ipv4.c | 7 +++++++ net/ipv4/tcp.c | 7 +++++++ net/ipv4/tcp_input.c | 12 ++++++++++++ 6 files changed, 42 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/networking/ip-sysctl.txt b/Documentation/networking/ip-sysctl.txt index f147310d9af..2571a62d923 100644 --- a/Documentation/networking/ip-sysctl.txt +++ b/Documentation/networking/ip-sysctl.txt @@ -499,6 +499,18 @@ tcp_thin_linear_timeouts - BOOLEAN Documentation/networking/tcp-thin.txt Default: 0 +tcp_thin_dupack - BOOLEAN + Enable dynamic triggering of retransmissions after one dupACK + for thin streams. If set, a check is performed upon reception + of a dupACK to determine if the stream is thin (less than 4 + packets in flight). As long as the stream is found to be thin, + data is retransmitted on the first received dupACK. This + improves retransmission latency for non-aggressive thin + streams, often found to be time-dependent. + For more information on thin streams, see + Documentation/networking/tcp-thin.txt + Default: 0 + UDP variables: udp_mem - vector of 3 INTEGERs: min, pressure, max diff --git a/include/linux/tcp.h b/include/linux/tcp.h index 3ba8b074612..a778ee02459 100644 --- a/include/linux/tcp.h +++ b/include/linux/tcp.h @@ -104,6 +104,7 @@ enum { #define TCP_MD5SIG 14 /* TCP MD5 Signature (RFC2385) */ #define TCP_COOKIE_TRANSACTIONS 15 /* TCP Cookie Transactions */ #define TCP_THIN_LINEAR_TIMEOUTS 16 /* Use linear timeouts for thin streams*/ +#define TCP_THIN_DUPACK 17 /* Fast retrans. after 1 dupack */ /* for TCP_INFO socket option */ #define TCPI_OPT_TIMESTAMPS 1 @@ -343,7 +344,8 @@ struct tcp_sock { u8 frto_counter; /* Number of new acks after RTO */ u8 nonagle : 4,/* Disable Nagle algorithm? */ thin_lto : 1,/* Use linear timeouts for thin streams */ - unused : 3; + thin_dupack : 1,/* Fast retransmit on first dupack */ + unused : 2; /* RTT measurement */ u32 srtt; /* smoothed round trip time << 3 */ diff --git a/include/net/tcp.h b/include/net/tcp.h index 6278fc734ab..56f0aec40ed 100644 --- a/include/net/tcp.h +++ b/include/net/tcp.h @@ -245,6 +245,7 @@ extern int sysctl_tcp_slow_start_after_idle; extern int sysctl_tcp_max_ssthresh; extern int sysctl_tcp_cookie_size; extern int sysctl_tcp_thin_linear_timeouts; +extern int sysctl_tcp_thin_dupack; extern atomic_t tcp_memory_allocated; extern struct percpu_counter tcp_sockets_allocated; diff --git a/net/ipv4/sysctl_net_ipv4.c b/net/ipv4/sysctl_net_ipv4.c index e6a2460587d..c1bc074f61b 100644 --- a/net/ipv4/sysctl_net_ipv4.c +++ b/net/ipv4/sysctl_net_ipv4.c @@ -582,6 +582,13 @@ static struct ctl_table ipv4_table[] = { .mode = 0644, .proc_handler = proc_dointvec }, + { + .procname = "tcp_thin_dupack", + .data = &sysctl_tcp_thin_dupack, + .maxlen = sizeof(int), + .mode = 0644, + .proc_handler = proc_dointvec + }, { .procname = "udp_mem", .data = &sysctl_udp_mem, diff --git a/net/ipv4/tcp.c b/net/ipv4/tcp.c index 21bae9afefe..5901010fad5 100644 --- a/net/ipv4/tcp.c +++ b/net/ipv4/tcp.c @@ -2236,6 +2236,13 @@ static int do_tcp_setsockopt(struct sock *sk, int level, tp->thin_lto = val; break; + case TCP_THIN_DUPACK: + if (val < 0 || val > 1) + err = -EINVAL; + else + tp->thin_dupack = val; + break; + case TCP_CORK: /* When set indicates to always queue non-full frames. * Later the user clears this option and we transmit diff --git a/net/ipv4/tcp_input.c b/net/ipv4/tcp_input.c index 3fddc69cccc..788851ca8c5 100644 --- a/net/ipv4/tcp_input.c +++ b/net/ipv4/tcp_input.c @@ -89,6 +89,8 @@ int sysctl_tcp_frto __read_mostly = 2; int sysctl_tcp_frto_response __read_mostly; int sysctl_tcp_nometrics_save __read_mostly; +int sysctl_tcp_thin_dupack __read_mostly; + int sysctl_tcp_moderate_rcvbuf __read_mostly = 1; int sysctl_tcp_abc __read_mostly; @@ -2447,6 +2449,16 @@ static int tcp_time_to_recover(struct sock *sk) return 1; } + /* If a thin stream is detected, retransmit after first + * received dupack. Employ only if SACK is supported in order + * to avoid possible corner-case series of spurious retransmissions + * Use only if there are no unsent data. + */ + if ((tp->thin_dupack || sysctl_tcp_thin_dupack) && + tcp_stream_is_thin(tp) && tcp_dupack_heuristics(tp) > 1 && + tcp_is_sack(tp) && !tcp_send_head(sk)) + return 1; + return 0; } -- cgit v1.2.3-70-g09d2 From 4b3073e1c53a256275f1079c0fbfbe85883d9275 Mon Sep 17 00:00:00 2001 From: Russell King Date: Fri, 18 Dec 2009 16:40:18 +0000 Subject: MM: Pass a PTE pointer to update_mmu_cache() rather than the PTE itself On VIVT ARM, when we have multiple shared mappings of the same file in the same MM, we need to ensure that we have coherency across all copies. We do this via make_coherent() by making the pages uncacheable. This used to work fine, until we allowed highmem with highpte - we now have a page table which is mapped as required, and is not available for modification via update_mmu_cache(). Ralf Beache suggested getting rid of the PTE value passed to update_mmu_cache(): On MIPS update_mmu_cache() calls __update_tlb() which walks pagetables to construct a pointer to the pte again. Passing a pte_t * is much more elegant. Maybe we might even replace the pte argument with the pte_t? Ben Herrenschmidt would also like the pte pointer for PowerPC: Passing the ptep in there is exactly what I want. I want that -instead- of the PTE value, because I have issue on some ppc cases, for I$/D$ coherency, where set_pte_at() may decide to mask out the _PAGE_EXEC. So, pass in the mapped page table pointer into update_mmu_cache(), and remove the PTE value, updating all implementations and call sites to suit. Includes a fix from Stephen Rothwell: sparc: fix fallout from update_mmu_cache API change Signed-off-by: Stephen Rothwell Acked-by: Benjamin Herrenschmidt Signed-off-by: Russell King --- Documentation/cachetlb.txt | 6 +++--- arch/alpha/include/asm/pgtable.h | 2 +- arch/arm/include/asm/tlbflush.h | 3 ++- arch/arm/mm/fault-armv.c | 5 +++-- arch/avr32/include/asm/pgtable.h | 2 +- arch/avr32/mm/tlb.c | 4 ++-- arch/cris/include/asm/pgtable.h | 2 +- arch/frv/include/asm/pgtable.h | 2 +- arch/ia64/include/asm/pgtable.h | 2 +- arch/m32r/include/asm/tlbflush.h | 2 +- arch/m32r/mm/fault-nommu.c | 2 +- arch/m32r/mm/fault.c | 6 +++--- arch/m68k/include/asm/pgtable_mm.h | 2 +- arch/microblaze/include/asm/tlbflush.h | 2 +- arch/mips/include/asm/pgtable.h | 3 ++- arch/mn10300/include/asm/pgtable.h | 2 +- arch/mn10300/mm/mmu-context.c | 3 ++- arch/parisc/include/asm/pgtable.h | 2 +- arch/parisc/kernel/cache.c | 4 ++-- arch/powerpc/include/asm/pgtable.h | 2 +- arch/powerpc/mm/mem.c | 4 ++-- arch/s390/include/asm/pgtable.h | 2 +- arch/score/include/asm/pgtable.h | 3 ++- arch/sh/include/asm/pgtable.h | 3 ++- arch/sh/mm/fault_32.c | 2 +- arch/sparc/include/asm/pgtable_32.h | 4 ++-- arch/sparc/include/asm/pgtable_64.h | 2 +- arch/sparc/mm/fault_32.c | 4 ++-- arch/sparc/mm/init_64.c | 3 ++- arch/sparc/mm/nosun4c.c | 2 +- arch/sparc/mm/srmmu.c | 6 +++--- arch/sparc/mm/sun4c.c | 6 +++--- arch/um/include/asm/pgtable.h | 2 +- arch/x86/include/asm/pgtable_32.h | 2 +- arch/x86/include/asm/pgtable_64.h | 2 +- arch/xtensa/include/asm/pgtable.h | 2 +- arch/xtensa/mm/cache.c | 4 ++-- mm/hugetlb.c | 4 ++-- mm/memory.c | 14 +++++++------- mm/migrate.c | 2 +- 40 files changed, 69 insertions(+), 62 deletions(-) (limited to 'Documentation') diff --git a/Documentation/cachetlb.txt b/Documentation/cachetlb.txt index da42ab414c4..74a8b6fefa2 100644 --- a/Documentation/cachetlb.txt +++ b/Documentation/cachetlb.txt @@ -88,12 +88,12 @@ changes occur: This is used primarily during fault processing. 5) void update_mmu_cache(struct vm_area_struct *vma, - unsigned long address, pte_t pte) + unsigned long address, pte_t *ptep) At the end of every page fault, this routine is invoked to tell the architecture specific code that a translation - described by "pte" now exists at virtual address "address" - for address space "vma->vm_mm", in the software page tables. + now exists at virtual address "address" for address space + "vma->vm_mm", in the software page tables. A port may use this information in any way it so chooses. For example, it could use this event to pre-load TLB diff --git a/arch/alpha/include/asm/pgtable.h b/arch/alpha/include/asm/pgtable.h index 3f0c59f6d8a..71a24329414 100644 --- a/arch/alpha/include/asm/pgtable.h +++ b/arch/alpha/include/asm/pgtable.h @@ -329,7 +329,7 @@ extern pgd_t swapper_pg_dir[1024]; * tables contain all the necessary information. */ extern inline void update_mmu_cache(struct vm_area_struct * vma, - unsigned long address, pte_t pte) + unsigned long address, pte_t *ptep) { } diff --git a/arch/arm/include/asm/tlbflush.h b/arch/arm/include/asm/tlbflush.h index c2f1605de35..e085e2c545e 100644 --- a/arch/arm/include/asm/tlbflush.h +++ b/arch/arm/include/asm/tlbflush.h @@ -529,7 +529,8 @@ extern void flush_tlb_kernel_range(unsigned long start, unsigned long end); * cache entries for the kernels virtual memory range are written * back to the page. */ -extern void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, pte_t pte); +extern void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, + pte_t *ptep); #endif diff --git a/arch/arm/mm/fault-armv.c b/arch/arm/mm/fault-armv.c index ae88f2c3a6d..c45f9bb318a 100644 --- a/arch/arm/mm/fault-armv.c +++ b/arch/arm/mm/fault-armv.c @@ -149,9 +149,10 @@ make_coherent(struct address_space *mapping, struct vm_area_struct *vma, unsigne * * Note that the pte lock will be held. */ -void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, pte_t pte) +void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, + pte_t *ptep) { - unsigned long pfn = pte_pfn(pte); + unsigned long pfn = pte_pfn(*ptep); struct address_space *mapping; struct page *page; diff --git a/arch/avr32/include/asm/pgtable.h b/arch/avr32/include/asm/pgtable.h index fecdda16f44..a9ae30c41e7 100644 --- a/arch/avr32/include/asm/pgtable.h +++ b/arch/avr32/include/asm/pgtable.h @@ -325,7 +325,7 @@ static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) struct vm_area_struct; extern void update_mmu_cache(struct vm_area_struct * vma, - unsigned long address, pte_t pte); + unsigned long address, pte_t *ptep); /* * Encode and decode a swap entry diff --git a/arch/avr32/mm/tlb.c b/arch/avr32/mm/tlb.c index 06677be98ff..0da23109f81 100644 --- a/arch/avr32/mm/tlb.c +++ b/arch/avr32/mm/tlb.c @@ -101,7 +101,7 @@ static void update_dtlb(unsigned long address, pte_t pte) } void update_mmu_cache(struct vm_area_struct *vma, - unsigned long address, pte_t pte) + unsigned long address, pte_t *ptep) { unsigned long flags; @@ -110,7 +110,7 @@ void update_mmu_cache(struct vm_area_struct *vma, return; local_irq_save(flags); - update_dtlb(address, pte); + update_dtlb(address, *ptep); local_irq_restore(flags); } diff --git a/arch/cris/include/asm/pgtable.h b/arch/cris/include/asm/pgtable.h index 1fcce00f01f..99ea6cd1b14 100644 --- a/arch/cris/include/asm/pgtable.h +++ b/arch/cris/include/asm/pgtable.h @@ -270,7 +270,7 @@ extern pgd_t swapper_pg_dir[PTRS_PER_PGD]; /* defined in head.S */ * Actually I am not sure on what this could be used for. */ static inline void update_mmu_cache(struct vm_area_struct * vma, - unsigned long address, pte_t pte) + unsigned long address, pte_t *ptep) { } diff --git a/arch/frv/include/asm/pgtable.h b/arch/frv/include/asm/pgtable.h index 22c60692b55..c18b0d32e63 100644 --- a/arch/frv/include/asm/pgtable.h +++ b/arch/frv/include/asm/pgtable.h @@ -505,7 +505,7 @@ static inline int pte_file(pte_t pte) /* * preload information about a newly instantiated PTE into the SCR0/SCR1 PGE cache */ -static inline void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +static inline void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { struct mm_struct *mm; unsigned long ampr; diff --git a/arch/ia64/include/asm/pgtable.h b/arch/ia64/include/asm/pgtable.h index 69bf13857a9..c3286f42e50 100644 --- a/arch/ia64/include/asm/pgtable.h +++ b/arch/ia64/include/asm/pgtable.h @@ -462,7 +462,7 @@ pte_same (pte_t a, pte_t b) return pte_val(a) == pte_val(b); } -#define update_mmu_cache(vma, address, pte) do { } while (0) +#define update_mmu_cache(vma, address, ptep) do { } while (0) extern pgd_t swapper_pg_dir[PTRS_PER_PGD]; extern void paging_init (void); diff --git a/arch/m32r/include/asm/tlbflush.h b/arch/m32r/include/asm/tlbflush.h index 0ef95307784..92614b0ccf1 100644 --- a/arch/m32r/include/asm/tlbflush.h +++ b/arch/m32r/include/asm/tlbflush.h @@ -92,6 +92,6 @@ static __inline__ void __flush_tlb_all(void) ); } -extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t); +extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t *); #endif /* _ASM_M32R_TLBFLUSH_H */ diff --git a/arch/m32r/mm/fault-nommu.c b/arch/m32r/mm/fault-nommu.c index 88469178ea6..888aab1157e 100644 --- a/arch/m32r/mm/fault-nommu.c +++ b/arch/m32r/mm/fault-nommu.c @@ -95,7 +95,7 @@ asmlinkage void do_page_fault(struct pt_regs *regs, unsigned long error_code, * update_mmu_cache() *======================================================================*/ void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, - pte_t pte) + pte_t *ptep) { BUG(); } diff --git a/arch/m32r/mm/fault.c b/arch/m32r/mm/fault.c index 7274b47f4c2..28ee389e5f5 100644 --- a/arch/m32r/mm/fault.c +++ b/arch/m32r/mm/fault.c @@ -336,7 +336,7 @@ vmalloc_fault: addr = (address & PAGE_MASK); set_thread_fault_code(error_code); - update_mmu_cache(NULL, addr, *pte_k); + update_mmu_cache(NULL, addr, pte_k); set_thread_fault_code(0); return; } @@ -349,7 +349,7 @@ vmalloc_fault: #define ITLB_END (unsigned long *)(ITLB_BASE + (NR_TLB_ENTRIES * 8)) #define DTLB_END (unsigned long *)(DTLB_BASE + (NR_TLB_ENTRIES * 8)) void update_mmu_cache(struct vm_area_struct *vma, unsigned long vaddr, - pte_t pte) + pte_t *ptep) { volatile unsigned long *entry1, *entry2; unsigned long pte_data, flags; @@ -365,7 +365,7 @@ void update_mmu_cache(struct vm_area_struct *vma, unsigned long vaddr, vaddr = (vaddr & PAGE_MASK) | get_asid(); - pte_data = pte_val(pte); + pte_data = pte_val(*ptep); #ifdef CONFIG_CHIP_OPSP entry1 = (unsigned long *)ITLB_BASE; diff --git a/arch/m68k/include/asm/pgtable_mm.h b/arch/m68k/include/asm/pgtable_mm.h index aca0e28581c..87174c904d2 100644 --- a/arch/m68k/include/asm/pgtable_mm.h +++ b/arch/m68k/include/asm/pgtable_mm.h @@ -115,7 +115,7 @@ extern void kernel_set_cachemode(void *addr, unsigned long size, int cmode); * they are updated on demand. */ static inline void update_mmu_cache(struct vm_area_struct *vma, - unsigned long address, pte_t pte) + unsigned long address, pte_t *ptep) { } diff --git a/arch/microblaze/include/asm/tlbflush.h b/arch/microblaze/include/asm/tlbflush.h index eb31a0e8a77..10ec70cd873 100644 --- a/arch/microblaze/include/asm/tlbflush.h +++ b/arch/microblaze/include/asm/tlbflush.h @@ -38,7 +38,7 @@ static inline void local_flush_tlb_range(struct vm_area_struct *vma, #define flush_tlb_kernel_range(start, end) do { } while (0) -#define update_mmu_cache(vma, addr, pte) do { } while (0) +#define update_mmu_cache(vma, addr, ptep) do { } while (0) #define flush_tlb_all local_flush_tlb_all #define flush_tlb_mm local_flush_tlb_mm diff --git a/arch/mips/include/asm/pgtable.h b/arch/mips/include/asm/pgtable.h index 1854336e56a..c56bf8afc09 100644 --- a/arch/mips/include/asm/pgtable.h +++ b/arch/mips/include/asm/pgtable.h @@ -362,8 +362,9 @@ extern void __update_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte); static inline void update_mmu_cache(struct vm_area_struct *vma, - unsigned long address, pte_t pte) + unsigned long address, pte_t *ptep) { + pte_t pte = *ptep; __update_tlb(vma, address, pte); __update_cache(vma, address, pte); } diff --git a/arch/mn10300/include/asm/pgtable.h b/arch/mn10300/include/asm/pgtable.h index 6dc30fc827c..16d88577f3e 100644 --- a/arch/mn10300/include/asm/pgtable.h +++ b/arch/mn10300/include/asm/pgtable.h @@ -466,7 +466,7 @@ static inline int set_kernel_exec(unsigned long vaddr, int enable) * the kernel page tables containing the necessary information by tlb-mn10300.S */ extern void update_mmu_cache(struct vm_area_struct *vma, - unsigned long address, pte_t pte); + unsigned long address, pte_t *ptep); #endif /* !__ASSEMBLY__ */ diff --git a/arch/mn10300/mm/mmu-context.c b/arch/mn10300/mm/mmu-context.c index 31c9d27a75a..36ba02191d4 100644 --- a/arch/mn10300/mm/mmu-context.c +++ b/arch/mn10300/mm/mmu-context.c @@ -51,9 +51,10 @@ void flush_tlb_page(struct vm_area_struct *vma, unsigned long addr) /* * preemptively set a TLB entry */ -void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, pte_t pte) +void update_mmu_cache(struct vm_area_struct *vma, unsigned long addr, pte_t *ptep) { unsigned long pteu, ptel, cnx, flags; + pte_t pte = *ptep; addr &= PAGE_MASK; ptel = pte_val(pte) & ~(xPTEL_UNUSED1 | xPTEL_UNUSED2); diff --git a/arch/parisc/include/asm/pgtable.h b/arch/parisc/include/asm/pgtable.h index a27d2e200fb..01c15035e78 100644 --- a/arch/parisc/include/asm/pgtable.h +++ b/arch/parisc/include/asm/pgtable.h @@ -410,7 +410,7 @@ extern void paging_init (void); #define PG_dcache_dirty PG_arch_1 -extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t); +extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t *); /* Encode and de-code a swap entry */ diff --git a/arch/parisc/kernel/cache.c b/arch/parisc/kernel/cache.c index b6ed34de14e..1054baa2fc6 100644 --- a/arch/parisc/kernel/cache.c +++ b/arch/parisc/kernel/cache.c @@ -68,9 +68,9 @@ flush_cache_all_local(void) EXPORT_SYMBOL(flush_cache_all_local); void -update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { - struct page *page = pte_page(pte); + struct page *page = pte_page(*ptep); if (pfn_valid(page_to_pfn(page)) && page_mapping(page) && test_bit(PG_dcache_dirty, &page->flags)) { diff --git a/arch/powerpc/include/asm/pgtable.h b/arch/powerpc/include/asm/pgtable.h index 21207e54825..89f158731ce 100644 --- a/arch/powerpc/include/asm/pgtable.h +++ b/arch/powerpc/include/asm/pgtable.h @@ -209,7 +209,7 @@ extern void paging_init(void); * corresponding HPTE into the hash table ahead of time, instead of * waiting for the inevitable extra hash-table miss exception. */ -extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t); +extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t *); extern int gup_hugepd(hugepd_t *hugepd, unsigned pdshift, unsigned long addr, unsigned long end, int write, struct page **pages, int *nr); diff --git a/arch/powerpc/mm/mem.c b/arch/powerpc/mm/mem.c index b9b152558f9..311224cdb7a 100644 --- a/arch/powerpc/mm/mem.c +++ b/arch/powerpc/mm/mem.c @@ -494,13 +494,13 @@ EXPORT_SYMBOL(flush_icache_user_range); * This must always be called with the pte lock held. */ void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, - pte_t pte) + pte_t *ptep) { #ifdef CONFIG_PPC_STD_MMU unsigned long access = 0, trap; /* We only want HPTEs for linux PTEs that have _PAGE_ACCESSED set */ - if (!pte_young(pte) || address >= TASK_SIZE) + if (!pte_young(*ptep) || address >= TASK_SIZE) return; /* We try to figure out if we are coming from an instruction diff --git a/arch/s390/include/asm/pgtable.h b/arch/s390/include/asm/pgtable.h index e2fa79cf061..9b5b9189c15 100644 --- a/arch/s390/include/asm/pgtable.h +++ b/arch/s390/include/asm/pgtable.h @@ -43,7 +43,7 @@ extern void vmem_map_init(void); * The S390 doesn't have any external MMU info: the kernel page * tables contain all the necessary information. */ -#define update_mmu_cache(vma, address, pte) do { } while (0) +#define update_mmu_cache(vma, address, ptep) do { } while (0) /* * ZERO_PAGE is a global shared page that is always zero: used diff --git a/arch/score/include/asm/pgtable.h b/arch/score/include/asm/pgtable.h index 674934b4017..ccf38f06c57 100644 --- a/arch/score/include/asm/pgtable.h +++ b/arch/score/include/asm/pgtable.h @@ -272,8 +272,9 @@ extern void __update_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte); static inline void update_mmu_cache(struct vm_area_struct *vma, - unsigned long address, pte_t pte) + unsigned long address, pte_t *ptep) { + pte_t pte = *ptep; __update_tlb(vma, address, pte); __update_cache(vma, address, pte); } diff --git a/arch/sh/include/asm/pgtable.h b/arch/sh/include/asm/pgtable.h index ba3046e4f06..1ff93ac1aa4 100644 --- a/arch/sh/include/asm/pgtable.h +++ b/arch/sh/include/asm/pgtable.h @@ -165,8 +165,9 @@ extern void __update_tlb(struct vm_area_struct *vma, unsigned long address, pte_t pte); static inline void -update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { + pte_t pte = *ptep; __update_cache(vma, address, pte); __update_tlb(vma, address, pte); } diff --git a/arch/sh/mm/fault_32.c b/arch/sh/mm/fault_32.c index 47530104e0a..1677b5ee191 100644 --- a/arch/sh/mm/fault_32.c +++ b/arch/sh/mm/fault_32.c @@ -371,7 +371,7 @@ handle_tlbmiss(struct pt_regs *regs, unsigned long writeaccess, local_flush_tlb_one(get_asid(), address & PAGE_MASK); #endif - update_mmu_cache(NULL, address, entry); + update_mmu_cache(NULL, address, pte); return 0; } diff --git a/arch/sparc/include/asm/pgtable_32.h b/arch/sparc/include/asm/pgtable_32.h index e0cabe790ec..77f906d8cc2 100644 --- a/arch/sparc/include/asm/pgtable_32.h +++ b/arch/sparc/include/asm/pgtable_32.h @@ -330,9 +330,9 @@ BTFIXUPDEF_CALL(void, mmu_info, struct seq_file *) #define FAULT_CODE_WRITE 0x2 #define FAULT_CODE_USER 0x4 -BTFIXUPDEF_CALL(void, update_mmu_cache, struct vm_area_struct *, unsigned long, pte_t) +BTFIXUPDEF_CALL(void, update_mmu_cache, struct vm_area_struct *, unsigned long, pte_t *) -#define update_mmu_cache(vma,addr,pte) BTFIXUP_CALL(update_mmu_cache)(vma,addr,pte) +#define update_mmu_cache(vma,addr,ptep) BTFIXUP_CALL(update_mmu_cache)(vma,addr,ptep) BTFIXUPDEF_CALL(void, sparc_mapiorange, unsigned int, unsigned long, unsigned long, unsigned int) diff --git a/arch/sparc/include/asm/pgtable_64.h b/arch/sparc/include/asm/pgtable_64.h index f3cb790fa2a..f5b5fa76c02 100644 --- a/arch/sparc/include/asm/pgtable_64.h +++ b/arch/sparc/include/asm/pgtable_64.h @@ -706,7 +706,7 @@ extern unsigned long find_ecache_flush_span(unsigned long size); #define mmu_unlockarea(vaddr, len) do { } while(0) struct vm_area_struct; -extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t); +extern void update_mmu_cache(struct vm_area_struct *, unsigned long, pte_t *); /* Encode and de-code a swap entry */ #define __swp_type(entry) (((entry).val >> PAGE_SHIFT) & 0xffUL) diff --git a/arch/sparc/mm/fault_32.c b/arch/sparc/mm/fault_32.c index b99f81c4906..43e20efb251 100644 --- a/arch/sparc/mm/fault_32.c +++ b/arch/sparc/mm/fault_32.c @@ -370,7 +370,7 @@ asmlinkage void do_sun4c_fault(struct pt_regs *regs, int text_fault, int write, unsigned long address) { extern void sun4c_update_mmu_cache(struct vm_area_struct *, - unsigned long,pte_t); + unsigned long,pte_t *); extern pte_t *sun4c_pte_offset_kernel(pmd_t *,unsigned long); struct task_struct *tsk = current; struct mm_struct *mm = tsk->mm; @@ -447,7 +447,7 @@ asmlinkage void do_sun4c_fault(struct pt_regs *regs, int text_fault, int write, * on the CPU and doing a shrink_mmap() on this vma. */ sun4c_update_mmu_cache (find_vma(current->mm, address), address, - *ptep); + ptep); else do_sparc_fault(regs, text_fault, write, address); } diff --git a/arch/sparc/mm/init_64.c b/arch/sparc/mm/init_64.c index 1886d37d411..9245a822a2f 100644 --- a/arch/sparc/mm/init_64.c +++ b/arch/sparc/mm/init_64.c @@ -289,12 +289,13 @@ static void flush_dcache(unsigned long pfn) } } -void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +void update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { struct mm_struct *mm; struct tsb *tsb; unsigned long tag, flags; unsigned long tsb_index, tsb_hash_shift; + pte_t pte = *ptep; if (tlb_type != hypervisor) { unsigned long pfn = pte_pfn(pte); diff --git a/arch/sparc/mm/nosun4c.c b/arch/sparc/mm/nosun4c.c index 196263f895b..4e62c27147c 100644 --- a/arch/sparc/mm/nosun4c.c +++ b/arch/sparc/mm/nosun4c.c @@ -62,7 +62,7 @@ pte_t *sun4c_pte_offset_kernel(pmd_t *dir, unsigned long address) return NULL; } -void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { } diff --git a/arch/sparc/mm/srmmu.c b/arch/sparc/mm/srmmu.c index 367321a030d..df49b200ca4 100644 --- a/arch/sparc/mm/srmmu.c +++ b/arch/sparc/mm/srmmu.c @@ -694,7 +694,7 @@ extern void tsunami_setup_blockops(void); * The following code is a deadwood that may be necessary when * we start to make precise page flushes again. --zaitcev */ -static void swift_update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t pte) +static void swift_update_mmu_cache(struct vm_area_struct * vma, unsigned long address, pte_t *ptep) { #if 0 static unsigned long last; @@ -703,10 +703,10 @@ static void swift_update_mmu_cache(struct vm_area_struct * vma, unsigned long ad if (address == last) { val = srmmu_hwprobe(address); - if (val != 0 && pte_val(pte) != val) { + if (val != 0 && pte_val(*ptep) != val) { printk("swift_update_mmu_cache: " "addr %lx put %08x probed %08x from %p\n", - address, pte_val(pte), val, + address, pte_val(*ptep), val, __builtin_return_address(0)); srmmu_flush_whole_tlb(); } diff --git a/arch/sparc/mm/sun4c.c b/arch/sparc/mm/sun4c.c index a89baf0d875..18652534b91 100644 --- a/arch/sparc/mm/sun4c.c +++ b/arch/sparc/mm/sun4c.c @@ -1887,7 +1887,7 @@ static void sun4c_check_pgt_cache(int low, int high) /* An experiment, turn off by default for now... -DaveM */ #define SUN4C_PRELOAD_PSEG -void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t pte) +void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, pte_t *ptep) { unsigned long flags; int pseg; @@ -1929,7 +1929,7 @@ void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, p start += PAGE_SIZE; } #ifndef SUN4C_PRELOAD_PSEG - sun4c_put_pte(address, pte_val(pte)); + sun4c_put_pte(address, pte_val(*ptep)); #endif local_irq_restore(flags); return; @@ -1940,7 +1940,7 @@ void sun4c_update_mmu_cache(struct vm_area_struct *vma, unsigned long address, p add_lru(entry); } - sun4c_put_pte(address, pte_val(pte)); + sun4c_put_pte(address, pte_val(*ptep)); local_irq_restore(flags); } diff --git a/arch/um/include/asm/pgtable.h b/arch/um/include/asm/pgtable.h index 9ce3f165111..a9f7251b4a8 100644 --- a/arch/um/include/asm/pgtable.h +++ b/arch/um/include/asm/pgtable.h @@ -345,7 +345,7 @@ static inline pte_t pte_modify(pte_t pte, pgprot_t newprot) struct mm_struct; extern pte_t *virt_to_pte(struct mm_struct *mm, unsigned long addr); -#define update_mmu_cache(vma,address,pte) do ; while (0) +#define update_mmu_cache(vma,address,ptep) do ; while (0) /* Encode and de-code a swap entry */ #define __swp_type(x) (((x).val >> 4) & 0x3f) diff --git a/arch/x86/include/asm/pgtable_32.h b/arch/x86/include/asm/pgtable_32.h index 01fd9461d32..a2866839650 100644 --- a/arch/x86/include/asm/pgtable_32.h +++ b/arch/x86/include/asm/pgtable_32.h @@ -80,7 +80,7 @@ do { \ * The i386 doesn't have any external MMU info: the kernel page * tables contain all the necessary information. */ -#define update_mmu_cache(vma, address, pte) do { } while (0) +#define update_mmu_cache(vma, address, ptep) do { } while (0) #endif /* !__ASSEMBLY__ */ diff --git a/arch/x86/include/asm/pgtable_64.h b/arch/x86/include/asm/pgtable_64.h index c57a3011714..181be528c61 100644 --- a/arch/x86/include/asm/pgtable_64.h +++ b/arch/x86/include/asm/pgtable_64.h @@ -129,7 +129,7 @@ static inline int pgd_large(pgd_t pgd) { return 0; } #define pte_unmap(pte) /* NOP */ #define pte_unmap_nested(pte) /* NOP */ -#define update_mmu_cache(vma, address, pte) do { } while (0) +#define update_mmu_cache(vma, address, ptep) do { } while (0) /* Encode and de-code a swap entry */ #if _PAGE_BIT_FILE < _PAGE_BIT_PROTNONE diff --git a/arch/xtensa/include/asm/pgtable.h b/arch/xtensa/include/asm/pgtable.h index a138770c358..76bf3555411 100644 --- a/arch/xtensa/include/asm/pgtable.h +++ b/arch/xtensa/include/asm/pgtable.h @@ -394,7 +394,7 @@ ptep_set_wrprotect(struct mm_struct *mm, unsigned long addr, pte_t *ptep) #define kern_addr_valid(addr) (1) extern void update_mmu_cache(struct vm_area_struct * vma, - unsigned long address, pte_t pte); + unsigned long address, pte_t *ptep); /* * remap a physical page `pfn' of size `size' with page protection `prot' diff --git a/arch/xtensa/mm/cache.c b/arch/xtensa/mm/cache.c index 3ba990c6767..85df4655d32 100644 --- a/arch/xtensa/mm/cache.c +++ b/arch/xtensa/mm/cache.c @@ -147,9 +147,9 @@ void flush_cache_page(struct vm_area_struct* vma, unsigned long address, #endif void -update_mmu_cache(struct vm_area_struct * vma, unsigned long addr, pte_t pte) +update_mmu_cache(struct vm_area_struct * vma, unsigned long addr, pte_t *ptep) { - unsigned long pfn = pte_pfn(pte); + unsigned long pfn = pte_pfn(*ptep); struct page *page; if (!pfn_valid(pfn)) diff --git a/mm/hugetlb.c b/mm/hugetlb.c index e91b81b6367..94cd94df56e 100644 --- a/mm/hugetlb.c +++ b/mm/hugetlb.c @@ -2088,7 +2088,7 @@ static void set_huge_ptep_writable(struct vm_area_struct *vma, entry = pte_mkwrite(pte_mkdirty(huge_ptep_get(ptep))); if (huge_ptep_set_access_flags(vma, address, ptep, entry, 1)) { - update_mmu_cache(vma, address, entry); + update_mmu_cache(vma, address, ptep); } } @@ -2559,7 +2559,7 @@ int hugetlb_fault(struct mm_struct *mm, struct vm_area_struct *vma, entry = pte_mkyoung(entry); if (huge_ptep_set_access_flags(vma, address, ptep, entry, flags & FAULT_FLAG_WRITE)) - update_mmu_cache(vma, address, entry); + update_mmu_cache(vma, address, ptep); out_page_table_lock: spin_unlock(&mm->page_table_lock); diff --git a/mm/memory.c b/mm/memory.c index 09e4b1be7b6..72fb5f39bcc 100644 --- a/mm/memory.c +++ b/mm/memory.c @@ -1593,7 +1593,7 @@ static int insert_pfn(struct vm_area_struct *vma, unsigned long addr, /* Ok, finally just insert the thing.. */ entry = pte_mkspecial(pfn_pte(pfn, prot)); set_pte_at(mm, addr, pte, entry); - update_mmu_cache(vma, addr, entry); /* XXX: why not for insert_page? */ + update_mmu_cache(vma, addr, pte); /* XXX: why not for insert_page? */ retval = 0; out_unlock: @@ -2116,7 +2116,7 @@ reuse: entry = pte_mkyoung(orig_pte); entry = maybe_mkwrite(pte_mkdirty(entry), vma); if (ptep_set_access_flags(vma, address, page_table, entry,1)) - update_mmu_cache(vma, address, entry); + update_mmu_cache(vma, address, page_table); ret |= VM_FAULT_WRITE; goto unlock; } @@ -2185,7 +2185,7 @@ gotten: * new page to be mapped directly into the secondary page table. */ set_pte_at_notify(mm, address, page_table, entry); - update_mmu_cache(vma, address, entry); + update_mmu_cache(vma, address, page_table); if (old_page) { /* * Only after switching the pte to the new page may @@ -2629,7 +2629,7 @@ static int do_swap_page(struct mm_struct *mm, struct vm_area_struct *vma, } /* No need to invalidate - it was non-present before */ - update_mmu_cache(vma, address, pte); + update_mmu_cache(vma, address, page_table); unlock: pte_unmap_unlock(page_table, ptl); out: @@ -2694,7 +2694,7 @@ setpte: set_pte_at(mm, address, page_table, entry); /* No need to invalidate - it was non-present before */ - update_mmu_cache(vma, address, entry); + update_mmu_cache(vma, address, page_table); unlock: pte_unmap_unlock(page_table, ptl); return 0; @@ -2855,7 +2855,7 @@ static int __do_fault(struct mm_struct *mm, struct vm_area_struct *vma, set_pte_at(mm, address, page_table, entry); /* no need to invalidate: a not-present page won't be cached */ - update_mmu_cache(vma, address, entry); + update_mmu_cache(vma, address, page_table); } else { if (charged) mem_cgroup_uncharge_page(page); @@ -2992,7 +2992,7 @@ static inline int handle_pte_fault(struct mm_struct *mm, } entry = pte_mkyoung(entry); if (ptep_set_access_flags(vma, address, pte, entry, flags & FAULT_FLAG_WRITE)) { - update_mmu_cache(vma, address, entry); + update_mmu_cache(vma, address, pte); } else { /* * This is needed only for protection faults but the arch code diff --git a/mm/migrate.c b/mm/migrate.c index efddbf0926b..e58e5da25b9 100644 --- a/mm/migrate.c +++ b/mm/migrate.c @@ -134,7 +134,7 @@ static int remove_migration_pte(struct page *new, struct vm_area_struct *vma, page_add_file_rmap(new); /* No need to invalidate - it was non-present before */ - update_mmu_cache(vma, addr, pte); + update_mmu_cache(vma, addr, ptep); unlock: pte_unmap_unlock(ptep, ptl); out: -- cgit v1.2.3-70-g09d2 From e458b1fadf9239d1fdb165ff4c4ea0d807041bec Mon Sep 17 00:00:00 2001 From: Luke Yelavich Date: Fri, 12 Feb 2010 16:28:29 +1100 Subject: ALSA: hda - Add Macmini 3,1 support BugLink: https://bugs.edge.launchpad.net/ubuntu/+source/linux/+bug/343989 Add a model quirk for the NVIDIA based Macmini hardware, aka Macmini 3,1. The pinout is almost identical to the mb5 quirk, except for no microphone and the line-in mixer controls being on a different index. Everything works in 2ch mode, but as I am not sure what needs to be changed for 6ch mode, or whether the Mac Mini's chip supports 6ch mode, I have simply duplicated the code from the mb5 quirk for the mac mini chmode management. The new model parameter for this quirk is "macmini3". Signed-off-by: Luke Yelavich Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio-Models.txt | 1 + sound/pci/hda/patch_realtek.c | 136 +++++++++++++++++++++++++++ 2 files changed, 137 insertions(+) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio-Models.txt b/Documentation/sound/alsa/HD-Audio-Models.txt index 0c7ebef6289..5efacf01d9e 100644 --- a/Documentation/sound/alsa/HD-Audio-Models.txt +++ b/Documentation/sound/alsa/HD-Audio-Models.txt @@ -124,6 +124,7 @@ ALC882/883/885/888/889 asus-a7m ASUS A7M macpro MacPro support mb5 Macbook 5,1 + macmini3 Macmini 3,1 mbp3 Macbook Pro rev3 imac24 iMac 24'' with jack detection imac91 iMac 9,1 diff --git a/sound/pci/hda/patch_realtek.c b/sound/pci/hda/patch_realtek.c index 0c224977c8c..b5a6ba02593 100644 --- a/sound/pci/hda/patch_realtek.c +++ b/sound/pci/hda/patch_realtek.c @@ -211,6 +211,7 @@ enum { ALC885_MACPRO, ALC885_MBP3, ALC885_MB5, + ALC885_MACMINI3, ALC885_IMAC24, ALC885_IMAC91, ALC883_3ST_2ch_DIG, @@ -6751,6 +6752,14 @@ static struct hda_input_mux mb5_capture_source = { }, }; +static struct hda_input_mux macmini3_capture_source = { + .num_items = 2, + .items = { + { "Line", 0x2 }, + { "CD", 0x4 }, + }, +}; + static struct hda_input_mux alc883_3stack_6ch_intel = { .num_items = 4, .items = { @@ -6999,6 +7008,35 @@ static struct hda_channel_mode alc885_mb5_6ch_modes[2] = { { 6, alc885_mb5_ch6_init }, }; +/* + * 2ch + * Speakers/Woofer/HP = Front + * LineIn = Input + */ +static struct hda_verb alc885_macmini3_ch2_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + { } /* end */ +}; + +/* + * 6ch mode + * Speakers/HP = Front + * Woofer = LFE + * LineIn = Surround + */ +static struct hda_verb alc885_macmini3_ch6_init[] = { + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x15, AC_VERB_SET_CONNECT_SEL, 0x01}, + { } /* end */ +}; + +static struct hda_channel_mode alc885_macmini3_6ch_modes[2] = { + { 2, alc885_mb5_ch2_init }, + { 6, alc885_mb5_ch6_init }, +}; + /* * 2ch mode @@ -7243,6 +7281,21 @@ static struct snd_kcontrol_new alc885_mb5_mixer[] = { { } /* end */ }; +static struct snd_kcontrol_new alc885_macmini3_mixer[] = { + HDA_CODEC_VOLUME("Front Playback Volume", 0x0c, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("Front Playback Switch", 0x0c, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Surround Playback Volume", 0x0d, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("Surround Playback Switch", 0x0d, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("LFE Playback Volume", 0x0e, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("LFE Playback Switch", 0x0e, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Headphone Playback Volume", 0x0f, 0x00, HDA_OUTPUT), + HDA_BIND_MUTE ("Headphone Playback Switch", 0x0f, 0x02, HDA_INPUT), + HDA_CODEC_VOLUME("Line Playback Volume", 0x0b, 0x07, HDA_INPUT), + HDA_CODEC_MUTE ("Line Playback Switch", 0x0b, 0x07, HDA_INPUT), + HDA_CODEC_VOLUME("Line Boost", 0x15, 0x00, HDA_INPUT), + { } /* end */ +}; + static struct snd_kcontrol_new alc885_imac91_mixer[] = { HDA_CODEC_VOLUME("Line-Out Playback Volume", 0x0c, 0x00, HDA_OUTPUT), HDA_BIND_MUTE ("Line-Out Playback Switch", 0x0c, 0x02, HDA_INPUT), @@ -7617,6 +7670,53 @@ static struct hda_verb alc885_mb5_init_verbs[] = { { } }; +/* Macmini 3,1 */ +static struct hda_verb alc885_macmini3_init_verbs[] = { + /* DACs */ + {0x02, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x03, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x04, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x05, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + /* Front mixer */ + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0c, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Surround mixer */ + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0d, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* LFE mixer */ + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0e, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* HP mixer */ + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_ZERO}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(0)}, + {0x0f, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(1)}, + /* Front Pin (0x0c) */ + {0x18, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01}, + {0x18, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x18, AC_VERB_SET_CONNECT_SEL, 0x00}, + /* LFE Pin (0x0e) */ + {0x1a, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT | 0x01}, + {0x1a, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x1a, AC_VERB_SET_CONNECT_SEL, 0x02}, + /* HP Pin (0x0f) */ + {0x14, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_OUT}, + {0x14, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_UNMUTE}, + {0x14, AC_VERB_SET_CONNECT_SEL, 0x03}, + {0x14, AC_VERB_SET_UNSOLICITED_ENABLE, ALC880_HP_EVENT | AC_USRSP_EN}, + /* Line In pin */ + {0x15, AC_VERB_SET_PIN_WIDGET_CONTROL, PIN_IN}, + {0x15, AC_VERB_SET_AMP_GAIN_MUTE, AMP_OUT_MUTE}, + + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_UNMUTE(1)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(3)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(2)}, + {0x24, AC_VERB_SET_AMP_GAIN_MUTE, AMP_IN_MUTE(4)}, + { } +}; + /* Macbook Pro rev3 */ static struct hda_verb alc885_mbp3_init_verbs[] = { /* Front mixer: unmute input/output amp left and right (volume = 0) */ @@ -7800,6 +7900,18 @@ static void alc885_mb5_automute(struct hda_codec *codec) } +static void alc885_macmini3_automute(struct hda_codec *codec) +{ + unsigned int present; + + present = snd_hda_codec_read(codec, 0x14, 0, + AC_VERB_GET_PIN_SENSE, 0) & 0x80000000; + snd_hda_codec_amp_stereo(codec, 0x18, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); + snd_hda_codec_amp_stereo(codec, 0x1a, HDA_OUTPUT, 0, + HDA_AMP_MUTE, present ? HDA_AMP_MUTE : 0); +} + static void alc885_mb5_unsol_event(struct hda_codec *codec, unsigned int res) { @@ -7808,6 +7920,14 @@ static void alc885_mb5_unsol_event(struct hda_codec *codec, alc885_mb5_automute(codec); } +static void alc885_macmini3_unsol_event(struct hda_codec *codec, + unsigned int res) +{ + /* Headphone insertion or removal. */ + if ((res >> 26) == ALC880_HP_EVENT) + alc885_mb5_automute(codec); +} + static void alc885_imac91_automute(struct hda_codec *codec) { unsigned int present; @@ -8974,6 +9094,7 @@ static const char *alc882_models[ALC882_MODEL_LAST] = { [ALC882_ASUS_A7M] = "asus-a7m", [ALC885_MACPRO] = "macpro", [ALC885_MB5] = "mb5", + [ALC885_MACMINI3] = "macmini3", [ALC885_MBP3] = "mbp3", [ALC885_IMAC24] = "imac24", [ALC885_IMAC91] = "imac91", @@ -9157,6 +9278,7 @@ static struct snd_pci_quirk alc882_ssid_cfg_tbl[] = { */ SND_PCI_QUIRK(0x106b, 0x4000, "MacbookPro 5,1", ALC885_MB5), SND_PCI_QUIRK(0x106b, 0x4600, "MacbookPro 5,2", ALC885_MB5), + SND_PCI_QUIRK(0x106b, 0x4100, "Macmini 3,1", ALC885_MACMINI3), {} /* terminator */ }; @@ -9238,6 +9360,20 @@ static struct alc_config_preset alc882_presets[] = { .unsol_event = alc885_mb5_unsol_event, .init_hook = alc885_mb5_automute, }, + [ALC885_MACMINI3] = { + .mixers = { alc885_macmini3_mixer, alc882_chmode_mixer }, + .init_verbs = { alc885_macmini3_init_verbs, + alc880_gpio1_init_verbs }, + .num_dacs = ARRAY_SIZE(alc882_dac_nids), + .dac_nids = alc882_dac_nids, + .channel_mode = alc885_macmini3_6ch_modes, + .num_channel_mode = ARRAY_SIZE(alc885_macmini3_6ch_modes), + .input_mux = &macmini3_capture_source, + .dig_out_nid = ALC882_DIGOUT_NID, + .dig_in_nid = ALC882_DIGIN_NID, + .unsol_event = alc885_macmini3_unsol_event, + .init_hook = alc885_macmini3_automute, + }, [ALC885_MACPRO] = { .mixers = { alc882_macpro_mixer }, .init_verbs = { alc882_macpro_init_verbs }, -- cgit v1.2.3-70-g09d2 From c7f486567c1d0acd2e4166c47069835b9f75e77b Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 17 Feb 2010 23:39:08 +0100 Subject: PCI PM: PCIe PME root port service driver PCIe native PME detection mechanism is based on interrupts generated by root ports or event collectors every time a PCIe device sends a PME message upstream. Once a PME message has been sent by an endpoint device and received by its root port (or event collector in the case of root complex integrated endpoints), the Requester ID from the message header is registered in the root port's Root Status register. At the same time, the PME Status bit of the Root Status register is set to indicate that there's a PME to handle. If PCIe PME interrupt is enabled for the root port, it generates an interrupt once the PME Status has been set. After receiving the interrupt, the kernel can identify the PCIe device that generated the PME using the Requester ID from the root port's Root Status register. [For details, see PCI Express Base Specification, Rev. 2.0.] Implement a driver for the PCIe PME root port service working in accordance with the above description. Based on a patch from Shaohua Li . Signed-off-by: Rafael J. Wysocki Signed-off-by: Jesse Barnes --- Documentation/kernel-parameters.txt | 6 + drivers/pci/pcie/Kconfig | 4 + drivers/pci/pcie/Makefile | 2 + drivers/pci/pcie/pme/Makefile | 8 + drivers/pci/pcie/pme/pcie_pme.c | 493 +++++++++++++++++++++++++++++++++++ drivers/pci/pcie/pme/pcie_pme.h | 28 ++ drivers/pci/pcie/pme/pcie_pme_acpi.c | 54 ++++ include/linux/pci.h | 1 + 8 files changed, 596 insertions(+) create mode 100644 drivers/pci/pcie/pme/Makefile create mode 100644 drivers/pci/pcie/pme/pcie_pme.c create mode 100644 drivers/pci/pcie/pme/pcie_pme.h create mode 100644 drivers/pci/pcie/pme/pcie_pme_acpi.c (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index e7848a0d99e..747a2c8f62f 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1998,6 +1998,12 @@ and is between 256 and 4096 characters. It is defined in the file force Enable ASPM even on devices that claim not to support it. WARNING: Forcing ASPM on may cause system lockups. + pcie_pme= [PCIE,PM] Native PCIe PME signaling options: + off Do not use native PCIe PME signaling. + force Use native PCIe PME signaling even if the BIOS refuses + to allow the kernel to control the relevant PCIe config + registers. + pcmv= [HW,PCMCIA] BadgePAD 4 pd. [PARIDE] diff --git a/drivers/pci/pcie/Kconfig b/drivers/pci/pcie/Kconfig index 5a0c6ad53f8..b8b494b3e0d 100644 --- a/drivers/pci/pcie/Kconfig +++ b/drivers/pci/pcie/Kconfig @@ -46,3 +46,7 @@ config PCIEASPM_DEBUG help This enables PCI Express ASPM debug support. It will add per-device interface to control ASPM. + +config PCIE_PME + def_bool y + depends on PCIEPORTBUS && PM_RUNTIME && EXPERIMENTAL && ACPI diff --git a/drivers/pci/pcie/Makefile b/drivers/pci/pcie/Makefile index 11f6bb1eae2..ea654545e7c 100644 --- a/drivers/pci/pcie/Makefile +++ b/drivers/pci/pcie/Makefile @@ -11,3 +11,5 @@ obj-$(CONFIG_PCIEPORTBUS) += pcieportdrv.o # Build PCI Express AER if needed obj-$(CONFIG_PCIEAER) += aer/ + +obj-$(CONFIG_PCIE_PME) += pme/ diff --git a/drivers/pci/pcie/pme/Makefile b/drivers/pci/pcie/pme/Makefile new file mode 100644 index 00000000000..8b923805308 --- /dev/null +++ b/drivers/pci/pcie/pme/Makefile @@ -0,0 +1,8 @@ +# +# Makefile for PCI-Express Root Port PME signaling driver +# + +obj-$(CONFIG_PCIE_PME) += pmedriver.o + +pmedriver-objs := pcie_pme.o +pmedriver-$(CONFIG_ACPI) += pcie_pme_acpi.o diff --git a/drivers/pci/pcie/pme/pcie_pme.c b/drivers/pci/pcie/pme/pcie_pme.c new file mode 100644 index 00000000000..b5f96fb3cd8 --- /dev/null +++ b/drivers/pci/pcie/pme/pcie_pme.c @@ -0,0 +1,493 @@ +/* + * PCIe Native PME support + * + * Copyright (C) 2007 - 2009 Intel Corp + * Copyright (C) 2007 - 2009 Shaohua Li + * Copyright (C) 2009 Rafael J. Wysocki , Novell Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License V2. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../pci.h" +#include "pcie_pme.h" + +#define PCI_EXP_RTSTA_PME 0x10000 /* PME status */ +#define PCI_EXP_RTSTA_PENDING 0x20000 /* PME pending */ + +/* + * If set, this switch will prevent the PCIe root port PME service driver from + * being registered. Consequently, the interrupt-based PCIe PME signaling will + * not be used by any PCIe root ports in that case. + */ +static bool pcie_pme_disabled; + +/* + * The PCI Express Base Specification 2.0, Section 6.1.8, states the following: + * "In order to maintain compatibility with non-PCI Express-aware system + * software, system power management logic must be configured by firmware to use + * the legacy mechanism of signaling PME by default. PCI Express-aware system + * software must notify the firmware prior to enabling native, interrupt-based + * PME signaling." However, if the platform doesn't provide us with a suitable + * notification mechanism or the notification fails, it is not clear whether or + * not we are supposed to use the interrupt-based PCIe PME signaling. The + * switch below can be used to indicate the desired behaviour. When set, it + * will make the kernel use the interrupt-based PCIe PME signaling regardless of + * the platform notification status, although the kernel will attempt to notify + * the platform anyway. When unset, it will prevent the kernel from using the + * the interrupt-based PCIe PME signaling if the platform notification fails, + * which is the default. + */ +static bool pcie_pme_force_enable; + +static int __init pcie_pme_setup(char *str) +{ + if (!strcmp(str, "off")) + pcie_pme_disabled = true; + else if (!strcmp(str, "force")) + pcie_pme_force_enable = true; + return 1; +} +__setup("pcie_pme=", pcie_pme_setup); + +/** + * pcie_pme_platform_setup - Ensure that the kernel controls the PCIe PME. + * @srv: PCIe PME root port service to use for carrying out the check. + * + * Notify the platform that the native PCIe PME is going to be used and return + * 'true' if the control of the PCIe PME registers has been acquired from the + * platform. + */ +static bool pcie_pme_platform_setup(struct pcie_device *srv) +{ + return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable; +} + +struct pcie_pme_service_data { + spinlock_t lock; + struct pcie_device *srv; + struct work_struct work; + bool noirq; /* Don't enable the PME interrupt used by this service. */ +}; + +/** + * pcie_pme_interrupt_enable - Enable/disable PCIe PME interrupt generation. + * @dev: PCIe root port or event collector. + * @enable: Enable or disable the interrupt. + */ +static void pcie_pme_interrupt_enable(struct pci_dev *dev, bool enable) +{ + int rtctl_pos; + u16 rtctl; + + rtctl_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTCTL; + + pci_read_config_word(dev, rtctl_pos, &rtctl); + if (enable) + rtctl |= PCI_EXP_RTCTL_PMEIE; + else + rtctl &= ~PCI_EXP_RTCTL_PMEIE; + pci_write_config_word(dev, rtctl_pos, rtctl); +} + +/** + * pcie_pme_clear_status - Clear root port PME interrupt status. + * @dev: PCIe root port or event collector. + */ +static void pcie_pme_clear_status(struct pci_dev *dev) +{ + int rtsta_pos; + u32 rtsta; + + rtsta_pos = pci_find_capability(dev, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA; + + pci_read_config_dword(dev, rtsta_pos, &rtsta); + rtsta |= PCI_EXP_RTSTA_PME; + pci_write_config_dword(dev, rtsta_pos, rtsta); +} + +/** + * pcie_pme_walk_bus - Scan a PCI bus for devices asserting PME#. + * @bus: PCI bus to scan. + * + * Scan given PCI bus and all buses under it for devices asserting PME#. + */ +static bool pcie_pme_walk_bus(struct pci_bus *bus) +{ + struct pci_dev *dev; + bool ret = false; + + list_for_each_entry(dev, &bus->devices, bus_list) { + /* Skip PCIe devices in case we started from a root port. */ + if (!dev->is_pcie && pci_check_pme_status(dev)) { + pm_request_resume(&dev->dev); + ret = true; + } + + if (dev->subordinate && pcie_pme_walk_bus(dev->subordinate)) + ret = true; + } + + return ret; +} + +/** + * pcie_pme_from_pci_bridge - Check if PCIe-PCI bridge generated a PME. + * @bus: Secondary bus of the bridge. + * @devfn: Device/function number to check. + * + * PME from PCI devices under a PCIe-PCI bridge may be converted to an in-band + * PCIe PME message. In such that case the bridge should use the Requester ID + * of device/function number 0 on its secondary bus. + */ +static bool pcie_pme_from_pci_bridge(struct pci_bus *bus, u8 devfn) +{ + struct pci_dev *dev; + bool found = false; + + if (devfn) + return false; + + dev = pci_dev_get(bus->self); + if (!dev) + return false; + + if (dev->is_pcie && dev->pcie_type == PCI_EXP_TYPE_PCI_BRIDGE) { + down_read(&pci_bus_sem); + if (pcie_pme_walk_bus(bus)) + found = true; + up_read(&pci_bus_sem); + } + + pci_dev_put(dev); + return found; +} + +/** + * pcie_pme_handle_request - Find device that generated PME and handle it. + * @port: Root port or event collector that generated the PME interrupt. + * @req_id: PCIe Requester ID of the device that generated the PME. + */ +static void pcie_pme_handle_request(struct pci_dev *port, u16 req_id) +{ + u8 busnr = req_id >> 8, devfn = req_id & 0xff; + struct pci_bus *bus; + struct pci_dev *dev; + bool found = false; + + /* First, check if the PME is from the root port itself. */ + if (port->devfn == devfn && port->bus->number == busnr) { + if (pci_check_pme_status(port)) { + pm_request_resume(&port->dev); + found = true; + } else { + /* + * Apparently, the root port generated the PME on behalf + * of a non-PCIe device downstream. If this is done by + * a root port, the Requester ID field in its status + * register may contain either the root port's, or the + * source device's information (PCI Express Base + * Specification, Rev. 2.0, Section 6.1.9). + */ + down_read(&pci_bus_sem); + found = pcie_pme_walk_bus(port->subordinate); + up_read(&pci_bus_sem); + } + goto out; + } + + /* Second, find the bus the source device is on. */ + bus = pci_find_bus(pci_domain_nr(port->bus), busnr); + if (!bus) + goto out; + + /* Next, check if the PME is from a PCIe-PCI bridge. */ + found = pcie_pme_from_pci_bridge(bus, devfn); + if (found) + goto out; + + /* Finally, try to find the PME source on the bus. */ + down_read(&pci_bus_sem); + list_for_each_entry(dev, &bus->devices, bus_list) { + pci_dev_get(dev); + if (dev->devfn == devfn) { + found = true; + break; + } + pci_dev_put(dev); + } + up_read(&pci_bus_sem); + + if (found) { + /* The device is there, but we have to check its PME status. */ + found = pci_check_pme_status(dev); + if (found) + pm_request_resume(&dev->dev); + pci_dev_put(dev); + } else if (devfn) { + /* + * The device is not there, but we can still try to recover by + * assuming that the PME was reported by a PCIe-PCI bridge that + * used devfn different from zero. + */ + dev_dbg(&port->dev, "PME interrupt generated for " + "non-existent device %02x:%02x.%d\n", + busnr, PCI_SLOT(devfn), PCI_FUNC(devfn)); + found = pcie_pme_from_pci_bridge(bus, 0); + } + + out: + if (!found) + dev_dbg(&port->dev, "Spurious native PME interrupt!\n"); +} + +/** + * pcie_pme_work_fn - Work handler for PCIe PME interrupt. + * @work: Work structure giving access to service data. + */ +static void pcie_pme_work_fn(struct work_struct *work) +{ + struct pcie_pme_service_data *data = + container_of(work, struct pcie_pme_service_data, work); + struct pci_dev *port = data->srv->port; + int rtsta_pos; + u32 rtsta; + + rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA; + + spin_lock_irq(&data->lock); + + for (;;) { + if (data->noirq) + break; + + pci_read_config_dword(port, rtsta_pos, &rtsta); + if (rtsta & PCI_EXP_RTSTA_PME) { + /* + * Clear PME status of the port. If there are other + * pending PMEs, the status will be set again. + */ + pcie_pme_clear_status(port); + + spin_unlock_irq(&data->lock); + pcie_pme_handle_request(port, rtsta & 0xffff); + spin_lock_irq(&data->lock); + + continue; + } + + /* No need to loop if there are no more PMEs pending. */ + if (!(rtsta & PCI_EXP_RTSTA_PENDING)) + break; + + spin_unlock_irq(&data->lock); + cpu_relax(); + spin_lock_irq(&data->lock); + } + + if (!data->noirq) + pcie_pme_interrupt_enable(port, true); + + spin_unlock_irq(&data->lock); +} + +/** + * pcie_pme_irq - Interrupt handler for PCIe root port PME interrupt. + * @irq: Interrupt vector. + * @context: Interrupt context pointer. + */ +static irqreturn_t pcie_pme_irq(int irq, void *context) +{ + struct pci_dev *port; + struct pcie_pme_service_data *data; + int rtsta_pos; + u32 rtsta; + unsigned long flags; + + port = ((struct pcie_device *)context)->port; + data = get_service_data((struct pcie_device *)context); + + rtsta_pos = pci_find_capability(port, PCI_CAP_ID_EXP) + PCI_EXP_RTSTA; + + spin_lock_irqsave(&data->lock, flags); + pci_read_config_dword(port, rtsta_pos, &rtsta); + + if (!(rtsta & PCI_EXP_RTSTA_PME)) { + spin_unlock_irqrestore(&data->lock, flags); + return IRQ_NONE; + } + + pcie_pme_interrupt_enable(port, false); + spin_unlock_irqrestore(&data->lock, flags); + + /* We don't use pm_wq, because it's freezable. */ + schedule_work(&data->work); + + return IRQ_HANDLED; +} + +/** + * pcie_pme_set_native - Set the PME interrupt flag for given device. + * @dev: PCI device to handle. + * @ign: Ignored. + */ +static int pcie_pme_set_native(struct pci_dev *dev, void *ign) +{ + dev_info(&dev->dev, "Signaling PME through PCIe PME interrupt\n"); + + device_set_run_wake(&dev->dev, true); + dev->pme_interrupt = true; + return 0; +} + +/** + * pcie_pme_mark_devices - Set the PME interrupt flag for devices below a port. + * @port: PCIe root port or event collector to handle. + * + * For each device below given root port, including the port itself (or for each + * root complex integrated endpoint if @port is a root complex event collector) + * set the flag indicating that it can signal run-time wake-up events via PCIe + * PME interrupts. + */ +static void pcie_pme_mark_devices(struct pci_dev *port) +{ + pcie_pme_set_native(port, NULL); + if (port->subordinate) { + pci_walk_bus(port->subordinate, pcie_pme_set_native, NULL); + } else { + struct pci_bus *bus = port->bus; + struct pci_dev *dev; + + /* Check if this is a root port event collector. */ + if (port->pcie_type != PCI_EXP_TYPE_RC_EC || !bus) + return; + + down_read(&pci_bus_sem); + list_for_each_entry(dev, &bus->devices, bus_list) + if (dev->is_pcie + && dev->pcie_type == PCI_EXP_TYPE_RC_END) + pcie_pme_set_native(dev, NULL); + up_read(&pci_bus_sem); + } +} + +/** + * pcie_pme_probe - Initialize PCIe PME service for given root port. + * @srv: PCIe service to initialize. + */ +static int pcie_pme_probe(struct pcie_device *srv) +{ + struct pci_dev *port; + struct pcie_pme_service_data *data; + int ret; + + if (!pcie_pme_platform_setup(srv)) + return -EACCES; + + data = kzalloc(sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + spin_lock_init(&data->lock); + INIT_WORK(&data->work, pcie_pme_work_fn); + data->srv = srv; + set_service_data(srv, data); + + port = srv->port; + pcie_pme_interrupt_enable(port, false); + pcie_pme_clear_status(port); + + ret = request_irq(srv->irq, pcie_pme_irq, IRQF_SHARED, "PCIe PME", srv); + if (ret) { + kfree(data); + } else { + pcie_pme_mark_devices(port); + pcie_pme_interrupt_enable(port, true); + } + + return ret; +} + +/** + * pcie_pme_suspend - Suspend PCIe PME service device. + * @srv: PCIe service device to suspend. + */ +static int pcie_pme_suspend(struct pcie_device *srv) +{ + struct pcie_pme_service_data *data = get_service_data(srv); + struct pci_dev *port = srv->port; + + spin_lock_irq(&data->lock); + pcie_pme_interrupt_enable(port, false); + pcie_pme_clear_status(port); + data->noirq = true; + spin_unlock_irq(&data->lock); + + synchronize_irq(srv->irq); + + return 0; +} + +/** + * pcie_pme_resume - Resume PCIe PME service device. + * @srv - PCIe service device to resume. + */ +static int pcie_pme_resume(struct pcie_device *srv) +{ + struct pcie_pme_service_data *data = get_service_data(srv); + struct pci_dev *port = srv->port; + + spin_lock_irq(&data->lock); + data->noirq = false; + pcie_pme_clear_status(port); + pcie_pme_interrupt_enable(port, true); + spin_unlock_irq(&data->lock); + + return 0; +} + +/** + * pcie_pme_remove - Prepare PCIe PME service device for removal. + * @srv - PCIe service device to resume. + */ +static void pcie_pme_remove(struct pcie_device *srv) +{ + pcie_pme_suspend(srv); + free_irq(srv->irq, srv); + kfree(get_service_data(srv)); +} + +static struct pcie_port_service_driver pcie_pme_driver = { + .name = "pcie_pme", + .port_type = PCI_EXP_TYPE_ROOT_PORT, + .service = PCIE_PORT_SERVICE_PME, + + .probe = pcie_pme_probe, + .suspend = pcie_pme_suspend, + .resume = pcie_pme_resume, + .remove = pcie_pme_remove, +}; + +/** + * pcie_pme_service_init - Register the PCIe PME service driver. + */ +static int __init pcie_pme_service_init(void) +{ + return pcie_pme_disabled ? + -ENODEV : pcie_port_service_register(&pcie_pme_driver); +} + +module_init(pcie_pme_service_init); diff --git a/drivers/pci/pcie/pme/pcie_pme.h b/drivers/pci/pcie/pme/pcie_pme.h new file mode 100644 index 00000000000..b30d2b7c977 --- /dev/null +++ b/drivers/pci/pcie/pme/pcie_pme.h @@ -0,0 +1,28 @@ +/* + * drivers/pci/pcie/pme/pcie_pme.h + * + * PCI Express Root Port PME signaling support + * + * Copyright (C) 2009 Rafael J. Wysocki , Novell Inc. + */ + +#ifndef _PCIE_PME_H_ +#define _PCIE_PME_H_ + +struct pcie_device; + +#ifdef CONFIG_ACPI +extern int pcie_pme_acpi_setup(struct pcie_device *srv); + +static inline int pcie_pme_platform_notify(struct pcie_device *srv) +{ + return pcie_pme_acpi_setup(srv); +} +#else /* !CONFIG_ACPI */ +static inline int pcie_pme_platform_notify(struct pcie_device *srv) +{ + return 0; +} +#endif /* !CONFIG_ACPI */ + +#endif diff --git a/drivers/pci/pcie/pme/pcie_pme_acpi.c b/drivers/pci/pcie/pme/pcie_pme_acpi.c new file mode 100644 index 00000000000..83ab2287ae3 --- /dev/null +++ b/drivers/pci/pcie/pme/pcie_pme_acpi.c @@ -0,0 +1,54 @@ +/* + * PCIe Native PME support, ACPI-related part + * + * Copyright (C) 2009 Rafael J. Wysocki , Novell Inc. + * + * This file is subject to the terms and conditions of the GNU General Public + * License V2. See the file "COPYING" in the main directory of this archive + * for more details. + */ + +#include +#include +#include +#include +#include +#include + +/** + * pcie_pme_acpi_setup - Request the ACPI BIOS to release control over PCIe PME. + * @srv - PCIe PME service for a root port or event collector. + * + * Invoked when the PCIe bus type loads PCIe PME service driver. To avoid + * conflict with the BIOS PCIe support requires the BIOS to yield PCIe PME + * control to the kernel. + */ +int pcie_pme_acpi_setup(struct pcie_device *srv) +{ + acpi_status status = AE_NOT_FOUND; + struct pci_dev *port = srv->port; + acpi_handle handle; + int error = 0; + + if (acpi_pci_disabled) + return -ENOSYS; + + dev_info(&port->dev, "Requesting control of PCIe PME from ACPI BIOS\n"); + + handle = acpi_find_root_bridge_handle(port); + if (!handle) + return -EINVAL; + + status = acpi_pci_osc_control_set(handle, + OSC_PCI_EXPRESS_PME_CONTROL | + OSC_PCI_EXPRESS_CAP_STRUCTURE_CONTROL); + if (ACPI_FAILURE(status)) { + dev_info(&port->dev, + "Failed to receive control of PCIe PME service: %s\n", + (status == AE_SUPPORT || status == AE_NOT_FOUND) ? + "no _OSC support" : "ACPI _OSC failed"); + error = -ENODEV; + } + + return error; +} diff --git a/include/linux/pci.h b/include/linux/pci.h index 578a0770961..9fe4b2089b7 100644 --- a/include/linux/pci.h +++ b/include/linux/pci.h @@ -266,6 +266,7 @@ struct pci_dev { configuration space */ unsigned int pme_support:5; /* Bitmask of states from which PME# can be generated */ + unsigned int pme_interrupt:1; unsigned int d1_support:1; /* Low power state D1 is supported */ unsigned int d2_support:1; /* Low power state D2 is supported */ unsigned int no_d1d2:1; /* Only allow D0 and D3 */ -- cgit v1.2.3-70-g09d2 From c39fae1416d59fd565606793f090cebe3720d50d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Wed, 17 Feb 2010 23:40:07 +0100 Subject: PCI PM: Make it possible to force using INTx for PCIe PME signaling Apparently, some machines may have problems with PCI run-time power management if MSIs are used for the native PCIe PME signaling. In particular, on the MSI Wind U-100 PCIe PME interrupts are not generated by a PCIe root port after a resume from suspend to RAM, if the system wake-up was triggered by a PME from the device attached to this port. [It doesn't help to free the interrupt on suspend and request it back on resume, even if that is done along with disabling the MSI and re-enabling it, respectively.] However, if INTx interrupts are used for this purpose on the same machine, everything works just fine. For this reason, add a kernel command line switch allowing one to request that MSIs be not used for the native PCIe PME signaling, introduce a DMI table allowing us to blacklist machines that need this switch to be set by default and put the MSI Wind U-100 into this table. Signed-off-by: Rafael J. Wysocki Signed-off-by: Jesse Barnes --- Documentation/kernel-parameters.txt | 2 ++ drivers/pci/pcie/pme/pcie_pme.c | 14 +++++++++++++- drivers/pci/pcie/portdrv.h | 17 +++++++++++++++++ drivers/pci/pcie/portdrv_core.c | 12 ++++++++++-- drivers/pci/pcie/portdrv_pci.c | 27 +++++++++++++++++++++++++++ 5 files changed, 69 insertions(+), 3 deletions(-) (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 747a2c8f62f..516225a864f 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2003,6 +2003,8 @@ and is between 256 and 4096 characters. It is defined in the file force Use native PCIe PME signaling even if the BIOS refuses to allow the kernel to control the relevant PCIe config registers. + nomsi Do not use MSI for native PCIe PME signaling (this makes + all PCIe root ports use INTx for everything). pcmv= [HW,PCMCIA] BadgePAD 4 diff --git a/drivers/pci/pcie/pme/pcie_pme.c b/drivers/pci/pcie/pme/pcie_pme.c index b5f96fb3cd8..51a69061b12 100644 --- a/drivers/pci/pcie/pme/pcie_pme.c +++ b/drivers/pci/pcie/pme/pcie_pme.c @@ -53,12 +53,22 @@ static bool pcie_pme_disabled; */ static bool pcie_pme_force_enable; +/* + * If this switch is set, MSI will not be used for PCIe PME signaling. This + * causes the PCIe port driver to use INTx interrupts only, but it turns out + * that using MSI for PCIe PME signaling doesn't play well with PCIe PME-based + * wake-up from system sleep states. + */ +bool pcie_pme_msi_disabled; + static int __init pcie_pme_setup(char *str) { if (!strcmp(str, "off")) pcie_pme_disabled = true; else if (!strcmp(str, "force")) pcie_pme_force_enable = true; + else if (!strcmp(str, "nomsi")) + pcie_pme_msi_disabled = true; return 1; } __setup("pcie_pme=", pcie_pme_setup); @@ -73,7 +83,9 @@ __setup("pcie_pme=", pcie_pme_setup); */ static bool pcie_pme_platform_setup(struct pcie_device *srv) { - return !pcie_pme_platform_notify(srv) || pcie_pme_force_enable; + if (!pcie_pme_platform_notify(srv)) + return true; + return pcie_pme_force_enable; } struct pcie_pme_service_data { diff --git a/drivers/pci/pcie/portdrv.h b/drivers/pci/pcie/portdrv.h index aaeb9d21cba..813a5c3427b 100644 --- a/drivers/pci/pcie/portdrv.h +++ b/drivers/pci/pcie/portdrv.h @@ -30,4 +30,21 @@ extern void pcie_port_device_remove(struct pci_dev *dev); extern int __must_check pcie_port_bus_register(void); extern void pcie_port_bus_unregister(void); +#ifdef CONFIG_PCIE_PME +extern bool pcie_pme_msi_disabled; + +static inline void pcie_pme_disable_msi(void) +{ + pcie_pme_msi_disabled = true; +} + +static inline bool pcie_pme_no_msi(void) +{ + return pcie_pme_msi_disabled; +} +#else /* !CONFIG_PCIE_PME */ +static inline void pcie_pme_disable_msi(void) {} +static inline bool pcie_pme_no_msi(void) { return false; } +#endif /* !CONFIG_PCIE_PME */ + #endif /* _PORTDRV_H_ */ diff --git a/drivers/pci/pcie/portdrv_core.c b/drivers/pci/pcie/portdrv_core.c index b174188ac12..0d34ff41539 100644 --- a/drivers/pci/pcie/portdrv_core.c +++ b/drivers/pci/pcie/portdrv_core.c @@ -186,16 +186,24 @@ static int pcie_port_enable_msix(struct pci_dev *dev, int *vectors, int mask) */ static int init_service_irqs(struct pci_dev *dev, int *irqs, int mask) { - int i, irq; + int i, irq = -1; + + /* We have to use INTx if MSI cannot be used for PCIe PME. */ + if ((mask & PCIE_PORT_SERVICE_PME) && pcie_pme_no_msi()) { + if (dev->pin) + irq = dev->irq; + goto no_msi; + } /* Try to use MSI-X if supported */ if (!pcie_port_enable_msix(dev, irqs, mask)) return 0; + /* We're not going to use MSI-X, so try MSI and fall back to INTx */ - irq = -1; if (!pci_enable_msi(dev) || dev->pin) irq = dev->irq; + no_msi: for (i = 0; i < PCIE_PORT_DEVICE_MAXSERVICES; i++) irqs[i] = irq; irqs[PCIE_PORT_SERVICE_VC_SHIFT] = -1; diff --git a/drivers/pci/pcie/portdrv_pci.c b/drivers/pci/pcie/portdrv_pci.c index 13c8972886e..127e8f169d9 100644 --- a/drivers/pci/pcie/portdrv_pci.c +++ b/drivers/pci/pcie/portdrv_pci.c @@ -15,6 +15,7 @@ #include #include #include +#include #include "portdrv.h" #include "aer/aerdrv.h" @@ -273,10 +274,36 @@ static struct pci_driver pcie_portdriver = { .driver.pm = PCIE_PORTDRV_PM_OPS, }; +static int __init dmi_pcie_pme_disable_msi(const struct dmi_system_id *d) +{ + pr_notice("%s detected: will not use MSI for PCIe PME signaling\n", + d->ident); + pcie_pme_disable_msi(); + return 0; +} + +static struct dmi_system_id __initdata pcie_portdrv_dmi_table[] = { + /* + * Boxes that should not use MSI for PCIe PME signaling. + */ + { + .callback = dmi_pcie_pme_disable_msi, + .ident = "MSI Wind U-100", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, + "MICRO-STAR INTERNATIONAL CO., LTD"), + DMI_MATCH(DMI_PRODUCT_NAME, "U-100"), + }, + }, + {} +}; + static int __init pcie_portdrv_init(void) { int retval; + dmi_check_system(pcie_portdrv_dmi_table); + retval = pcie_port_bus_register(); if (retval) { printk(KERN_WARNING "PCIE: bus_register error: %d\n", retval); -- cgit v1.2.3-70-g09d2 From 0d7d8bf3b896dd8752284e2a7639a03936085e5f Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Tue, 23 Feb 2010 10:55:46 +0100 Subject: ALSA: hda - Add missing description in HD-Audio-Models.txt Signed-off-by: Takashi Iwai --- Documentation/sound/alsa/HD-Audio-Models.txt | 1 + 1 file changed, 1 insertion(+) (limited to 'Documentation') diff --git a/Documentation/sound/alsa/HD-Audio-Models.txt b/Documentation/sound/alsa/HD-Audio-Models.txt index 5efacf01d9e..1d38b0dfba9 100644 --- a/Documentation/sound/alsa/HD-Audio-Models.txt +++ b/Documentation/sound/alsa/HD-Audio-Models.txt @@ -125,6 +125,7 @@ ALC882/883/885/888/889 macpro MacPro support mb5 Macbook 5,1 macmini3 Macmini 3,1 + mba21 Macbook Air 2,1 mbp3 Macbook Pro rev3 imac24 iMac 24'' with jack detection imac91 iMac 9,1 -- cgit v1.2.3-70-g09d2 From 7bc5e3f2be32ae6fb0c74cd0f707f986b3a01a26 Mon Sep 17 00:00:00 2001 From: Bjorn Helgaas Date: Tue, 23 Feb 2010 10:24:41 -0700 Subject: x86/PCI: use host bridge _CRS info by default on 2008 and newer machines The main benefit of using ACPI host bridge window information is that we can do better resource allocation in systems with multiple host bridges, e.g., http://bugzilla.kernel.org/show_bug.cgi?id=14183 Sometimes we need _CRS information even if we only have one host bridge, e.g., https://bugs.launchpad.net/ubuntu/+source/linux/+bug/341681 Most of these systems are relatively new, so this patch turns on "pci=use_crs" only on machines with a BIOS date of 2008 or newer. Signed-off-by: Bjorn Helgaas Signed-off-by: Jesse Barnes --- Documentation/kernel-parameters.txt | 8 ++++-- arch/ia64/include/asm/acpi.h | 1 + arch/x86/include/asm/pci_x86.h | 1 + arch/x86/pci/acpi.c | 53 ++++++++++++++++++++++++++++++++----- arch/x86/pci/common.c | 3 +++ drivers/acpi/pci_root.c | 1 + include/acpi/acpi_drivers.h | 1 + 7 files changed, 60 insertions(+), 8 deletions(-) (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 516225a864f..3e69c1c4f50 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -1948,8 +1948,12 @@ and is between 256 and 4096 characters. It is defined in the file IRQ routing is enabled. noacpi [X86] Do not use ACPI for IRQ routing or for PCI scanning. - use_crs [X86] Use _CRS for PCI resource - allocation. + use_crs [X86] Use PCI host bridge window information + from ACPI. On BIOSes from 2008 or later, this + is enabled by default. If you need to use this, + please report a bug. + nocrs [X86] Ignore PCI host bridge windows from ACPI. + If you need to use this, please report a bug. routeirq Do IRQ routing for all PCI devices. This is normally done in pci_enable_device(), so this option is a temporary workaround diff --git a/arch/ia64/include/asm/acpi.h b/arch/ia64/include/asm/acpi.h index e97b255d97b..93997bd5edc 100644 --- a/arch/ia64/include/asm/acpi.h +++ b/arch/ia64/include/asm/acpi.h @@ -98,6 +98,7 @@ ia64_acpi_release_global_lock (unsigned int *lock) #endif #define acpi_processor_cstate_check(x) (x) /* no idle limits on IA64 :) */ static inline void disable_acpi(void) { } +static inline void pci_acpi_crs_quirks(void) { } const char *acpi_get_sysname (void); int acpi_request_vector (u32 int_type); diff --git a/arch/x86/include/asm/pci_x86.h b/arch/x86/include/asm/pci_x86.h index b4bf9a942ed..05b58ccb2e8 100644 --- a/arch/x86/include/asm/pci_x86.h +++ b/arch/x86/include/asm/pci_x86.h @@ -29,6 +29,7 @@ #define PCI_CHECK_ENABLE_AMD_MMCONF 0x20000 #define PCI_HAS_IO_ECS 0x40000 #define PCI_NOASSIGN_ROMS 0x80000 +#define PCI_ROOT_NO_CRS 0x100000 extern unsigned int pci_probe; extern unsigned long pirq_table_addr; diff --git a/arch/x86/pci/acpi.c b/arch/x86/pci/acpi.c index a2f8cdb8c1d..5f11ff6f538 100644 --- a/arch/x86/pci/acpi.c +++ b/arch/x86/pci/acpi.c @@ -15,6 +15,51 @@ struct pci_root_info { int busnum; }; +static bool pci_use_crs = true; + +static int __init set_use_crs(const struct dmi_system_id *id) +{ + pci_use_crs = true; + return 0; +} + +static const struct dmi_system_id pci_use_crs_table[] __initconst = { + /* http://bugzilla.kernel.org/show_bug.cgi?id=14183 */ + { + .callback = set_use_crs, + .ident = "IBM System x3800", + .matches = { + DMI_MATCH(DMI_SYS_VENDOR, "IBM"), + DMI_MATCH(DMI_PRODUCT_NAME, "x3800"), + }, + }, + {} +}; + +void __init pci_acpi_crs_quirks(void) +{ + int year; + + if (dmi_get_date(DMI_BIOS_DATE, &year, NULL, NULL) && year < 2008) + pci_use_crs = false; + + dmi_check_system(pci_use_crs_table); + + /* + * If the user specifies "pci=use_crs" or "pci=nocrs" explicitly, that + * takes precedence over anything we figured out above. + */ + if (pci_probe & PCI_ROOT_NO_CRS) + pci_use_crs = false; + else if (pci_probe & PCI_USE__CRS) + pci_use_crs = true; + + printk(KERN_INFO "PCI: %s host bridge windows from ACPI; " + "if necessary, use \"pci=%s\" and report a bug\n", + pci_use_crs ? "Using" : "Ignoring", + pci_use_crs ? "nocrs" : "use_crs"); +} + static acpi_status resource_to_addr(struct acpi_resource *resource, struct acpi_resource_address64 *addr) @@ -106,7 +151,7 @@ setup_resource(struct acpi_resource *acpi_res, void *data) res->child = NULL; align_resource(info->bridge, res); - if (!(pci_probe & PCI_USE__CRS)) { + if (!pci_use_crs) { dev_printk(KERN_DEBUG, &info->bridge->dev, "host bridge window %pR (ignored)\n", res); return AE_OK; @@ -137,12 +182,8 @@ get_current_resources(struct acpi_device *device, int busnum, struct pci_root_info info; size_t size; - if (pci_probe & PCI_USE__CRS) + if (pci_use_crs) pci_bus_remove_resources(bus); - else - dev_info(&device->dev, - "ignoring host bridge windows from ACPI; " - "boot with \"pci=use_crs\" to use them\n"); info.bridge = device; info.bus = bus; diff --git a/arch/x86/pci/common.c b/arch/x86/pci/common.c index d2552c68e94..3736176acaa 100644 --- a/arch/x86/pci/common.c +++ b/arch/x86/pci/common.c @@ -520,6 +520,9 @@ char * __devinit pcibios_setup(char *str) } else if (!strcmp(str, "use_crs")) { pci_probe |= PCI_USE__CRS; return NULL; + } else if (!strcmp(str, "nocrs")) { + pci_probe |= PCI_ROOT_NO_CRS; + return NULL; } else if (!strcmp(str, "earlydump")) { pci_early_dump_regs = 1; return NULL; diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index 9cd8bedb1e5..d724736d56c 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -566,6 +566,7 @@ static int __init acpi_pci_root_init(void) if (acpi_pci_disabled) return 0; + pci_acpi_crs_quirks(); if (acpi_bus_register_driver(&acpi_pci_root_driver) < 0) return -ENODEV; diff --git a/include/acpi/acpi_drivers.h b/include/acpi/acpi_drivers.h index f4906f6568d..3a4767c01c5 100644 --- a/include/acpi/acpi_drivers.h +++ b/include/acpi/acpi_drivers.h @@ -104,6 +104,7 @@ int acpi_pci_bind_root(struct acpi_device *device); struct pci_bus *pci_acpi_scan_root(struct acpi_device *device, int domain, int bus); +void pci_acpi_crs_quirks(void); /* -------------------------------------------------------------------------- Processor -- cgit v1.2.3-70-g09d2 From 28ff4ef71795fe6f6c0cc06a04911c9ca09c2c11 Mon Sep 17 00:00:00 2001 From: Rusty Russell Date: Wed, 24 Feb 2010 14:22:18 -0600 Subject: lguest: remove unneeded zlib.h include in example launcher Two years ago 5bbf89fc2608 removed the horrible bzImage unpacking code. Now it's time to remove the unneeded zlib.h include, too. Signed-off-by: Rusty Russell --- Documentation/lguest/lguest.c | 1 - 1 file changed, 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/lguest/lguest.c b/Documentation/lguest/lguest.c index 42208511b5c..3119f5db75b 100644 --- a/Documentation/lguest/lguest.c +++ b/Documentation/lguest/lguest.c @@ -34,7 +34,6 @@ #include #include #include -#include #include #include #include -- cgit v1.2.3-70-g09d2 From 14315592009c17035cac81f4954d5a1f4d71e489 Mon Sep 17 00:00:00 2001 From: Ian Campbell Date: Wed, 17 Feb 2010 10:38:10 +0000 Subject: x86, mm: Allow highmem user page tables to be disabled at boot time Distros generally (I looked at Debian, RHEL5 and SLES11) seem to enable CONFIG_HIGHPTE for any x86 configuration which has highmem enabled. This means that the overhead applies even to machines which have a fairly modest amount of high memory and which therefore do not really benefit from allocating PTEs in high memory but still pay the price of the additional mapping operations. Running kernbench on a 4G box I found that with CONFIG_HIGHPTE=y but no actual highptes being allocated there was a reduction in system time used from 59.737s to 55.9s. With CONFIG_HIGHPTE=y and highmem PTEs being allocated: Average Optimal load -j 4 Run (std deviation): Elapsed Time 175.396 (0.238914) User Time 515.983 (5.85019) System Time 59.737 (1.26727) Percent CPU 263.8 (71.6796) Context Switches 39989.7 (4672.64) Sleeps 42617.7 (246.307) With CONFIG_HIGHPTE=y but with no highmem PTEs being allocated: Average Optimal load -j 4 Run (std deviation): Elapsed Time 174.278 (0.831968) User Time 515.659 (6.07012) System Time 55.9 (1.07799) Percent CPU 263.8 (71.266) Context Switches 39929.6 (4485.13) Sleeps 42583.7 (373.039) This patch allows the user to control the allocation of PTEs in highmem from the command line ("userpte=nohigh") but retains the status-quo as the default. It is possible that some simple heuristic could be developed which allows auto-tuning of this option however I don't have a sufficiently large machine available to me to perform any particularly meaningful experiments. We could probably handwave up an argument for a threshold at 16G of total RAM. Assuming 768M of lowmem we have 196608 potential lowmem PTE pages. Each page can map 2M of RAM in a PAE-enabled configuration, meaning a maximum of 384G of RAM could potentially be mapped using lowmem PTEs. Even allowing generous factor of 10 to account for other required lowmem allocations, generous slop to account for page sharing (which reduces the total amount of RAM mappable by a given number of PT pages) and other innacuracies in the estimations it would seem that even a 32G machine would not have a particularly pressing need for highmem PTEs. I think 32G could be considered to be at the upper bound of what might be sensible on a 32 bit machine (although I think in practice 64G is still supported). It's seems questionable if HIGHPTE is even a win for any amount of RAM you would sensibly run a 32 bit kernel on rather than going 64 bit. Signed-off-by: Ian Campbell LKML-Reference: <1266403090-20162-1-git-send-email-ian.campbell@citrix.com> Signed-off-by: H. Peter Anvin --- Documentation/kernel-parameters.txt | 7 +++++++ arch/x86/include/asm/pgalloc.h | 5 +++++ arch/x86/mm/pgtable.c | 31 ++++++++++++++++++++++++++----- 3 files changed, 38 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 736d4560288..67c69ffe7b7 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -2694,6 +2694,13 @@ and is between 256 and 4096 characters. It is defined in the file medium is write-protected). Example: quirks=0419:aaf5:rl,0421:0433:rc + userpte= + [X86] Flags controlling user PTE allocations. + + nohigh = do not allocate PTE pages in + HIGHMEM regardless of setting + of CONFIG_HIGHPTE. + vdso= [X86,SH] vdso=2: enable compat VDSO (default with COMPAT_VDSO) vdso=1: enable VDSO (default) diff --git a/arch/x86/include/asm/pgalloc.h b/arch/x86/include/asm/pgalloc.h index 0e8c2a0fd92..271de94c381 100644 --- a/arch/x86/include/asm/pgalloc.h +++ b/arch/x86/include/asm/pgalloc.h @@ -22,6 +22,11 @@ static inline void paravirt_release_pmd(unsigned long pfn) {} static inline void paravirt_release_pud(unsigned long pfn) {} #endif +/* + * Flags to use when allocating a user page table page. + */ +extern gfp_t __userpte_alloc_gfp; + /* * Allocate and free page tables. */ diff --git a/arch/x86/mm/pgtable.c b/arch/x86/mm/pgtable.c index ed34f5e3599..c9ba9deafe8 100644 --- a/arch/x86/mm/pgtable.c +++ b/arch/x86/mm/pgtable.c @@ -6,6 +6,14 @@ #define PGALLOC_GFP GFP_KERNEL | __GFP_NOTRACK | __GFP_REPEAT | __GFP_ZERO +#ifdef CONFIG_HIGHPTE +#define PGALLOC_USER_GFP __GFP_HIGHMEM +#else +#define PGALLOC_USER_GFP 0 +#endif + +gfp_t __userpte_alloc_gfp = PGALLOC_GFP | PGALLOC_USER_GFP; + pte_t *pte_alloc_one_kernel(struct mm_struct *mm, unsigned long address) { return (pte_t *)__get_free_page(PGALLOC_GFP); @@ -15,16 +23,29 @@ pgtable_t pte_alloc_one(struct mm_struct *mm, unsigned long address) { struct page *pte; -#ifdef CONFIG_HIGHPTE - pte = alloc_pages(PGALLOC_GFP | __GFP_HIGHMEM, 0); -#else - pte = alloc_pages(PGALLOC_GFP, 0); -#endif + pte = alloc_pages(__userpte_alloc_gfp, 0); if (pte) pgtable_page_ctor(pte); return pte; } +static int __init setup_userpte(char *arg) +{ + if (!arg) + return -EINVAL; + + /* + * "userpte=nohigh" disables allocation of user pagetables in + * high memory. + */ + if (strcmp(arg, "nohigh") == 0) + __userpte_alloc_gfp &= ~__GFP_HIGHMEM; + else + return -EINVAL; + return 0; +} +early_param("userpte", setup_userpte); + void ___pte_free_tlb(struct mmu_gather *tlb, struct page *pte) { pgtable_page_dtor(pte); -- cgit v1.2.3-70-g09d2 From c598a070bc581aea8a518b460dae8c0cf8e74344 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Mon, 22 Feb 2010 17:04:57 -0800 Subject: rcu: Documentation update for CONFIG_PROVE_RCU Adds a lockdep.txt file and updates checklist.txt and whatisRCU.txt to reflect the new lockdep-enabled capabilities of RCU. 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: <1266887105-1528-13-git-send-email-paulmck@linux.vnet.ibm.com> Signed-off-by: Ingo Molnar --- Documentation/RCU/00-INDEX | 2 ++ Documentation/RCU/checklist.txt | 34 ++++++++++++--------- Documentation/RCU/lockdep.txt | 67 +++++++++++++++++++++++++++++++++++++++++ Documentation/RCU/whatisRCU.txt | 13 +++++--- 4 files changed, 97 insertions(+), 19 deletions(-) create mode 100644 Documentation/RCU/lockdep.txt (limited to 'Documentation') diff --git a/Documentation/RCU/00-INDEX b/Documentation/RCU/00-INDEX index 0a27ea9621f..71b6f500ddb 100644 --- a/Documentation/RCU/00-INDEX +++ b/Documentation/RCU/00-INDEX @@ -6,6 +6,8 @@ checklist.txt - Review Checklist for RCU Patches listRCU.txt - Using RCU to Protect Read-Mostly Linked Lists +lockdep.txt + - RCU and lockdep checking NMI-RCU.txt - Using RCU to Protect Dynamic NMI Handlers rcubarrier.txt diff --git a/Documentation/RCU/checklist.txt b/Documentation/RCU/checklist.txt index 767cf06a427..cbc180f9019 100644 --- a/Documentation/RCU/checklist.txt +++ b/Documentation/RCU/checklist.txt @@ -127,10 +127,14 @@ over a rather long period of time, but improvements are always welcome! perfectly legal (if redundant) for update-side code to use rcu_dereference() and the "_rcu()" list-traversal primitives. This is particularly useful in code that - is common to readers and updaters. However, neither - rcu_dereference() nor the "_rcu()" list-traversal - primitives can substitute for a good concurrency design - coordinating among multiple updaters. + is common to readers and updaters. However, lockdep + will complain if you access rcu_dereference() outside + of an RCU read-side critical section. See lockdep.txt + to learn what to do about this. + + Of course, neither rcu_dereference() nor the "_rcu()" + list-traversal primitives can substitute for a good + concurrency design coordinating among multiple updaters. b. If the list macros are being used, the list_add_tail_rcu() and list_add_rcu() primitives must be used in order @@ -249,7 +253,9 @@ over a rather long period of time, but improvements are always welcome! must be protected by appropriate update-side locks. RCU read-side critical sections are delimited by rcu_read_lock() and rcu_read_unlock(), or by similar primitives such as - rcu_read_lock_bh() and rcu_read_unlock_bh(). + rcu_read_lock_bh() and rcu_read_unlock_bh(), in which case + the matching rcu_dereference() primitive must be used in order + to keep lockdep happy, in this case, rcu_dereference_bh(). The reason that it is permissible to use RCU list-traversal primitives when the update-side lock is held is that doing so @@ -302,15 +308,15 @@ over a rather long period of time, but improvements are always welcome! not the case, a self-spawning RCU callback would prevent the victim CPU from ever going offline.) -14. SRCU (srcu_read_lock(), srcu_read_unlock(), synchronize_srcu(), - and synchronize_srcu_expedited()) may only be invoked from - process context. Unlike other forms of RCU, it -is- permissible - to block in an SRCU read-side critical section (demarked by - srcu_read_lock() and srcu_read_unlock()), hence the "SRCU": - "sleepable RCU". Please note that if you don't need to sleep - in read-side critical sections, you should be using RCU rather - than SRCU, because RCU is almost always faster and easier to - use than is SRCU. +14. SRCU (srcu_read_lock(), srcu_read_unlock(), srcu_dereference(), + synchronize_srcu(), and synchronize_srcu_expedited()) may only + be invoked from process context. Unlike other forms of RCU, it + -is- permissible to block in an SRCU read-side critical section + (demarked by srcu_read_lock() and srcu_read_unlock()), hence the + "SRCU": "sleepable RCU". Please note that if you don't need + to sleep in read-side critical sections, you should be using + RCU rather than SRCU, because RCU is almost always faster and + easier to use than is SRCU. Also unlike other forms of RCU, explicit initialization and cleanup is required via init_srcu_struct() and diff --git a/Documentation/RCU/lockdep.txt b/Documentation/RCU/lockdep.txt new file mode 100644 index 00000000000..fe24b58627b --- /dev/null +++ b/Documentation/RCU/lockdep.txt @@ -0,0 +1,67 @@ +RCU and lockdep checking + +All flavors of RCU have lockdep checking available, so that lockdep is +aware of when each task enters and leaves any flavor of RCU read-side +critical section. Each flavor of RCU is tracked separately (but note +that this is not the case in 2.6.32 and earlier). This allows lockdep's +tracking to include RCU state, which can sometimes help when debugging +deadlocks and the like. + +In addition, RCU provides the following primitives that check lockdep's +state: + + rcu_read_lock_held() for normal RCU. + rcu_read_lock_bh_held() for RCU-bh. + rcu_read_lock_sched_held() for RCU-sched. + srcu_read_lock_held() for SRCU. + +These functions are conservative, and will therefore return 1 if they +aren't certain (for example, if CONFIG_DEBUG_LOCK_ALLOC is not set). +This prevents things like WARN_ON(!rcu_read_lock_held()) from giving false +positives when lockdep is disabled. + +In addition, a separate kernel config parameter CONFIG_PROVE_RCU enables +checking of rcu_dereference() primitives: + + rcu_dereference(p): + Check for RCU read-side critical section. + rcu_dereference_bh(p): + Check for RCU-bh read-side critical section. + rcu_dereference_sched(p): + Check for RCU-sched read-side critical section. + srcu_dereference(p, sp): + Check for SRCU read-side critical section. + rcu_dereference_check(p, c): + Use explicit check expression "c". + rcu_dereference_raw(p) + Don't check. (Use sparingly, if at all.) + +The rcu_dereference_check() check expression can be any boolean +expression, but would normally include one of the rcu_read_lock_held() +family of functions and a lockdep expression. However, any boolean +expression can be used. For a moderately ornate example, consider +the following: + + file = rcu_dereference_check(fdt->fd[fd], + rcu_read_lock_held() || + lockdep_is_held(&files->file_lock) || + atomic_read(&files->count) == 1); + +This expression picks up the pointer "fdt->fd[fd]" in an RCU-safe manner, +and, if CONFIG_PROVE_RCU is configured, verifies that this expression +is used in: + +1. An RCU read-side critical section, or +2. with files->file_lock held, or +3. on an unshared files_struct. + +In case (1), the pointer is picked up in an RCU-safe manner for vanilla +RCU read-side critical sections, in case (2) the ->file_lock prevents +any change from taking place, and finally, in case (3) the current task +is the only task accessing the file_struct, again preventing any change +from taking place. + +There are currently only "universal" versions of the rcu_assign_pointer() +and RCU list-/tree-traversal primitives, which do not (yet) check for +being in an RCU read-side critical section. In the future, separate +versions of these primitives might be created. diff --git a/Documentation/RCU/whatisRCU.txt b/Documentation/RCU/whatisRCU.txt index 469a58b2e67..1dc00ee9716 100644 --- a/Documentation/RCU/whatisRCU.txt +++ b/Documentation/RCU/whatisRCU.txt @@ -323,15 +323,17 @@ used as follows: Defer Protect a. synchronize_rcu() rcu_read_lock() / rcu_read_unlock() - call_rcu() + call_rcu() rcu_dereference() b. call_rcu_bh() rcu_read_lock_bh() / rcu_read_unlock_bh() + rcu_dereference_bh() c. synchronize_sched() rcu_read_lock_sched() / rcu_read_unlock_sched() preempt_disable() / preempt_enable() local_irq_save() / local_irq_restore() hardirq enter / hardirq exit NMI enter / NMI exit + rcu_dereference_sched() These three mechanisms are used as follows: @@ -781,9 +783,8 @@ Linux-kernel source code, but it helps to have a full list of the APIs, since there does not appear to be a way to categorize them in docbook. Here is the list, by category. -RCU pointer/list traversal: +RCU list traversal: - rcu_dereference list_for_each_entry_rcu hlist_for_each_entry_rcu hlist_nulls_for_each_entry_rcu @@ -809,7 +810,7 @@ RCU: Critical sections Grace period Barrier rcu_read_lock synchronize_net rcu_barrier rcu_read_unlock synchronize_rcu - synchronize_rcu_expedited + rcu_dereference synchronize_rcu_expedited call_rcu @@ -817,7 +818,7 @@ bh: Critical sections Grace period Barrier rcu_read_lock_bh call_rcu_bh rcu_barrier_bh rcu_read_unlock_bh synchronize_rcu_bh - synchronize_rcu_bh_expedited + rcu_dereference_bh synchronize_rcu_bh_expedited sched: Critical sections Grace period Barrier @@ -826,12 +827,14 @@ sched: Critical sections Grace period Barrier rcu_read_unlock_sched call_rcu_sched [preempt_disable] synchronize_sched_expedited [and friends] + rcu_dereference_sched SRCU: Critical sections Grace period Barrier srcu_read_lock synchronize_srcu N/A srcu_read_unlock synchronize_srcu_expedited + srcu_dereference SRCU: Initialization/cleanup init_srcu_struct -- cgit v1.2.3-70-g09d2 From 998f2ac3fea93bfa8b55c279fff68f7c5b9ab93d Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Mon, 22 Feb 2010 17:04:58 -0800 Subject: rcu: Fix citation of Mathieu's dissertation 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: <1266887105-1528-14-git-send-email-paulmck@linux.vnet.ibm.com> Signed-off-by: Ingo Molnar --- Documentation/RCU/RTFP.txt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/RCU/RTFP.txt b/Documentation/RCU/RTFP.txt index 5051209e683..96b7c00f5e8 100644 --- a/Documentation/RCU/RTFP.txt +++ b/Documentation/RCU/RTFP.txt @@ -864,9 +864,12 @@ Revised: } @phdthesis{MathieuDesnoyersPhD -, title = "Low-impact Operating System Tracing" +, title = "Low-Impact Operating System Tracing" , author = "Mathieu Desnoyers" , school = "Ecole Polytechnique de Montr\'{e}al" , month = "December" , year = 2009 +,note="Available: +\url{http://www.lttng.org/pub/thesis/desnoyers-dissertation-2009-12-v24.pdf} +[Viewed December 9, 2009]" } -- cgit v1.2.3-70-g09d2 From 1bd22e374b20c2f0ba1d2723c1f585acab2251c5 Mon Sep 17 00:00:00 2001 From: "Paul E. McKenney" Date: Mon, 22 Feb 2010 17:05:00 -0800 Subject: rcu: Use canonical URL for Mathieu's dissertation The version numbers change too quickly, so use a canonical URL that represents the most recent version. 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: <1266887105-1528-16-git-send-email-paulmck@linux.vnet.ibm.com> Signed-off-by: Ingo Molnar --- Documentation/RCU/RTFP.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/RCU/RTFP.txt b/Documentation/RCU/RTFP.txt index 96b7c00f5e8..5aea459e3dd 100644 --- a/Documentation/RCU/RTFP.txt +++ b/Documentation/RCU/RTFP.txt @@ -870,6 +870,6 @@ Revised: , month = "December" , year = 2009 ,note="Available: -\url{http://www.lttng.org/pub/thesis/desnoyers-dissertation-2009-12-v24.pdf} +\url{http://www.lttng.org/pub/thesis/desnoyers-dissertation-2009-12.pdf} [Viewed December 9, 2009]" } -- cgit v1.2.3-70-g09d2 From 2ac2ed5f2dfc97ae9ed9f446ad6e064fa821ef6d Mon Sep 17 00:00:00 2001 From: Tilman Schmidt Date: Mon, 22 Feb 2010 13:08:51 +0000 Subject: gigaset: small documentation improvement Clarify the non-support by isdnlog, and propose a better standard debug mask. Impact: Documentation Signed-off-by: Tilman Schmidt Signed-off-by: David S. Miller --- Documentation/isdn/README.gigaset | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/isdn/README.gigaset b/Documentation/isdn/README.gigaset index 794941fc949..e472df84232 100644 --- a/Documentation/isdn/README.gigaset +++ b/Documentation/isdn/README.gigaset @@ -292,10 +292,10 @@ GigaSet 307x Device Driver to /etc/modprobe.d/gigaset, /etc/modprobe.conf.local or a similar file. Problem: - Your isdn script aborts with a message about isdnlog. + The isdnlog program emits error messages or just doesn't work. Solution: - Try deactivating (or commenting out) isdnlog. This driver does not - support it. + Isdnlog supports only the HiSax driver. Do not attempt to use it with + other drivers such as Gigaset. Problem: You have two or more DECT data adapters (M101/M105) and only the @@ -321,8 +321,8 @@ GigaSet 307x Device Driver writing an appropriate value to /sys/module/gigaset/parameters/debug, e.g. echo 0 > /sys/module/gigaset/parameters/debug switches off debugging output completely, - echo 0x10a020 > /sys/module/gigaset/parameters/debug - enables the standard set of debugging output messages. These values are + echo 0x302020 > /sys/module/gigaset/parameters/debug + enables a reasonable set of debugging output messages. These values are bit patterns where every bit controls a certain type of debugging output. See the constants DEBUG_* in the source file gigaset.h for details. -- cgit v1.2.3-70-g09d2 From d41592a2a2b9a27425ade3fc2c8526e9e997acd6 Mon Sep 17 00:00:00 2001 From: Jean-Francois Moine Date: Sun, 13 Dec 2009 14:11:07 -0300 Subject: V4L/DVB (13815): gspca - sunplus: Add webcam 052b:1507. Signed-off-by: Jean-Francois Moine Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/gspca.txt | 1 + drivers/media/video/gspca/sunplus.c | 1 + 2 files changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt index 1800a62cf13..98ee599b4eb 100644 --- a/Documentation/video4linux/gspca.txt +++ b/Documentation/video4linux/gspca.txt @@ -142,6 +142,7 @@ sunplus 04fc:5360 Sunplus Generic spca500 04fc:7333 PalmPixDC85 sunplus 04fc:ffff Pure DigitalDakota spca501 0506:00df 3Com HomeConnect Lite +sunplus 052b:1507 Megapixel 5 Pretec DC-1007 sunplus 052b:1513 Megapix V4 sunplus 052b:1803 MegaImage VI tv8532 0545:808b Veo Stingray diff --git a/drivers/media/video/gspca/sunplus.c b/drivers/media/video/gspca/sunplus.c index 306b7d75b4a..56716c6ed96 100644 --- a/drivers/media/video/gspca/sunplus.c +++ b/drivers/media/video/gspca/sunplus.c @@ -1336,6 +1336,7 @@ static const __devinitdata struct usb_device_id device_table[] = { {USB_DEVICE(0x04fc, 0x5330), BS(SPCA533, 0)}, {USB_DEVICE(0x04fc, 0x5360), BS(SPCA536, 0)}, {USB_DEVICE(0x04fc, 0xffff), BS(SPCA504B, 0)}, + {USB_DEVICE(0x052b, 0x1507), BS(SPCA533, MegapixV4)}, {USB_DEVICE(0x052b, 0x1513), BS(SPCA533, MegapixV4)}, {USB_DEVICE(0x052b, 0x1803), BS(SPCA533, MegaImageVI)}, {USB_DEVICE(0x0546, 0x3155), BS(SPCA533, 0)}, -- cgit v1.2.3-70-g09d2 From 0a71d9cec238dd953e1e31df6ad28f69dee16fcc Mon Sep 17 00:00:00 2001 From: Jean-Francois Moine Date: Sun, 20 Dec 2009 09:09:22 -0300 Subject: V4L/DVB (13871): gspca - benq: New subdriver for camera 04a5:3035. Tested-by: Francesco Lavra Signed-off-by: Jean-Francois Moine Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/gspca.txt | 1 + drivers/media/video/gspca/Kconfig | 9 + drivers/media/video/gspca/Makefile | 2 + drivers/media/video/gspca/benq.c | 322 ++++++++++++++++++++++++++++++++++++ 4 files changed, 334 insertions(+) create mode 100644 drivers/media/video/gspca/benq.c (limited to 'Documentation') diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt index 98ee599b4eb..d3896199a34 100644 --- a/Documentation/video4linux/gspca.txt +++ b/Documentation/video4linux/gspca.txt @@ -109,6 +109,7 @@ sunplus 04a5:3003 Benq DC 1300 sunplus 04a5:3008 Benq DC 1500 sunplus 04a5:300a Benq DC 3410 spca500 04a5:300c Benq DC 1016 +benq 04a5:3035 Benq DC E300 finepix 04cb:0104 Fujifilm FinePix 4800 finepix 04cb:0109 Fujifilm FinePix A202 finepix 04cb:010b Fujifilm FinePix A203 diff --git a/drivers/media/video/gspca/Kconfig b/drivers/media/video/gspca/Kconfig index 609d65b0b10..824e95a3d88 100644 --- a/drivers/media/video/gspca/Kconfig +++ b/drivers/media/video/gspca/Kconfig @@ -21,6 +21,15 @@ source "drivers/media/video/gspca/m5602/Kconfig" source "drivers/media/video/gspca/stv06xx/Kconfig" source "drivers/media/video/gspca/gl860/Kconfig" +config USB_GSPCA_BENQ + tristate "Benq USB Camera Driver" + depends on VIDEO_V4L2 && USB_GSPCA + help + Say Y here if you want support for the Benq DC E300 camera. + + To compile this driver as a module, choose M here: the + module will be called gspca_benq. + config USB_GSPCA_CONEX tristate "Conexant Camera Driver" depends on VIDEO_V4L2 && USB_GSPCA diff --git a/drivers/media/video/gspca/Makefile b/drivers/media/video/gspca/Makefile index ff2c7279d82..82da9c3d204 100644 --- a/drivers/media/video/gspca/Makefile +++ b/drivers/media/video/gspca/Makefile @@ -1,4 +1,5 @@ obj-$(CONFIG_USB_GSPCA) += gspca_main.o +obj-$(CONFIG_USB_GSPCA_BENQ) += gspca_benq.o obj-$(CONFIG_USB_GSPCA_CONEX) += gspca_conex.o obj-$(CONFIG_USB_GSPCA_ETOMS) += gspca_etoms.o obj-$(CONFIG_USB_GSPCA_FINEPIX) += gspca_finepix.o @@ -30,6 +31,7 @@ obj-$(CONFIG_USB_GSPCA_VC032X) += gspca_vc032x.o obj-$(CONFIG_USB_GSPCA_ZC3XX) += gspca_zc3xx.o gspca_main-objs := gspca.o +gspca_benq-objs := benq.o gspca_conex-objs := conex.o gspca_etoms-objs := etoms.o gspca_finepix-objs := finepix.o diff --git a/drivers/media/video/gspca/benq.c b/drivers/media/video/gspca/benq.c new file mode 100644 index 00000000000..604c087aed3 --- /dev/null +++ b/drivers/media/video/gspca/benq.c @@ -0,0 +1,322 @@ +/* + * Benq DC E300 subdriver + * + * Copyright (C) 2009 Jean-Francois Moine (http://moinejf.free.fr) + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define MODULE_NAME "benq" + +#include "gspca.h" + +MODULE_AUTHOR("Jean-Francois Moine "); +MODULE_DESCRIPTION("Benq DC E300 USB Camera Driver"); +MODULE_LICENSE("GPL"); + +/* specific webcam descriptor */ +struct sd { + struct gspca_dev gspca_dev; /* !! must be the first item */ +}; + +/* V4L2 controls supported by the driver */ +static struct ctrl sd_ctrls[] = { +}; + +static const struct v4l2_pix_format vga_mode[] = { + {320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, + .bytesperline = 320, + .sizeimage = 320 * 240 * 3 / 8 + 590, + .colorspace = V4L2_COLORSPACE_JPEG}, +}; + +static void sd_isoc_irq(struct urb *urb); + +/* -- write a register -- */ +static void reg_w(struct gspca_dev *gspca_dev, + u16 value, u16 index) +{ + struct usb_device *dev = gspca_dev->dev; + int ret; + + if (gspca_dev->usb_err < 0) + return; + ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + 0x02, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + value, + index, + NULL, + 0, + 500); + if (ret < 0) { + PDEBUG(D_ERR, "reg_w err %d", ret); + gspca_dev->usb_err = ret; + } +} + +/* this function is called at probe time */ +static int sd_config(struct gspca_dev *gspca_dev, + const struct usb_device_id *id) +{ + gspca_dev->cam.cam_mode = vga_mode; + gspca_dev->cam.nmodes = ARRAY_SIZE(vga_mode); + gspca_dev->cam.no_urb_create = 1; + gspca_dev->cam.reverse_alts = 1; + return 0; +} + +/* this function is called at probe and resume time */ +static int sd_init(struct gspca_dev *gspca_dev) +{ + return 0; +} + +static int sd_isoc_init(struct gspca_dev *gspca_dev) +{ + int ret; + + ret = usb_set_interface(gspca_dev->dev, gspca_dev->iface, + gspca_dev->nbalt - 1); + if (ret < 0) { + err("usb_set_interface failed"); + return ret; + } +/* reg_w(gspca_dev, 0x0003, 0x0002); */ + return 0; +} + +/* -- start the camera -- */ +static int sd_start(struct gspca_dev *gspca_dev) +{ + struct urb *urb; + int i, n; + + /* create 4 URBs - 2 on endpoint 0x83 and 2 on 0x082 */ +#if MAX_NURBS < 4 +#error "Not enough URBs in the gspca table" +#endif +#define SD_PKT_SZ 64 +#define SD_NPKT 32 + for (n = 0; n < 4; n++) { + urb = usb_alloc_urb(SD_NPKT, GFP_KERNEL); + if (!urb) { + err("usb_alloc_urb failed"); + return -ENOMEM; + } + gspca_dev->urb[n] = urb; + urb->transfer_buffer = usb_buffer_alloc(gspca_dev->dev, + SD_PKT_SZ * SD_NPKT, + GFP_KERNEL, + &urb->transfer_dma); + + if (urb->transfer_buffer == NULL) { + err("usb_buffer_alloc failed"); + return -ENOMEM; + } + urb->dev = gspca_dev->dev; + urb->context = gspca_dev; + urb->transfer_buffer_length = SD_PKT_SZ * SD_NPKT; + urb->pipe = usb_rcvisocpipe(gspca_dev->dev, + n & 1 ? 0x82 : 0x83); + urb->transfer_flags = URB_ISO_ASAP + | URB_NO_TRANSFER_DMA_MAP; + urb->interval = 1; + urb->complete = sd_isoc_irq; + urb->number_of_packets = SD_NPKT; + for (i = 0; i < SD_NPKT; i++) { + urb->iso_frame_desc[i].length = SD_PKT_SZ; + urb->iso_frame_desc[i].offset = SD_PKT_SZ * i; + } + } + + return gspca_dev->usb_err; +} + +static void sd_stopN(struct gspca_dev *gspca_dev) +{ + reg_w(gspca_dev, 0x003c, 0x0003); + reg_w(gspca_dev, 0x003c, 0x0004); + reg_w(gspca_dev, 0x003c, 0x0005); + reg_w(gspca_dev, 0x003c, 0x0006); + reg_w(gspca_dev, 0x003c, 0x0007); + usb_set_interface(gspca_dev->dev, gspca_dev->iface, gspca_dev->nbalt - 1); +} + +static void sd_pkt_scan(struct gspca_dev *gspca_dev, + u8 *data, /* isoc packet */ + int len) /* iso packet length */ +{ + /* unused */ +} + +/* reception of an URB */ +static void sd_isoc_irq(struct urb *urb) +{ + struct gspca_dev *gspca_dev = (struct gspca_dev *) urb->context; + struct urb *urb0; + u8 *data; + int i, st; + + PDEBUG(D_PACK, "sd isoc irq"); + if (!gspca_dev->streaming) + return; + if (urb->status != 0) { + if (urb->status == -ESHUTDOWN) + return; /* disconnection */ +#ifdef CONFIG_PM + if (gspca_dev->frozen) + return; +#endif + PDEBUG(D_ERR|D_PACK, "urb status: %d", urb->status); + return; + } + + /* if this is a control URN (ep 0x83), wait */ + if (urb == gspca_dev->urb[0] || urb == gspca_dev->urb[2]) + return; + + /* scan both received URBs */ + if (urb == gspca_dev->urb[1]) + urb0 = gspca_dev->urb[0]; + else + urb0 = gspca_dev->urb[2]; + for (i = 0; i < urb->number_of_packets; i++) { + + /* check the packet status and length */ + if (urb0->iso_frame_desc[i].actual_length != SD_PKT_SZ + || urb->iso_frame_desc[i].actual_length != SD_PKT_SZ) { + PDEBUG(D_ERR, "ISOC bad lengths %d / %d", + urb0->iso_frame_desc[i].actual_length, + urb->iso_frame_desc[i].actual_length); + gspca_dev->last_packet_type = DISCARD_PACKET; + continue; + } + st = urb0->iso_frame_desc[i].status; + if (st == 0) + st = urb->iso_frame_desc[i].status; + if (st) { + PDEBUG(D_ERR, + "ISOC data error: [%d] status=%d", + i, st); + gspca_dev->last_packet_type = DISCARD_PACKET; + continue; + } + + /* + * The images are received in URBs of different endpoints + * (0x83 and 0x82). + * Image pieces in URBs of ep 0x83 are continuated in URBs of + * ep 0x82 of the same index. + * The packets in the URBs of endpoint 0x83 start with: + * - 80 ba/bb 00 00 = start of image followed by 'ff d8' + * - 04 ba/bb oo oo = image piece + * where 'oo oo' is the image offset + (not cheked) + * - (other -> bad frame) + * The images are JPEG encoded with full header and + * normal ff escape. + * The end of image ('ff d9') may occur in any URB. + * (not cheked) + */ + data = (u8 *) urb0->transfer_buffer + + urb0->iso_frame_desc[i].offset; + if (data[0] == 0x80 && (data[1] & 0xfe) == 0xba) { + + /* new image */ + gspca_frame_add(gspca_dev, LAST_PACKET, + NULL, 0); + gspca_frame_add(gspca_dev, FIRST_PACKET, + data + 4, SD_PKT_SZ - 4); + } else if (data[0] == 0x04 && (data[1] & 0xfe) == 0xba) { + gspca_frame_add(gspca_dev, INTER_PACKET, + data + 4, SD_PKT_SZ - 4); + } else { + gspca_dev->last_packet_type = DISCARD_PACKET; + continue; + } + data = (u8 *) urb->transfer_buffer + + urb->iso_frame_desc[i].offset; + gspca_frame_add(gspca_dev, INTER_PACKET, + data, SD_PKT_SZ); + } + + /* resubmit the URBs */ + st = usb_submit_urb(urb0, GFP_ATOMIC); + if (st < 0) + PDEBUG(D_ERR|D_PACK, "usb_submit_urb(0) ret %d", st); + st = usb_submit_urb(urb, GFP_ATOMIC); + if (st < 0) + PDEBUG(D_ERR|D_PACK, "usb_submit_urb() ret %d", st); +} + +/* sub-driver description */ +static const struct sd_desc sd_desc = { + .name = MODULE_NAME, + .ctrls = sd_ctrls, + .nctrls = ARRAY_SIZE(sd_ctrls), + .config = sd_config, + .init = sd_init, + .isoc_init = sd_isoc_init, + .start = sd_start, + .stopN = sd_stopN, + .pkt_scan = sd_pkt_scan, +}; + +/* -- module initialisation -- */ +static const __devinitdata struct usb_device_id device_table[] = { + {USB_DEVICE(0x04a5, 0x3035)}, + {} +}; +MODULE_DEVICE_TABLE(usb, device_table); + +/* -- device connect -- */ +static int sd_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd), + THIS_MODULE); +} + +static struct usb_driver sd_driver = { + .name = MODULE_NAME, + .id_table = device_table, + .probe = sd_probe, + .disconnect = gspca_disconnect, +#ifdef CONFIG_PM + .suspend = gspca_suspend, + .resume = gspca_resume, +#endif +}; + +/* -- module insert / remove -- */ +static int __init sd_mod_init(void) +{ + int ret; + + ret = usb_register(&sd_driver); + if (ret < 0) + return ret; + info("registered"); + return 0; +} +static void __exit sd_mod_exit(void) +{ + usb_deregister(&sd_driver); + info("deregistered"); +} + +module_init(sd_mod_init); +module_exit(sd_mod_exit); -- cgit v1.2.3-70-g09d2 From 64677573a28c354828343741bc177e5543f5077e Mon Sep 17 00:00:00 2001 From: Jean-Francois Moine Date: Sun, 20 Dec 2009 12:31:28 -0300 Subject: V4L/DVB (13872): gspca - sonixj: Add sensor adcm1700 and webcam 0c45:614a. Signed-off-by: Jean-Francois Moine Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/gspca.txt | 1 + drivers/media/video/gspca/sonixj.c | 184 ++++++++++++++++++++++++++++-------- 2 files changed, 147 insertions(+), 38 deletions(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt index d3896199a34..7b603439509 100644 --- a/Documentation/video4linux/gspca.txt +++ b/Documentation/video4linux/gspca.txt @@ -304,6 +304,7 @@ sonixj 0c45:613b Surfer SN-206 sonixj 0c45:613c Sonix Pccam168 sonixj 0c45:6143 Sonix Pccam168 sonixj 0c45:6148 Digitus DA-70811/ZSMC USB PC Camera ZS211/Microdia +sonixj 0c45:614a Frontech E-Ccam (JIL-2225) sn9c20x 0c45:6240 PC Camera (SN9C201 + MT9M001) sn9c20x 0c45:6242 PC Camera (SN9C201 + MT9M111) sn9c20x 0c45:6248 PC Camera (SN9C201 + OV9655) diff --git a/drivers/media/video/gspca/sonixj.c b/drivers/media/video/gspca/sonixj.c index 0bd36a00dd2..1f21c6a7a72 100644 --- a/drivers/media/video/gspca/sonixj.c +++ b/drivers/media/video/gspca/sonixj.c @@ -64,16 +64,17 @@ struct sd { #define BRIDGE_SN9C110 2 #define BRIDGE_SN9C120 3 u8 sensor; /* Type of image sensor chip */ -#define SENSOR_HV7131R 0 -#define SENSOR_MI0360 1 -#define SENSOR_MO4000 2 -#define SENSOR_MT9V111 3 -#define SENSOR_OM6802 4 -#define SENSOR_OV7630 5 -#define SENSOR_OV7648 6 -#define SENSOR_OV7660 7 -#define SENSOR_PO1030 8 -#define SENSOR_SP80708 9 +#define SENSOR_ADCM1700 0 +#define SENSOR_HV7131R 1 +#define SENSOR_MI0360 2 +#define SENSOR_MO4000 3 +#define SENSOR_MT9V111 4 +#define SENSOR_OM6802 5 +#define SENSOR_OV7630 6 +#define SENSOR_OV7648 7 +#define SENSOR_OV7660 8 +#define SENSOR_PO1030 9 +#define SENSOR_SP80708 10 u8 i2c_addr; u8 *jpeg_hdr; @@ -261,28 +262,37 @@ static struct ctrl sd_ctrls[] = { /* table of the disabled controls */ static __u32 ctrl_dis[] = { + (1 << INFRARED_IDX) | (1 << VFLIP_IDX) | (1 << FREQ_IDX) | + (1 << AUTOGAIN_IDX) | (1 << BRIGHTNESS_IDX), /* SENSOR_ADCM1700 0 */ (1 << INFRARED_IDX) | (1 << VFLIP_IDX) | (1 << FREQ_IDX), - /* SENSOR_HV7131R 0 */ + /* SENSOR_HV7131R 1 */ (1 << INFRARED_IDX) | (1 << VFLIP_IDX) | (1 << FREQ_IDX), - /* SENSOR_MI0360 1 */ + /* SENSOR_MI0360 2 */ (1 << INFRARED_IDX) | (1 << VFLIP_IDX) | (1 << FREQ_IDX), - /* SENSOR_MO4000 2 */ + /* SENSOR_MO4000 3 */ (1 << VFLIP_IDX) | (1 << FREQ_IDX), - /* SENSOR_MT9V111 3 */ + /* SENSOR_MT9V111 4 */ (1 << INFRARED_IDX) | (1 << VFLIP_IDX) | (1 << FREQ_IDX), - /* SENSOR_OM6802 4 */ + /* SENSOR_OM6802 5 */ (1 << INFRARED_IDX), - /* SENSOR_OV7630 5 */ + /* SENSOR_OV7630 6 */ (1 << INFRARED_IDX), - /* SENSOR_OV7648 6 */ + /* SENSOR_OV7648 7 */ (1 << AUTOGAIN_IDX) | (1 << INFRARED_IDX) | (1 << VFLIP_IDX), - /* SENSOR_OV7660 7 */ + /* SENSOR_OV7660 8 */ (1 << AUTOGAIN_IDX) | (1 << INFRARED_IDX) | (1 << VFLIP_IDX) | - (1 << FREQ_IDX), /* SENSOR_PO1030 8 */ + (1 << FREQ_IDX), /* SENSOR_PO1030 9 */ (1 << AUTOGAIN_IDX) | (1 << INFRARED_IDX) | (1 << VFLIP_IDX) | - (1 << FREQ_IDX), /* SENSOR_SP80708 9 */ + (1 << FREQ_IDX), /* SENSOR_SP80708 10 */ }; +static const struct v4l2_pix_format cif_mode[] = { + {352, 288, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, + .bytesperline = 352, + .sizeimage = 352 * 288 * 4 / 8 + 590, + .colorspace = V4L2_COLORSPACE_JPEG, + .priv = 0}, +}; static const struct v4l2_pix_format vga_mode[] = { {160, 120, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, .bytesperline = 160, @@ -302,6 +312,17 @@ static const struct v4l2_pix_format vga_mode[] = { .priv = 0}, }; +static const u8 sn_adcm1700[0x1c] = { +/* reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 */ + 0x00, 0x42, 0x60, 0x00, 0x1a, 0x20, 0x20, 0x20, +/* reg8 reg9 rega regb regc regd rege regf */ + 0x80, 0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/* reg10 reg11 reg12 reg13 reg14 reg15 reg16 reg17 */ + 0x03, 0x00, 0x05, 0x01, 0x05, 0x16, 0x12, 0x42, +/* reg18 reg19 reg1a reg1b */ + 0x06, 0x00, 0x00, 0x00 +}; + /*Data from sn9c102p+hv7131r */ static const u8 sn_hv7131[0x1c] = { /* reg0 reg1 reg2 reg3 reg4 reg5 reg6 reg7 */ @@ -415,6 +436,7 @@ static const u8 sn_sp80708[0x1c] = { /* sequence specific to the sensors - !! index = SENSOR_xxx */ static const u8 *sn_tb[] = { + sn_adcm1700, sn_hv7131, sn_mi0360, sn_mo4000, @@ -432,6 +454,11 @@ static const u8 gamma_def[17] = { 0x00, 0x2d, 0x46, 0x5a, 0x6c, 0x7c, 0x8b, 0x99, 0xa6, 0xb2, 0xbf, 0xca, 0xd5, 0xe0, 0xeb, 0xf5, 0xff }; +/* gamma for sensor ADCM1700 */ +static const u8 gamma_spec_0[17] = { + 0x0f, 0x39, 0x5a, 0x74, 0x86, 0x95, 0xa6, 0xb4, + 0xbd, 0xc4, 0xcc, 0xd4, 0xd5, 0xde, 0xe4, 0xed, 0xf5 +}; /* gamma for sensors HV7131R and MT9V111 */ static const u8 gamma_spec_1[17] = { 0x08, 0x3a, 0x52, 0x65, 0x75, 0x83, 0x91, 0x9d, @@ -450,6 +477,38 @@ static const u8 reg84[] = { 0x3e, 0x00, 0xcd, 0x0f, 0xf7, 0x0f, /* VR VG VB */ 0x00, 0x00, 0x00 /* YUV offsets */ }; +static const u8 adcm1700_sensor_init[][8] = { + {0xa0, 0x51, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x04, 0x08, 0x00, 0x00, 0x00, 0x10}, + {0xdd, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xb0, 0x51, 0x04, 0x00, 0x00, 0x00, 0x00, 0x10}, + {0xdd, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xb0, 0x51, 0x0c, 0xe0, 0x2e, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x10, 0x02, 0x02, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x14, 0x0e, 0x0e, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x1c, 0x00, 0x80, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x20, 0x01, 0x00, 0x00, 0x00, 0x10}, + {0xdd, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xb0, 0x51, 0x04, 0x04, 0x00, 0x00, 0x00, 0x10}, + {0xdd, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, + {0xb0, 0x51, 0x04, 0x01, 0x00, 0x00, 0x00, 0x10}, + {0xa0, 0x51, 0xfe, 0x10, 0x00, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x14, 0x01, 0x00, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x32, 0x00, 0x00, 0x00, 0x00, 0x10}, + {} +}; +static const u8 adcm1700_sensor_param1[][8] = { + {0xb0, 0x51, 0x26, 0xf9, 0x01, 0x00, 0x00, 0x10}, + {0xd0, 0x51, 0x1e, 0x8e, 0x8e, 0x8e, 0x8e, 0x10}, + + {0xa0, 0x51, 0xfe, 0x01, 0x00, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x00, 0x02, 0x00, 0x00, 0x00, 0x10}, + {0xa0, 0x51, 0xfe, 0x10, 0x00, 0x00, 0x00, 0x10}, + {0xb0, 0x51, 0x32, 0x00, 0x72, 0x00, 0x00, 0x10}, + {0xd0, 0x51, 0x1e, 0x8e, 0x91, 0x91, 0x8e, 0x10}, + + {} +}; static const u8 hv7131r_sensor_init[][8] = { {0xc1, 0x11, 0x01, 0x08, 0x01, 0x00, 0x00, 0x10}, {0xb1, 0x11, 0x34, 0x17, 0x7f, 0x00, 0x00, 0x10}, @@ -986,17 +1045,18 @@ static const u8 sp80708_sensor_param1[][8] = { {} }; -static const u8 (*sensor_init[10])[8] = { - hv7131r_sensor_init, /* HV7131R 0 */ - mi0360_sensor_init, /* MI0360 1 */ - mo4000_sensor_init, /* MO4000 2 */ - mt9v111_sensor_init, /* MT9V111 3 */ - om6802_sensor_init, /* OM6802 4 */ - ov7630_sensor_init, /* OV7630 5 */ - ov7648_sensor_init, /* OV7648 6 */ - ov7660_sensor_init, /* OV7660 7 */ - po1030_sensor_init, /* PO1030 8 */ - sp80708_sensor_init, /* SP80708 9 */ +static const u8 (*sensor_init[11])[8] = { + adcm1700_sensor_init, /* ADCM1700 0 */ + hv7131r_sensor_init, /* HV7131R 1 */ + mi0360_sensor_init, /* MI0360 2 */ + mo4000_sensor_init, /* MO4000 3 */ + mt9v111_sensor_init, /* MT9V111 4 */ + om6802_sensor_init, /* OM6802 5 */ + ov7630_sensor_init, /* OV7630 6 */ + ov7648_sensor_init, /* OV7648 7 */ + ov7660_sensor_init, /* OV7660 8 */ + po1030_sensor_init, /* PO1030 9 */ + sp80708_sensor_init, /* SP80708 10 */ }; /* read bytes to gspca_dev->usb_buf */ @@ -1064,6 +1124,7 @@ static void i2c_w1(struct gspca_dev *gspca_dev, u8 reg, u8 val) PDEBUG(D_USBO, "i2c_w2 [%02x] = %02x", reg, val); switch (sd->sensor) { + case SENSOR_ADCM1700: case SENSOR_OM6802: /* i2c command = a0 (100 kHz) */ gspca_dev->usb_buf[0] = 0x80 | (2 << 4); break; @@ -1110,6 +1171,7 @@ static void i2c_r(struct gspca_dev *gspca_dev, u8 reg, int len) u8 mode[8]; switch (sd->sensor) { + case SENSOR_ADCM1700: case SENSOR_OM6802: /* i2c command = 90 (100 kHz) */ mode[0] = 0x80 | 0x10; break; @@ -1284,6 +1346,12 @@ static void bridge_init(struct gspca_dev *gspca_dev, reg_w(gspca_dev, 0x03, &sn9c1xx[3], 0x0f); switch (sd->sensor) { + case SENSOR_ADCM1700: + reg_w1(gspca_dev, 0x01, 0x42); + reg_w1(gspca_dev, 0x17, 0x62); + reg_w1(gspca_dev, 0x01, 0x42); + reg_w1(gspca_dev, 0x01, 0x42); + break; case SENSOR_MT9V111: reg_w1(gspca_dev, 0x01, 0x61); reg_w1(gspca_dev, 0x17, 0x61); @@ -1358,8 +1426,13 @@ static int sd_config(struct gspca_dev *gspca_dev, struct cam *cam; cam = &gspca_dev->cam; - cam->cam_mode = vga_mode; - cam->nmodes = ARRAY_SIZE(vga_mode); + if (sd->sensor == SENSOR_ADCM1700) { + cam->cam_mode = cif_mode; + cam->nmodes = ARRAY_SIZE(cif_mode); + } else { + cam->cam_mode = vga_mode; + cam->nmodes = ARRAY_SIZE(vga_mode); + } cam->npkt = 24; /* 24 packets per ISOC message */ sd->bridge = id->driver_info >> 16; @@ -1543,6 +1616,8 @@ static void setbrightness(struct gspca_dev *gspca_dev) k2 = ((int) sd->brightness - 0x8000) >> 10; switch (sd->sensor) { + case SENSOR_ADCM1700: + return; case SENSOR_HV7131R: expo = sd->brightness << 4; if (expo > 0x002dc6c0) @@ -1625,6 +1700,9 @@ static void setgamma(struct gspca_dev *gspca_dev) }; switch (sd->sensor) { + case SENSOR_ADCM1700: + gamma_base = gamma_spec_0; + break; case SENSOR_HV7131R: case SENSOR_MT9V111: gamma_base = gamma_spec_1; @@ -1804,6 +1882,8 @@ static int sd_start(struct gspca_dev *gspca_dev) int mode; static const u8 C0[] = { 0x2d, 0x2d, 0x3a, 0x05, 0x04, 0x3f }; static const u8 CA[] = { 0x28, 0xd8, 0x14, 0xec }; + static const u8 CA_adcm1700[] = + { 0x14, 0xec, 0x0a, 0xf6 }; static const u8 CE[] = { 0x32, 0xdd, 0x2d, 0xdd }; /* MI0360 */ static const u8 CE_ov76xx[] = { 0x32, 0xdd, 0x32, 0xdd }; @@ -1824,6 +1904,9 @@ static int sd_start(struct gspca_dev *gspca_dev) i2c_w_seq(gspca_dev, sensor_init[sd->sensor]); switch (sd->sensor) { + case SENSOR_ADCM1700: + reg2 = 0x60; + break; case SENSOR_OM6802: reg2 = 0x71; break; @@ -1842,17 +1925,28 @@ static int sd_start(struct gspca_dev *gspca_dev) reg_w1(gspca_dev, 0x12, sn9c1xx[0x12]); reg_w1(gspca_dev, 0x13, sn9c1xx[0x13]); reg_w1(gspca_dev, 0x18, sn9c1xx[0x18]); - reg_w1(gspca_dev, 0xd2, 0x6a); /* DC29 */ - reg_w1(gspca_dev, 0xd3, 0x50); + if (sd->sensor == SENSOR_ADCM1700) { + reg_w1(gspca_dev, 0xd2, 0x3a); /* DC29 */ + reg_w1(gspca_dev, 0xd3, 0x30); + } else { + reg_w1(gspca_dev, 0xd2, 0x6a); /* DC29 */ + reg_w1(gspca_dev, 0xd3, 0x50); + } reg_w1(gspca_dev, 0xc6, 0x00); reg_w1(gspca_dev, 0xc7, 0x00); - reg_w1(gspca_dev, 0xc8, 0x50); - reg_w1(gspca_dev, 0xc9, 0x3c); + if (sd->sensor == SENSOR_ADCM1700) { + reg_w1(gspca_dev, 0xc8, 0x2c); + reg_w1(gspca_dev, 0xc9, 0x24); + } else { + reg_w1(gspca_dev, 0xc8, 0x50); + reg_w1(gspca_dev, 0xc9, 0x3c); + } reg_w1(gspca_dev, 0x18, sn9c1xx[0x18]); switch (sd->sensor) { case SENSOR_MT9V111: reg17 = 0xe0; break; + case SENSOR_ADCM1700: case SENSOR_OV7630: reg17 = 0xe2; break; @@ -1881,6 +1975,10 @@ static int sd_start(struct gspca_dev *gspca_dev) for (i = 0; i < 8; i++) reg_w(gspca_dev, 0x84, reg84, sizeof reg84); switch (sd->sensor) { + case SENSOR_ADCM1700: + reg_w1(gspca_dev, 0x9a, 0x05); + reg_w1(gspca_dev, 0x99, 0x60); + break; case SENSOR_MT9V111: reg_w1(gspca_dev, 0x9a, 0x07); reg_w1(gspca_dev, 0x99, 0x59); @@ -1917,6 +2015,11 @@ static int sd_start(struct gspca_dev *gspca_dev) reg1 = 0x06; /* 640x480: clk 24Mhz, video trf enable */ reg17 = 0x61; /* 0x:20: enable sensor clock */ switch (sd->sensor) { + case SENSOR_ADCM1700: + init = adcm1700_sensor_param1; + reg1 = 0x46; + reg17 = 0xe2; + break; case SENSOR_MO4000: if (mode) { /* reg1 = 0x46; * 320 clk 48Mhz 60fp/s */ @@ -1986,8 +2089,12 @@ static int sd_start(struct gspca_dev *gspca_dev) } reg_w(gspca_dev, 0xc0, C0, 6); - reg_w(gspca_dev, 0xca, CA, 4); + if (sd->sensor == SENSOR_ADCM1700) + reg_w(gspca_dev, 0xca, CA_adcm1700, 4); + else + reg_w(gspca_dev, 0xca, CA, 4); switch (sd->sensor) { + case SENSOR_ADCM1700: case SENSOR_OV7630: case SENSOR_OV7648: case SENSOR_OV7660: @@ -2472,6 +2579,7 @@ static const __devinitdata struct usb_device_id device_table[] = { /* {USB_DEVICE(0x0c45, 0x6142), BS(SN9C120, PO2030N)}, *sn9c120b*/ {USB_DEVICE(0x0c45, 0x6143), BS(SN9C120, SP80708)}, /*sn9c120b*/ {USB_DEVICE(0x0c45, 0x6148), BS(SN9C120, OM6802)}, /*sn9c120b*/ + {USB_DEVICE(0x0c45, 0x614a), BS(SN9C120, ADCM1700)}, /*sn9c120b*/ {} }; MODULE_DEVICE_TABLE(usb, device_table); -- cgit v1.2.3-70-g09d2 From 9c6f97a02358c78ed1db69cde702db263bc62cf6 Mon Sep 17 00:00:00 2001 From: Dmitri Belimov Date: Mon, 21 Dec 2009 02:00:38 -0300 Subject: V4L/DVB (13945): Add lost config and PCI ID for card of Beholder Add lost configuration for our TV card. Signed-off-by: Beholder Intl. Ltd. Dmitry Belimov Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/CARDLIST.saa7134 | 1 + drivers/media/video/saa7134/saa7134-cards.c | 48 +++++++++++++++++++++++++++-- drivers/media/video/saa7134/saa7134-input.c | 3 +- drivers/media/video/saa7134/saa7134.h | 3 +- 4 files changed, 50 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/CARDLIST.saa7134 b/Documentation/video4linux/CARDLIST.saa7134 index fce1e7eb047..b4a767060ed 100644 --- a/Documentation/video4linux/CARDLIST.saa7134 +++ b/Documentation/video4linux/CARDLIST.saa7134 @@ -174,3 +174,4 @@ 173 -> Zolid Hybrid TV Tuner PCI [1131:2004] 174 -> Asus Europa Hybrid OEM [1043:4847] 175 -> Leadtek Winfast DTV1000S [107d:6655] +176 -> Beholder BeholdTV 505 RDS [0000:5051] diff --git a/drivers/media/video/saa7134/saa7134-cards.c b/drivers/media/video/saa7134/saa7134-cards.c index 03f572708b8..4c76db1efd0 100644 --- a/drivers/media/video/saa7134/saa7134-cards.c +++ b/drivers/media/video/saa7134/saa7134-cards.c @@ -4160,7 +4160,7 @@ struct saa7134_board saa7134_boards[] = { .amux = LINE2, }, }, - [SAA7134_BOARD_BEHOLD_505RDS] = { + [SAA7134_BOARD_BEHOLD_505RDS_MK5] = { /* Beholder Intl. Ltd. 2008 */ /*Dmitry Belimov */ .name = "Beholder BeholdTV 505 RDS", @@ -5320,6 +5320,41 @@ struct saa7134_board saa7134_boards[] = { .vmux = 8, } }, }, + [SAA7134_BOARD_BEHOLD_505RDS_MK3] = { + /* Beholder Intl. Ltd. 2008 */ + /*Dmitry Belimov */ + .name = "Beholder BeholdTV 505 RDS", + .audio_clock = 0x00200000, + .tuner_type = TUNER_PHILIPS_FM1216ME_MK3, + .radio_type = UNSET, + .tuner_addr = ADDR_UNSET, + .radio_addr = ADDR_UNSET, + .rds_addr = 0x10, + .tda9887_conf = TDA9887_PRESENT, + .gpiomask = 0x00008000, + .inputs = {{ + .name = name_tv, + .vmux = 3, + .amux = LINE2, + .tv = 1, + }, { + .name = name_comp1, + .vmux = 1, + .amux = LINE1, + }, { + .name = name_svideo, + .vmux = 8, + .amux = LINE1, + } }, + .mute = { + .name = name_mute, + .amux = LINE1, + }, + .radio = { + .name = name_radio, + .amux = LINE2, + }, + }, }; @@ -6235,7 +6270,13 @@ struct pci_device_id saa7134_pci_tbl[] = { .device = PCI_DEVICE_ID_PHILIPS_SAA7130, .subvendor = 0x0000, .subdevice = 0x505B, - .driver_data = SAA7134_BOARD_BEHOLD_505RDS, + .driver_data = SAA7134_BOARD_BEHOLD_505RDS_MK5, + }, { + .vendor = PCI_VENDOR_ID_PHILIPS, + .device = PCI_DEVICE_ID_PHILIPS_SAA7130, + .subvendor = 0x0000, + .subdevice = 0x5051, + .driver_data = SAA7134_BOARD_BEHOLD_505RDS_MK3, },{ .vendor = PCI_VENDOR_ID_PHILIPS, .device = PCI_DEVICE_ID_PHILIPS_SAA7130, @@ -6792,7 +6833,8 @@ int saa7134_board_init1(struct saa7134_dev *dev) case SAA7134_BOARD_BEHOLD_407FM: case SAA7134_BOARD_BEHOLD_409: case SAA7134_BOARD_BEHOLD_505FM: - case SAA7134_BOARD_BEHOLD_505RDS: + case SAA7134_BOARD_BEHOLD_505RDS_MK5: + case SAA7134_BOARD_BEHOLD_505RDS_MK3: case SAA7134_BOARD_BEHOLD_507_9FM: case SAA7134_BOARD_BEHOLD_507RDS_MK3: case SAA7134_BOARD_BEHOLD_507RDS_MK5: diff --git a/drivers/media/video/saa7134/saa7134-input.c b/drivers/media/video/saa7134/saa7134-input.c index a4eaf1b75d7..9499000f66b 100644 --- a/drivers/media/video/saa7134/saa7134-input.c +++ b/drivers/media/video/saa7134/saa7134-input.c @@ -568,7 +568,8 @@ int saa7134_input_init1(struct saa7134_dev *dev) case SAA7134_BOARD_BEHOLD_407FM: case SAA7134_BOARD_BEHOLD_409: case SAA7134_BOARD_BEHOLD_505FM: - case SAA7134_BOARD_BEHOLD_505RDS: + case SAA7134_BOARD_BEHOLD_505RDS_MK5: + case SAA7134_BOARD_BEHOLD_505RDS_MK3: case SAA7134_BOARD_BEHOLD_507_9FM: case SAA7134_BOARD_BEHOLD_507RDS_MK3: case SAA7134_BOARD_BEHOLD_507RDS_MK5: diff --git a/drivers/media/video/saa7134/saa7134.h b/drivers/media/video/saa7134/saa7134.h index 53b7e0b8a2f..bf130967ed1 100644 --- a/drivers/media/video/saa7134/saa7134.h +++ b/drivers/media/video/saa7134/saa7134.h @@ -282,7 +282,7 @@ struct saa7134_format { #define SAA7134_BOARD_HAUPPAUGE_HVR1120 156 #define SAA7134_BOARD_AVERMEDIA_STUDIO_507UA 157 #define SAA7134_BOARD_AVERMEDIA_CARDBUS_501 158 -#define SAA7134_BOARD_BEHOLD_505RDS 159 +#define SAA7134_BOARD_BEHOLD_505RDS_MK5 159 #define SAA7134_BOARD_BEHOLD_507RDS_MK3 160 #define SAA7134_BOARD_BEHOLD_507RDS_MK5 161 #define SAA7134_BOARD_BEHOLD_607FM_MK5 162 @@ -299,6 +299,7 @@ struct saa7134_format { #define SAA7134_BOARD_ZOLID_HYBRID_PCI 173 #define SAA7134_BOARD_ASUS_EUROPA_HYBRID 174 #define SAA7134_BOARD_LEADTEK_WINFAST_DTV1000S 175 +#define SAA7134_BOARD_BEHOLD_505RDS_MK3 176 #define SAA7134_MAXBOARDS 32 #define SAA7134_INPUT_MAX 8 -- cgit v1.2.3-70-g09d2 From c52af79916028f9d15638519b54a80ed1c10bce5 Mon Sep 17 00:00:00 2001 From: Jean-Francois Moine Date: Thu, 7 Jan 2010 05:18:16 -0300 Subject: V4L/DVB (13916): gspca - ov534/ov534_9: Split the ov534 subdriver. The two sensors ov772x and ov965x have too much differences. Signed-off-by: Jean-Francois Moine Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/gspca.txt | 3 +- drivers/media/video/gspca/Kconfig | 16 +- drivers/media/video/gspca/Makefile | 2 + drivers/media/video/gspca/ov534.c | 1242 +++-------------------------- drivers/media/video/gspca/ov534_9.c | 1477 +++++++++++++++++++++++++++++++++++ 5 files changed, 1597 insertions(+), 1143 deletions(-) create mode 100644 drivers/media/video/gspca/ov534_9.c (limited to 'Documentation') diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt index 7b603439509..c6364faa15a 100644 --- a/Documentation/video4linux/gspca.txt +++ b/Documentation/video4linux/gspca.txt @@ -190,8 +190,7 @@ spca500 06bd:0404 Agfa CL20 spca500 06be:0800 Optimedia sunplus 06d6:0031 Trust 610 LCD PowerC@m Zoom spca506 06e1:a190 ADS Instant VCD -ov534 06f8:3002 Hercules Blog Webcam -ov534 06f8:3003 Hercules Dualpix HD Weblog +ov534_9 06f8:3003 Hercules Dualpix HD Weblog sonixj 06f8:3004 Hercules Classic Silver sonixj 06f8:3008 Hercules Deluxe Optical Glass pac7302 06f8:3009 Hercules Classic Link diff --git a/drivers/media/video/gspca/Kconfig b/drivers/media/video/gspca/Kconfig index 824e95a3d88..561bab0874c 100644 --- a/drivers/media/video/gspca/Kconfig +++ b/drivers/media/video/gspca/Kconfig @@ -95,15 +95,25 @@ config USB_GSPCA_OV519 module will be called gspca_ov519. config USB_GSPCA_OV534 - tristate "OV534 USB Camera Driver" + tristate "OV534 OV772x USB Camera Driver" depends on VIDEO_V4L2 && USB_GSPCA help - Say Y here if you want support for cameras based on the OV534 chip. - (e.g. Sony Playstation EYE) + Say Y here if you want support for cameras based on the OV534 chip + and sensor OV772x (e.g. Sony Playstation EYE) To compile this driver as a module, choose M here: the module will be called gspca_ov534. +config USB_GSPCA_OV534_9 + tristate "OV534 OV965x USB Camera Driver" + depends on VIDEO_V4L2 && USB_GSPCA + help + Say Y here if you want support for cameras based on the OV534 chip + and sensor OV965x (e.g. Hercules Dualpix) + + To compile this driver as a module, choose M here: the + module will be called gspca_ov534_9. + config USB_GSPCA_PAC207 tristate "Pixart PAC207 USB Camera Driver" depends on VIDEO_V4L2 && USB_GSPCA diff --git a/drivers/media/video/gspca/Makefile b/drivers/media/video/gspca/Makefile index 82da9c3d204..553753d5c5e 100644 --- a/drivers/media/video/gspca/Makefile +++ b/drivers/media/video/gspca/Makefile @@ -8,6 +8,7 @@ obj-$(CONFIG_USB_GSPCA_MARS) += gspca_mars.o obj-$(CONFIG_USB_GSPCA_MR97310A) += gspca_mr97310a.o obj-$(CONFIG_USB_GSPCA_OV519) += gspca_ov519.o obj-$(CONFIG_USB_GSPCA_OV534) += gspca_ov534.o +obj-$(CONFIG_USB_GSPCA_OV534_9) += gspca_ov534_9.o obj-$(CONFIG_USB_GSPCA_PAC207) += gspca_pac207.o obj-$(CONFIG_USB_GSPCA_PAC7302) += gspca_pac7302.o obj-$(CONFIG_USB_GSPCA_PAC7311) += gspca_pac7311.o @@ -40,6 +41,7 @@ gspca_mars-objs := mars.o gspca_mr97310a-objs := mr97310a.o gspca_ov519-objs := ov519.o gspca_ov534-objs := ov534.o +gspca_ov534_9-objs := ov534_9.o gspca_pac207-objs := pac207.o gspca_pac7302-objs := pac7302.o gspca_pac7311-objs := pac7311.o diff --git a/drivers/media/video/gspca/ov534.c b/drivers/media/video/gspca/ov534.c index da55637d07e..0878c09e248 100644 --- a/drivers/media/video/gspca/ov534.c +++ b/drivers/media/video/gspca/ov534.c @@ -1,5 +1,5 @@ /* - * ov534 gspca driver + * ov534-ov772x gspca driver * * Copyright (C) 2008 Antonio Ospite * Copyright (C) 2008 Jim Paris @@ -68,12 +68,7 @@ struct sd { s8 sharpness; u8 hflip; u8 vflip; - u8 satur; - u8 lightfreq; - u8 sensor; -#define SENSOR_OV772X 0 -#define SENSOR_OV965X 1 }; /* V4L2 controls supported by the driver */ @@ -101,12 +96,8 @@ static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val); static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val); static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val); static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setsatur(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getsatur(struct gspca_dev *gspca_dev, __s32 *val); -static int sd_setfreq(struct gspca_dev *gspca_dev, __s32 val); -static int sd_getfreq(struct gspca_dev *gspca_dev, __s32 *val); -static const struct ctrl sd_ctrls_ov772x[] = { +static const struct ctrl sd_ctrls[] = { { /* 0 */ { .id = V4L2_CID_BRIGHTNESS, @@ -115,8 +106,8 @@ static const struct ctrl sd_ctrls_ov772x[] = { .minimum = 0, .maximum = 255, .step = 1, -#define BRIGHTNESS_77_DEF 20 - .default_value = BRIGHTNESS_77_DEF, +#define BRIGHTNESS_DEF 20 + .default_value = BRIGHTNESS_DEF, }, .set = sd_setbrightness, .get = sd_getbrightness, @@ -129,8 +120,8 @@ static const struct ctrl sd_ctrls_ov772x[] = { .minimum = 0, .maximum = 255, .step = 1, -#define CONTRAST_77_DEF 37 - .default_value = CONTRAST_77_DEF, +#define CONTRAST_DEF 37 + .default_value = CONTRAST_DEF, }, .set = sd_setcontrast, .get = sd_getcontrast, @@ -157,8 +148,8 @@ static const struct ctrl sd_ctrls_ov772x[] = { .minimum = 0, .maximum = 255, .step = 1, -#define EXPO_77_DEF 120 - .default_value = EXPO_77_DEF, +#define EXPO_DEF 120 + .default_value = EXPO_DEF, }, .set = sd_setexposure, .get = sd_getexposure, @@ -213,13 +204,13 @@ static const struct ctrl sd_ctrls_ov772x[] = { .minimum = 0, .maximum = 1, .step = 1, -#define AUTOGAIN_77_DEF 0 - .default_value = AUTOGAIN_77_DEF, +#define AUTOGAIN_DEF 0 + .default_value = AUTOGAIN_DEF, }, .set = sd_setautogain, .get = sd_getautogain, }, -#define AWB_77_IDX 8 +#define AWB_IDX 8 { /* 8 */ { .id = V4L2_CID_AUTO_WHITE_BALANCE, @@ -242,8 +233,8 @@ static const struct ctrl sd_ctrls_ov772x[] = { .minimum = 0, .maximum = 63, .step = 1, -#define SHARPNESS_77_DEF 0 - .default_value = SHARPNESS_77_DEF, +#define SHARPNESS_DEF 0 + .default_value = SHARPNESS_DEF, }, .set = sd_setsharpness, .get = sd_getsharpness, @@ -277,107 +268,6 @@ static const struct ctrl sd_ctrls_ov772x[] = { .get = sd_getvflip, }, }; -static const struct ctrl sd_ctrls_ov965x[] = { - { /* 0 */ - { - .id = V4L2_CID_BRIGHTNESS, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Brightness", - .minimum = 0, - .maximum = 15, - .step = 1, -#define BRIGHTNESS_96_DEF 7 - .default_value = BRIGHTNESS_96_DEF, - }, - .set = sd_setbrightness, - .get = sd_getbrightness, - }, - { /* 1 */ - { - .id = V4L2_CID_CONTRAST, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Contrast", - .minimum = 0, - .maximum = 15, - .step = 1, -#define CONTRAST_96_DEF 3 - .default_value = CONTRAST_96_DEF, - }, - .set = sd_setcontrast, - .get = sd_getcontrast, - }, - { /* 2 */ - { - .id = V4L2_CID_AUTOGAIN, - .type = V4L2_CTRL_TYPE_BOOLEAN, - .name = "Autogain", - .minimum = 0, - .maximum = 1, - .step = 1, -#define AUTOGAIN_96_DEF 1 - .default_value = AUTOGAIN_96_DEF, - }, - .set = sd_setautogain, - .get = sd_getautogain, - }, -#define EXPO_96_IDX 3 - { /* 3 */ - { - .id = V4L2_CID_EXPOSURE, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Exposure", - .minimum = 0, - .maximum = 3, - .step = 1, -#define EXPO_96_DEF 0 - .default_value = EXPO_96_DEF, - }, - .set = sd_setexposure, - .get = sd_getexposure, - }, - { /* 4 */ - { - .id = V4L2_CID_SHARPNESS, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Sharpness", - .minimum = -1, /* -1 = auto */ - .maximum = 4, - .step = 1, -#define SHARPNESS_96_DEF -1 - .default_value = SHARPNESS_96_DEF, - }, - .set = sd_setsharpness, - .get = sd_getsharpness, - }, - { /* 5 */ - { - .id = V4L2_CID_SATURATION, - .type = V4L2_CTRL_TYPE_INTEGER, - .name = "Saturation", - .minimum = 0, - .maximum = 4, - .step = 1, -#define SATUR_DEF 2 - .default_value = SATUR_DEF, - }, - .set = sd_setsatur, - .get = sd_getsatur, - }, - { - { - .id = V4L2_CID_POWER_LINE_FREQUENCY, - .type = V4L2_CTRL_TYPE_MENU, - .name = "Light frequency filter", - .minimum = 0, - .maximum = 2, /* 0: 0, 1: 50Hz, 2:60Hz */ - .step = 1, -#define FREQ_DEF 0 - .default_value = FREQ_DEF, - }, - .set = sd_setfreq, - .get = sd_getfreq, - }, -}; static const struct v4l2_pix_format ov772x_mode[] = { {320, 240, V4L2_PIX_FMT_YUYV, V4L2_FIELD_NONE, @@ -392,35 +282,7 @@ static const struct v4l2_pix_format ov772x_mode[] = { .priv = 0}, }; -static const struct v4l2_pix_format ov965x_mode[] = { - {320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, - .bytesperline = 320, - .sizeimage = 320 * 240 * 3 / 8 + 590, - .colorspace = V4L2_COLORSPACE_JPEG, - .priv = 4}, - {640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, - .bytesperline = 640, - .sizeimage = 640 * 480 * 3 / 8 + 590, - .colorspace = V4L2_COLORSPACE_JPEG, - .priv = 3}, - {800, 600, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, - .bytesperline = 800, - .sizeimage = 800 * 600 * 3 / 8 + 590, - .colorspace = V4L2_COLORSPACE_JPEG, - .priv = 2}, - {1024, 768, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, - .bytesperline = 1024, - .sizeimage = 1024 * 768 * 3 / 8 + 590, - .colorspace = V4L2_COLORSPACE_JPEG, - .priv = 1}, - {1280, 1024, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, - .bytesperline = 1280, - .sizeimage = 1280 * 1024 * 3 / 8 + 590, - .colorspace = V4L2_COLORSPACE_JPEG, - .priv = 0}, -}; - -static const u8 bridge_init_ov772x[][2] = { +static const u8 bridge_init[][2] = { { 0xc2, 0x0c }, { 0x88, 0xf8 }, { 0xc3, 0x69 }, @@ -478,7 +340,7 @@ static const u8 bridge_init_ov772x[][2] = { { 0xc1, 0x3c }, { 0xc2, 0x0c }, }; -static const u8 sensor_init_ov772x[][2] = { +static const u8 sensor_init[][2] = { { 0x12, 0x80 }, { 0x11, 0x01 }, /*fixme: better have a delay?*/ @@ -571,7 +433,7 @@ static const u8 sensor_init_ov772x[][2] = { { 0x8e, 0x00 }, /* De-noise threshold */ { 0x0c, 0xd0 } }; -static const u8 bridge_start_ov772x_vga[][2] = { +static const u8 bridge_start_vga[][2] = { {0x1c, 0x00}, {0x1d, 0x40}, {0x1d, 0x02}, @@ -582,7 +444,7 @@ static const u8 bridge_start_ov772x_vga[][2] = { {0xc0, 0x50}, {0xc1, 0x3c}, }; -static const u8 sensor_start_ov772x_vga[][2] = { +static const u8 sensor_start_vga[][2] = { {0x12, 0x00}, {0x17, 0x26}, {0x18, 0xa0}, @@ -592,7 +454,7 @@ static const u8 sensor_start_ov772x_vga[][2] = { {0x2c, 0xf0}, {0x65, 0x20}, }; -static const u8 bridge_start_ov772x_qvga[][2] = { +static const u8 bridge_start_qvga[][2] = { {0x1c, 0x00}, {0x1d, 0x40}, {0x1d, 0x02}, @@ -603,7 +465,7 @@ static const u8 bridge_start_ov772x_qvga[][2] = { {0xc0, 0x28}, {0xc1, 0x1e}, }; -static const u8 sensor_start_ov772x_qvga[][2] = { +static const u8 sensor_start_qvga[][2] = { {0x12, 0x40}, {0x17, 0x3f}, {0x18, 0x50}, @@ -614,571 +476,6 @@ static const u8 sensor_start_ov772x_qvga[][2] = { {0x65, 0x2f}, }; -static const u8 bridge_init_ov965x[][2] = { - {0x88, 0xf8}, - {0x89, 0xff}, - {0x76, 0x03}, - {0x92, 0x03}, - {0x95, 0x10}, - {0xe2, 0x00}, - {0xe7, 0x3e}, - {0x8d, 0x1c}, - {0x8e, 0x00}, - {0x8f, 0x00}, - {0x1f, 0x00}, - {0xc3, 0xf9}, - {0x89, 0xff}, - {0x88, 0xf8}, - {0x76, 0x03}, - {0x92, 0x01}, - {0x93, 0x18}, - {0x1c, 0x0a}, - {0x1d, 0x48}, - {0xc0, 0x50}, - {0xc1, 0x3c}, - {0x34, 0x05}, - {0xc2, 0x0c}, - {0xc3, 0xf9}, - {0x34, 0x05}, - {0xe7, 0x2e}, - {0x31, 0xf9}, - {0x35, 0x02}, - {0xd9, 0x10}, - {0x25, 0x42}, - {0x94, 0x11}, -}; - -static const u8 sensor_init_ov965x[][2] = { - {0x12, 0x80}, /* com7 - SSCB reset */ - {0x00, 0x00}, /* gain */ - {0x01, 0x80}, /* blue */ - {0x02, 0x80}, /* red */ - {0x03, 0x1b}, /* vref */ - {0x04, 0x03}, /* com1 - exposure low bits */ - {0x0b, 0x57}, /* ver */ - {0x0e, 0x61}, /* com5 */ - {0x0f, 0x42}, /* com6 */ - {0x11, 0x00}, /* clkrc */ - {0x12, 0x02}, /* com7 - 15fps VGA YUYV */ - {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ - {0x14, 0x28}, /* com9 */ - {0x16, 0x24}, /* reg16 */ - {0x17, 0x1d}, /* hstart*/ - {0x18, 0xbd}, /* hstop */ - {0x19, 0x01}, /* vstrt */ - {0x1a, 0x81}, /* vstop*/ - {0x1e, 0x04}, /* mvfp */ - {0x24, 0x3c}, /* aew */ - {0x25, 0x36}, /* aeb */ - {0x26, 0x71}, /* vpt */ - {0x27, 0x08}, /* bbias */ - {0x28, 0x08}, /* gbbias */ - {0x29, 0x15}, /* gr com */ - {0x2a, 0x00}, /* exhch */ - {0x2b, 0x00}, /* exhcl */ - {0x2c, 0x08}, /* rbias */ - {0x32, 0xff}, /* href */ - {0x33, 0x00}, /* chlf */ - {0x34, 0x3f}, /* aref1 */ - {0x35, 0x00}, /* aref2 */ - {0x36, 0xf8}, /* aref3 */ - {0x38, 0x72}, /* adc2 */ - {0x39, 0x57}, /* aref4 */ - {0x3a, 0x80}, /* tslb - yuyv */ - {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ - {0x3d, 0x99}, /* com13 */ - {0x3f, 0xc1}, /* edge */ - {0x40, 0xc0}, /* com15 */ - {0x41, 0x40}, /* com16 */ - {0x42, 0xc0}, /* com17 */ - {0x43, 0x0a}, /* rsvd */ - {0x44, 0xf0}, - {0x45, 0x46}, - {0x46, 0x62}, - {0x47, 0x2a}, - {0x48, 0x3c}, - {0x4a, 0xfc}, - {0x4b, 0xfc}, - {0x4c, 0x7f}, - {0x4d, 0x7f}, - {0x4e, 0x7f}, - {0x4f, 0x98}, /* matrix */ - {0x50, 0x98}, - {0x51, 0x00}, - {0x52, 0x28}, - {0x53, 0x70}, - {0x54, 0x98}, - {0x58, 0x1a}, /* matrix coef sign */ - {0x59, 0x85}, /* AWB control */ - {0x5a, 0xa9}, - {0x5b, 0x64}, - {0x5c, 0x84}, - {0x5d, 0x53}, - {0x5e, 0x0e}, - {0x5f, 0xf0}, /* AWB blue limit */ - {0x60, 0xf0}, /* AWB red limit */ - {0x61, 0xf0}, /* AWB green limit */ - {0x62, 0x00}, /* lcc1 */ - {0x63, 0x00}, /* lcc2 */ - {0x64, 0x02}, /* lcc3 */ - {0x65, 0x16}, /* lcc4 */ - {0x66, 0x01}, /* lcc5 */ - {0x69, 0x02}, /* hv */ - {0x6b, 0x5a}, /* dbvl */ - {0x6c, 0x04}, - {0x6d, 0x55}, - {0x6e, 0x00}, - {0x6f, 0x9d}, - {0x70, 0x21}, /* dnsth */ - {0x71, 0x78}, - {0x72, 0x00}, /* poidx */ - {0x73, 0x01}, /* pckdv */ - {0x74, 0x3a}, /* xindx */ - {0x75, 0x35}, /* yindx */ - {0x76, 0x01}, - {0x77, 0x02}, - {0x7a, 0x12}, /* gamma curve */ - {0x7b, 0x08}, - {0x7c, 0x16}, - {0x7d, 0x30}, - {0x7e, 0x5e}, - {0x7f, 0x72}, - {0x80, 0x82}, - {0x81, 0x8e}, - {0x82, 0x9a}, - {0x83, 0xa4}, - {0x84, 0xac}, - {0x85, 0xb8}, - {0x86, 0xc3}, - {0x87, 0xd6}, - {0x88, 0xe6}, - {0x89, 0xf2}, - {0x8a, 0x03}, - {0x8c, 0x89}, /* com19 */ - {0x14, 0x28}, /* com9 */ - {0x90, 0x7d}, - {0x91, 0x7b}, - {0x9d, 0x03}, /* lcc6 */ - {0x9e, 0x04}, /* lcc7 */ - {0x9f, 0x7a}, - {0xa0, 0x79}, - {0xa1, 0x40}, /* aechm */ - {0xa4, 0x50}, /* com21 */ - {0xa5, 0x68}, /* com26 */ - {0xa6, 0x4a}, /* AWB green */ - {0xa8, 0xc1}, /* refa8 */ - {0xa9, 0xef}, /* refa9 */ - {0xaa, 0x92}, - {0xab, 0x04}, - {0xac, 0x80}, /* black level control */ - {0xad, 0x80}, - {0xae, 0x80}, - {0xaf, 0x80}, - {0xb2, 0xf2}, - {0xb3, 0x20}, - {0xb4, 0x20}, /* ctrlb4 */ - {0xb5, 0x00}, - {0xb6, 0xaf}, - {0xbb, 0xae}, - {0xbc, 0x7f}, /* ADC channel offsets */ - {0xdb, 0x7f}, - {0xbe, 0x7f}, - {0xbf, 0x7f}, - {0xc0, 0xe2}, - {0xc1, 0xc0}, - {0xc2, 0x01}, - {0xc3, 0x4e}, - {0xc6, 0x85}, - {0xc7, 0x80}, /* com24 */ - {0xc9, 0xe0}, - {0xca, 0xe8}, - {0xcb, 0xf0}, - {0xcc, 0xd8}, - {0xcd, 0xf1}, - {0x4f, 0x98}, /* matrix */ - {0x50, 0x98}, - {0x51, 0x00}, - {0x52, 0x28}, - {0x53, 0x70}, - {0x54, 0x98}, - {0x58, 0x1a}, - {0xff, 0x41}, /* read 41, write ff 00 */ - {0x41, 0x40}, /* com16 */ - - {0xc5, 0x03}, /* 60 Hz banding filter */ - {0x6a, 0x02}, /* 50 Hz banding filter */ - - {0x12, 0x62}, /* com7 - 30fps VGA YUV */ - {0x36, 0xfa}, /* aref3 */ - {0x69, 0x0a}, /* hv */ - {0x8c, 0x89}, /* com22 */ - {0x14, 0x28}, /* com9 */ - {0x3e, 0x0c}, - {0x41, 0x40}, /* com16 */ - {0x72, 0x00}, - {0x73, 0x00}, - {0x74, 0x3a}, - {0x75, 0x35}, - {0x76, 0x01}, - {0xc7, 0x80}, - {0x03, 0x12}, /* vref */ - {0x17, 0x16}, /* hstart */ - {0x18, 0x02}, /* hstop */ - {0x19, 0x01}, /* vstrt */ - {0x1a, 0x3d}, /* vstop */ - {0x32, 0xff}, /* href */ - {0xc0, 0xaa}, -}; - -static const u8 bridge_init_ov965x_2[][2] = { - {0x94, 0xaa}, - {0xf1, 0x60}, - {0xe5, 0x04}, - {0xc0, 0x50}, - {0xc1, 0x3c}, - {0x8c, 0x00}, - {0x8d, 0x1c}, - {0x34, 0x05}, - - {0xc2, 0x0c}, - {0xc3, 0xf9}, - {0xda, 0x01}, - {0x50, 0x00}, - {0x51, 0xa0}, - {0x52, 0x3c}, - {0x53, 0x00}, - {0x54, 0x00}, - {0x55, 0x00}, - {0x57, 0x00}, - {0x5c, 0x00}, - {0x5a, 0xa0}, - {0x5b, 0x78}, - {0x35, 0x02}, - {0xd9, 0x10}, - {0x94, 0x11}, -}; - -static const u8 sensor_init_ov965x_2[][2] = { - {0x3b, 0xc4}, - {0x1e, 0x04}, /* mvfp */ - {0x13, 0xe0}, /* com8 */ - {0x00, 0x00}, /* gain */ - {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ - {0x11, 0x03}, /* clkrc */ - {0x6b, 0x5a}, /* dblv */ - {0x6a, 0x05}, - {0xc5, 0x07}, - {0xa2, 0x4b}, - {0xa3, 0x3e}, - {0x2d, 0x00}, - {0xff, 0x42}, /* read 42, write ff 00 */ - {0x42, 0xc0}, /* com17 */ - {0x2d, 0x00}, - {0xff, 0x42}, /* read 42, write ff 00 */ - {0x42, 0xc1}, /* com17 */ -/* sharpness */ - {0x3f, 0x01}, - {0xff, 0x42}, /* read 42, write ff 00 */ - {0x42, 0xc1}, /* com17 */ -/* saturation */ - {0x4f, 0x98}, /* matrix */ - {0x50, 0x98}, - {0x51, 0x00}, - {0x52, 0x28}, - {0x53, 0x70}, - {0x54, 0x98}, - {0x58, 0x1a}, - {0xff, 0x41}, /* read 41, write ff 00 */ - {0x41, 0x40}, /* com16 */ -/* contrast */ - {0x56, 0x40}, -/* brightness */ - {0x55, 0x8f}, -/* expo */ - {0x10, 0x25}, /* aech - exposure high bits */ - {0xff, 0x13}, /* read 13, write ff 00 */ - {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ -}; - -static const u8 sensor_start_ov965x_1_vga[][2] = { /* same for qvga */ - {0x12, 0x62}, /* com7 - 30fps VGA YUV */ - {0x36, 0xfa}, /* aref3 */ - {0x69, 0x0a}, /* hv */ - {0x8c, 0x89}, /* com22 */ - {0x14, 0x28}, /* com9 */ - {0x3e, 0x0c}, /* com14 */ - {0x41, 0x40}, /* com16 */ - {0x72, 0x00}, - {0x73, 0x00}, - {0x74, 0x3a}, - {0x75, 0x35}, - {0x76, 0x01}, - {0xc7, 0x80}, /* com24 */ - {0x03, 0x12}, /* vref */ - {0x17, 0x16}, /* hstart */ - {0x18, 0x02}, /* hstop */ - {0x19, 0x01}, /* vstrt */ - {0x1a, 0x3d}, /* vstop */ - {0x32, 0xff}, /* href */ - {0xc0, 0xaa}, -}; - -static const u8 sensor_start_ov965x_1_svga[][2] = { - {0x12, 0x02}, /* com7 - YUYV - VGA 15 full resolution */ - {0x36, 0xf8}, /* aref3 */ - {0x69, 0x02}, /* hv */ - {0x8c, 0x0d}, /* com22 */ - {0x3e, 0x0c}, /* com14 */ - {0x41, 0x40}, /* com16 */ - {0x72, 0x00}, - {0x73, 0x01}, - {0x74, 0x3a}, - {0x75, 0x35}, - {0x76, 0x01}, - {0xc7, 0x80}, /* com24 */ - {0x03, 0x1b}, /* vref */ - {0x17, 0x1d}, /* hstart */ - {0x18, 0xbd}, /* hstop */ - {0x19, 0x01}, /* vstrt */ - {0x1a, 0x81}, /* vstop */ - {0x32, 0xff}, /* href */ - {0xc0, 0xe2}, -}; - -static const u8 sensor_start_ov965x_1_xga[][2] = { - {0x12, 0x02}, /* com7 */ - {0x36, 0xf8}, /* aref3 */ - {0x69, 0x02}, /* hv */ - {0x8c, 0x89}, /* com22 */ - {0x14, 0x28}, /* com9 */ - {0x3e, 0x0c}, /* com14 */ - {0x41, 0x40}, /* com16 */ - {0x72, 0x00}, - {0x73, 0x01}, - {0x74, 0x3a}, - {0x75, 0x35}, - {0x76, 0x01}, - {0xc7, 0x80}, /* com24 */ - {0x03, 0x1b}, /* vref */ - {0x17, 0x1d}, /* hstart */ - {0x18, 0xbd}, /* hstop */ - {0x19, 0x01}, /* vstrt */ - {0x1a, 0x81}, /* vstop */ - {0x32, 0xff}, /* href */ - {0xc0, 0xe2}, -}; - -static const u8 sensor_start_ov965x_1_sxga[][2] = { - {0x12, 0x02}, /* com7 */ - {0x36, 0xf8}, /* aref3 */ - {0x69, 0x02}, /* hv */ - {0x8c, 0x89}, /* com22 */ - {0x14, 0x28}, /* com9 */ - {0x3e, 0x0c}, /* com14 */ - {0x41, 0x40}, /* com16 */ - {0x72, 0x00}, - {0x73, 0x01}, - {0x74, 0x3a}, - {0x75, 0x35}, - {0x76, 0x01}, - {0xc7, 0x80}, /* com24 */ - {0x03, 0x1b}, /* vref */ - {0x17, 0x1d}, /* hstart */ - {0x18, 0x02}, /* hstop */ - {0x19, 0x01}, /* vstrt */ - {0x1a, 0x81}, /* vstop */ - {0x32, 0xff}, /* href */ - {0xc0, 0xe2}, -}; - -static const u8 bridge_start_ov965x_qvga[][2] = { - {0x94, 0xaa}, - {0xf1, 0x60}, - {0xe5, 0x04}, - {0xc0, 0x50}, - {0xc1, 0x3c}, - {0x8c, 0x00}, - {0x8d, 0x1c}, - {0x34, 0x05}, - - {0xc2, 0x4c}, - {0xc3, 0xf9}, - {0xda, 0x00}, - {0x50, 0x00}, - {0x51, 0xa0}, - {0x52, 0x78}, - {0x53, 0x00}, - {0x54, 0x00}, - {0x55, 0x00}, - {0x57, 0x00}, - {0x5c, 0x00}, - {0x5a, 0x50}, - {0x5b, 0x3c}, - {0x35, 0x02}, - {0xd9, 0x10}, - {0x94, 0x11}, -}; - -static const u8 bridge_start_ov965x_vga[][2] = { - {0x94, 0xaa}, - {0xf1, 0x60}, - {0xe5, 0x04}, - {0xc0, 0x50}, - {0xc1, 0x3c}, - {0x8c, 0x00}, - {0x8d, 0x1c}, - {0x34, 0x05}, - {0xc2, 0x0c}, - {0xc3, 0xf9}, - {0xda, 0x01}, - {0x50, 0x00}, - {0x51, 0xa0}, - {0x52, 0x3c}, - {0x53, 0x00}, - {0x54, 0x00}, - {0x55, 0x00}, - {0x57, 0x00}, - {0x5c, 0x00}, - {0x5a, 0xa0}, - {0x5b, 0x78}, - {0x35, 0x02}, - {0xd9, 0x10}, - {0x94, 0x11}, -}; - -static const u8 bridge_start_ov965x_svga[][2] = { - {0x94, 0xaa}, - {0xf1, 0x60}, - {0xe5, 0x04}, - {0xc0, 0xa0}, - {0xc1, 0x80}, - {0x8c, 0x00}, - {0x8d, 0x1c}, - {0x34, 0x05}, - {0xc2, 0x4c}, - {0xc3, 0xf9}, - {0x50, 0x00}, - {0x51, 0x40}, - {0x52, 0x00}, - {0x53, 0x00}, - {0x54, 0x00}, - {0x55, 0x88}, - {0x57, 0x00}, - {0x5c, 0x00}, - {0x5a, 0xc8}, - {0x5b, 0x96}, - {0x35, 0x02}, - {0xd9, 0x10}, - {0xda, 0x00}, - {0x94, 0x11}, -}; - -static const u8 bridge_start_ov965x_xga[][2] = { - {0x94, 0xaa}, - {0xf1, 0x60}, - {0xe5, 0x04}, - {0xc0, 0xa0}, - {0xc1, 0x80}, - {0x8c, 0x00}, - {0x8d, 0x1c}, - {0x34, 0x05}, - {0xc2, 0x4c}, - {0xc3, 0xf9}, - {0x50, 0x00}, - {0x51, 0x40}, - {0x52, 0x00}, - {0x53, 0x00}, - {0x54, 0x00}, - {0x55, 0x88}, - {0x57, 0x00}, - {0x5c, 0x01}, - {0x5a, 0x00}, - {0x5b, 0xc0}, - {0x35, 0x02}, - {0xd9, 0x10}, - {0xda, 0x01}, - {0x94, 0x11}, -}; - -static const u8 bridge_start_ov965x_sxga[][2] = { - {0x94, 0xaa}, - {0xf1, 0x60}, - {0xe5, 0x04}, - {0xc0, 0xa0}, - {0xc1, 0x80}, - {0x8c, 0x00}, - {0x8d, 0x1c}, - {0x34, 0x05}, - {0xc2, 0x0c}, - {0xc3, 0xf9}, - {0xda, 0x00}, - {0x35, 0x02}, - {0xd9, 0x10}, - {0x94, 0x11}, -}; - -static const u8 sensor_start_ov965x_2_qvga[][2] = { - {0x3b, 0xe4}, /* com11 - night mode 1/4 frame rate */ - {0x1e, 0x04}, /* mvfp */ - {0x13, 0xe0}, /* com8 */ - {0x00, 0x00}, - {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ - {0x11, 0x01}, /* clkrc */ - {0x6b, 0x5a}, /* dblv */ - {0x6a, 0x02}, /* 50 Hz banding filter */ - {0xc5, 0x03}, /* 60 Hz banding filter */ - {0xa2, 0x96}, /* bd50 */ - {0xa3, 0x7d}, /* bd60 */ - - {0xff, 0x13}, /* read 13, write ff 00 */ - {0x13, 0xe7}, - {0x3a, 0x80}, /* tslb - yuyv */ -}; - -static const u8 sensor_start_ov965x_2_vga[][2] = { - {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ - {0x1e, 0x04}, /* mvfp */ - {0x13, 0xe0}, /* com8 */ - {0x00, 0x00}, - {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ - {0x11, 0x03}, /* clkrc */ - {0x6b, 0x5a}, /* dblv */ - {0x6a, 0x05}, /* 50 Hz banding filter */ - {0xc5, 0x07}, /* 60 Hz banding filter */ - {0xa2, 0x4b}, /* bd50 */ - {0xa3, 0x3e}, /* bd60 */ - - {0x2d, 0x00}, /* advfl */ -}; - -static const u8 sensor_start_ov965x_2_svga[][2] = { /* same for xga */ - {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ - {0x1e, 0x04}, /* mvfp */ - {0x13, 0xe0}, /* com8 */ - {0x00, 0x00}, - {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ - {0x11, 0x01}, /* clkrc */ - {0x6b, 0x5a}, /* dblv */ - {0x6a, 0x0c}, /* 50 Hz banding filter */ - {0xc5, 0x0f}, /* 60 Hz banding filter */ - {0xa2, 0x4e}, /* bd50 */ - {0xa3, 0x41}, /* bd60 */ -}; - -static const u8 sensor_start_ov965x_2_sxga[][2] = { - {0x13, 0xe0}, /* com8 */ - {0x00, 0x00}, - {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ - {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ - {0x1e, 0x04}, /* mvfp */ - {0x11, 0x01}, /* clkrc */ - {0x6b, 0x5a}, /* dblv */ - {0x6a, 0x0c}, /* 50 Hz banding filter */ - {0xc5, 0x0f}, /* 60 Hz banding filter */ - {0xa2, 0x4e}, /* bd50 */ - {0xa3, 0x41}, /* bd60 */ -}; - static void ov534_reg_write(struct gspca_dev *gspca_dev, u16 reg, u8 val) { struct usb_device *udev = gspca_dev->dev; @@ -1360,14 +657,14 @@ static void set_frame_rate(struct gspca_dev *gspca_dev) PDEBUG(D_PROBE, "frame_rate: %d", r->fps); } -static void setbrightness_77(struct gspca_dev *gspca_dev) +static void setbrightness(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; sccb_reg_write(gspca_dev, 0x9B, sd->brightness); } -static void setcontrast_77(struct gspca_dev *gspca_dev) +static void setcontrast(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; @@ -1401,7 +698,7 @@ static void setgain(struct gspca_dev *gspca_dev) sccb_reg_write(gspca_dev, 0x00, val); } -static void setexposure_77(struct gspca_dev *gspca_dev) +static void setexposure(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; u8 val; @@ -1432,7 +729,7 @@ static void sethue(struct gspca_dev *gspca_dev) sccb_reg_write(gspca_dev, 0x01, sd->hue); } -static void setautogain_77(struct gspca_dev *gspca_dev) +static void setautogain(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; @@ -1457,7 +754,7 @@ static void setawb(struct gspca_dev *gspca_dev) sccb_reg_write(gspca_dev, 0x63, 0xaa); /* AWB off */ } -static void setsharpness_77(struct gspca_dev *gspca_dev) +static void setsharpness(struct gspca_dev *gspca_dev) { struct sd *sd = (struct sd *) gspca_dev; u8 val; @@ -1491,132 +788,6 @@ static void setvflip(struct gspca_dev *gspca_dev) sccb_reg_read(gspca_dev, 0x0c) & 0x7f); } -/* ov965x specific controls */ -static void setbrightness_96(struct gspca_dev *gspca_dev) -{ - struct sd *sd = (struct sd *) gspca_dev; - u8 val; - - val = sd->brightness; - if (val < 8) - val = 15 - val; /* f .. 8 */ - else - val = val - 8; /* 0 .. 7 */ - sccb_reg_write(gspca_dev, 0x55, /* brtn - brightness adjustment */ - 0x0f | (val << 4)); -} - -static void setcontrast_96(struct gspca_dev *gspca_dev) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sccb_reg_write(gspca_dev, 0x56, /* cnst1 - contrast 1 ctrl coeff */ - sd->contrast << 4); -} - -static void setexposure_96(struct gspca_dev *gspca_dev) -{ - struct sd *sd = (struct sd *) gspca_dev; - u8 val; - static const u8 expo[4] = {0x00, 0x25, 0x38, 0x5e}; - - sccb_reg_write(gspca_dev, 0x10, /* aec[9:2] */ - expo[sd->exposure]); - val = sccb_reg_read(gspca_dev, 0x13); /* com8 */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - sccb_reg_write(gspca_dev, 0x13, val); - val = sccb_reg_read(gspca_dev, 0xa1); /* aech */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - sccb_reg_write(gspca_dev, 0xa1, val & 0xe0); /* aec[15:10] = 0 */ -} - -static void setsharpness_96(struct gspca_dev *gspca_dev) -{ - struct sd *sd = (struct sd *) gspca_dev; - s8 val; - - val = sd->sharpness; - if (val < 0) { /* auto */ - val = sccb_reg_read(gspca_dev, 0x42); /* com17 */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - sccb_reg_write(gspca_dev, 0x42, val | 0x40); - /* Edge enhancement strength auto adjust */ - return; - } - if (val != 0) - val = 1 << (val - 1); - sccb_reg_write(gspca_dev, 0x3f, /* edge - edge enhance. factor */ - val); - val = sccb_reg_read(gspca_dev, 0x42); /* com17 */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - sccb_reg_write(gspca_dev, 0x42, val & 0xbf); -} - -static void setautogain_96(struct gspca_dev *gspca_dev) -{ - struct sd *sd = (struct sd *) gspca_dev; - u8 val; - -/*fixme: should adjust agc/awb/aec by different controls */ - val = sd->autogain; - val = sccb_reg_read(gspca_dev, 0x13); /* com8 */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - if (sd->autogain) - val |= 0x05; /* agc & aec */ - else - val &= 0xfa; - sccb_reg_write(gspca_dev, 0x13, val); -} - -static void setsatur(struct gspca_dev *gspca_dev) -{ - struct sd *sd = (struct sd *) gspca_dev; - u8 val1, val2, val3; - static const u8 matrix[5][2] = { - {0x14, 0x38}, - {0x1e, 0x54}, - {0x28, 0x70}, - {0x32, 0x8c}, - {0x48, 0x90} - }; - - val1 = matrix[sd->satur][0]; - val2 = matrix[sd->satur][1]; - val3 = val1 + val2; - sccb_reg_write(gspca_dev, 0x4f, val3); /* matrix coeff */ - sccb_reg_write(gspca_dev, 0x50, val3); - sccb_reg_write(gspca_dev, 0x51, 0x00); - sccb_reg_write(gspca_dev, 0x52, val1); - sccb_reg_write(gspca_dev, 0x53, val2); - sccb_reg_write(gspca_dev, 0x54, val3); - sccb_reg_write(gspca_dev, 0x58, 0x1a); /* mtxs - coeff signs */ - val1 = sccb_reg_read(gspca_dev, 0x41); /* com16 */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - sccb_reg_write(gspca_dev, 0x41, val1); -} - -static void setfreq(struct gspca_dev *gspca_dev) -{ - struct sd *sd = (struct sd *) gspca_dev; - u8 val; - - val = sccb_reg_read(gspca_dev, 0x13); /* com8 */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - if (sd->lightfreq == 0) { - sccb_reg_write(gspca_dev, 0x13, val & 0xdf); - return; - } - sccb_reg_write(gspca_dev, 0x13, val | 0x20); - - val = sccb_reg_read(gspca_dev, 0x42); /* com17 */ - sccb_reg_write(gspca_dev, 0xff, 0x00); - if (sd->lightfreq == 1) - val |= 0x01; - else - val &= 0xfe; - sccb_reg_write(gspca_dev, 0x42, val); -} - /* this function is called at probe time */ static int sd_config(struct gspca_dev *gspca_dev, const struct usb_device_id *id) @@ -1624,77 +795,49 @@ static int sd_config(struct gspca_dev *gspca_dev, struct sd *sd = (struct sd *) gspca_dev; struct cam *cam; - sd->sensor = id->driver_info; - cam = &gspca_dev->cam; - if (sd->sensor == SENSOR_OV772X) { - cam->cam_mode = ov772x_mode; - cam->nmodes = ARRAY_SIZE(ov772x_mode); + cam->cam_mode = ov772x_mode; + cam->nmodes = ARRAY_SIZE(ov772x_mode); - cam->bulk = 1; - cam->bulk_size = 16384; - cam->bulk_nurbs = 2; - } else { /* ov965x */ - cam->cam_mode = ov965x_mode; - cam->nmodes = ARRAY_SIZE(ov965x_mode); - } + cam->bulk = 1; + cam->bulk_size = 16384; + cam->bulk_nurbs = 2; sd->frame_rate = 30; - if (sd->sensor == SENSOR_OV772X) { - sd->brightness = BRIGHTNESS_77_DEF; - sd->contrast = CONTRAST_77_DEF; - sd->gain = GAIN_DEF; - sd->exposure = EXPO_77_DEF; - sd->redblc = RED_BALANCE_DEF; - sd->blueblc = BLUE_BALANCE_DEF; - sd->hue = HUE_DEF; -#if AUTOGAIN_77_DEF != 0 - sd->autogain = AUTOGAIN_77_DEF; + sd->brightness = BRIGHTNESS_DEF; + sd->contrast = CONTRAST_DEF; + sd->gain = GAIN_DEF; + sd->exposure = EXPO_DEF; + sd->redblc = RED_BALANCE_DEF; + sd->blueblc = BLUE_BALANCE_DEF; + sd->hue = HUE_DEF; +#if AUTOGAIN_DEF != 0 + sd->autogain = AUTOGAIN_DEF; #else - gspca_dev->ctrl_inac |= (1 << AWB_77_IDX); + gspca_dev->ctrl_inac |= (1 << AWB_IDX); #endif #if AWB_DEF != 0 - sd->awb = AWB_DEF + sd->awb = AWB_DEF #endif -#if SHARPNESS_77_DEF != 0 - sd->sharpness = SHARPNESS_77_DEF; +#if SHARPNESS_DEF != 0 + sd->sharpness = SHARPNESS_DEF; #endif #if HFLIP_DEF != 0 - sd->hflip = HFLIP_DEF; + sd->hflip = HFLIP_DEF; #endif #if VFLIP_DEF != 0 - sd->vflip = VFLIP_DEF; -#endif - } else { - sd->brightness = BRIGHTNESS_96_DEF; - sd->contrast = CONTRAST_96_DEF; -#if AUTOGAIN_96_DEF != 0 - sd->autogain = AUTOGAIN_96_DEF; - gspca_dev->ctrl_inac |= (1 << EXPO_96_IDX); -#endif -#if EXPO_96_DEF != 0 - sd->exposure = EXPO_96_DEF; + sd->vflip = VFLIP_DEF; #endif -#if SHARPNESS_96_DEF != 0 - sd->sharpness = SHARPNESS_96_DEF; -#endif - sd->satur = SATUR_DEF; - sd->lightfreq = FREQ_DEF; - } + return 0; } /* this function is called at probe and resume time */ static int sd_init(struct gspca_dev *gspca_dev) { - struct sd *sd = (struct sd *) gspca_dev; u16 sensor_id; - static const u8 sensor_addr[2] = { - 0x42, /* 0 SENSOR_OV772X */ - 0x60, /* 1 SENSOR_OV965X */ - }; /* reset bridge */ ov534_reg_write(gspca_dev, 0xe7, 0x3a); @@ -1702,8 +845,7 @@ static int sd_init(struct gspca_dev *gspca_dev) msleep(100); /* initialize the sensor address */ - ov534_reg_write(gspca_dev, OV534_REG_ADDRESS, - sensor_addr[sd->sensor]); + ov534_reg_write(gspca_dev, OV534_REG_ADDRESS, 0x42); /* reset sensor */ sccb_reg_write(gspca_dev, 0x12, 0x80); @@ -1717,64 +859,46 @@ static int sd_init(struct gspca_dev *gspca_dev) PDEBUG(D_PROBE, "Sensor ID: %04x", sensor_id); /* initialize */ - switch (sd->sensor) { - case SENSOR_OV772X: - reg_w_array(gspca_dev, bridge_init_ov772x, - ARRAY_SIZE(bridge_init_ov772x)); - ov534_set_led(gspca_dev, 1); - sccb_w_array(gspca_dev, sensor_init_ov772x, - ARRAY_SIZE(sensor_init_ov772x)); - ov534_reg_write(gspca_dev, 0xe0, 0x09); - ov534_set_led(gspca_dev, 0); - set_frame_rate(gspca_dev); - break; - default: -/* case SENSOR_OV965X: */ - reg_w_array(gspca_dev, bridge_init_ov965x, - ARRAY_SIZE(bridge_init_ov965x)); - sccb_w_array(gspca_dev, sensor_init_ov965x, - ARRAY_SIZE(sensor_init_ov965x)); - reg_w_array(gspca_dev, bridge_init_ov965x_2, - ARRAY_SIZE(bridge_init_ov965x_2)); - sccb_w_array(gspca_dev, sensor_init_ov965x_2, - ARRAY_SIZE(sensor_init_ov965x_2)); - ov534_reg_write(gspca_dev, 0xe0, 0x00); - ov534_reg_write(gspca_dev, 0xe0, 0x01); - ov534_set_led(gspca_dev, 0); - ov534_reg_write(gspca_dev, 0xe0, 0x00); - } + reg_w_array(gspca_dev, bridge_init, + ARRAY_SIZE(bridge_init)); + ov534_set_led(gspca_dev, 1); + sccb_w_array(gspca_dev, sensor_init, + ARRAY_SIZE(sensor_init)); + ov534_reg_write(gspca_dev, 0xe0, 0x09); + ov534_set_led(gspca_dev, 0); + set_frame_rate(gspca_dev); return 0; } -static int sd_start_ov772x(struct gspca_dev *gspca_dev) +static int sd_start(struct gspca_dev *gspca_dev) { int mode; mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv; if (mode != 0) { /* 320x240 */ - reg_w_array(gspca_dev, bridge_start_ov772x_qvga, - ARRAY_SIZE(bridge_start_ov772x_qvga)); - sccb_w_array(gspca_dev, sensor_start_ov772x_qvga, - ARRAY_SIZE(sensor_start_ov772x_qvga)); + reg_w_array(gspca_dev, bridge_start_qvga, + ARRAY_SIZE(bridge_start_qvga)); + sccb_w_array(gspca_dev, sensor_start_qvga, + ARRAY_SIZE(sensor_start_qvga)); } else { /* 640x480 */ - reg_w_array(gspca_dev, bridge_start_ov772x_vga, - ARRAY_SIZE(bridge_start_ov772x_vga)); - sccb_w_array(gspca_dev, sensor_start_ov772x_vga, - ARRAY_SIZE(sensor_start_ov772x_vga)); + reg_w_array(gspca_dev, bridge_start_vga, + ARRAY_SIZE(bridge_start_vga)); + sccb_w_array(gspca_dev, sensor_start_vga, + ARRAY_SIZE(sensor_start_vga)); } set_frame_rate(gspca_dev); - setautogain_77(gspca_dev); + setautogain(gspca_dev); setawb(gspca_dev); setgain(gspca_dev); setredblc(gspca_dev); setblueblc(gspca_dev); sethue(gspca_dev); - setexposure_77(gspca_dev); - setbrightness_77(gspca_dev); - setcontrast_77(gspca_dev); - setsharpness_77(gspca_dev); + setexposure(gspca_dev); + setbrightness(gspca_dev); + setcontrast(gspca_dev); + setsharpness(gspca_dev); setvflip(gspca_dev); sethflip(gspca_dev); @@ -1783,81 +907,12 @@ static int sd_start_ov772x(struct gspca_dev *gspca_dev) return 0; } -static int sd_start_ov965x(struct gspca_dev *gspca_dev) -{ - int mode; - - mode = gspca_dev->cam.cam_mode[gspca_dev->curr_mode].priv; - switch (mode) { - default: -/* case 4: * 320x240 */ - sccb_w_array(gspca_dev, sensor_start_ov965x_1_vga, - ARRAY_SIZE(sensor_start_ov965x_1_vga)); - reg_w_array(gspca_dev, bridge_start_ov965x_qvga, - ARRAY_SIZE(bridge_start_ov965x_qvga)); - sccb_w_array(gspca_dev, sensor_start_ov965x_2_qvga, - ARRAY_SIZE(sensor_start_ov965x_2_qvga)); - break; - case 3: /* 640x480 */ - sccb_w_array(gspca_dev, sensor_start_ov965x_1_vga, - ARRAY_SIZE(sensor_start_ov965x_1_vga)); - reg_w_array(gspca_dev, bridge_start_ov965x_vga, - ARRAY_SIZE(bridge_start_ov965x_vga)); - sccb_w_array(gspca_dev, sensor_start_ov965x_2_vga, - ARRAY_SIZE(sensor_start_ov965x_2_vga)); - break; - case 2: /* 800x600 */ - sccb_w_array(gspca_dev, sensor_start_ov965x_1_svga, - ARRAY_SIZE(sensor_start_ov965x_1_svga)); - reg_w_array(gspca_dev, bridge_start_ov965x_svga, - ARRAY_SIZE(bridge_start_ov965x_svga)); - sccb_w_array(gspca_dev, sensor_start_ov965x_2_svga, - ARRAY_SIZE(sensor_start_ov965x_2_svga)); - break; - case 1: /* 1024x768 */ - sccb_w_array(gspca_dev, sensor_start_ov965x_1_xga, - ARRAY_SIZE(sensor_start_ov965x_1_xga)); - reg_w_array(gspca_dev, bridge_start_ov965x_xga, - ARRAY_SIZE(bridge_start_ov965x_xga)); - sccb_w_array(gspca_dev, sensor_start_ov965x_2_svga, - ARRAY_SIZE(sensor_start_ov965x_2_svga)); - break; - case 0: /* 1280x1024 */ - sccb_w_array(gspca_dev, sensor_start_ov965x_1_sxga, - ARRAY_SIZE(sensor_start_ov965x_1_sxga)); - reg_w_array(gspca_dev, bridge_start_ov965x_sxga, - ARRAY_SIZE(bridge_start_ov965x_sxga)); - sccb_w_array(gspca_dev, sensor_start_ov965x_2_sxga, - ARRAY_SIZE(sensor_start_ov965x_2_sxga)); - break; - } - setfreq(gspca_dev); - setautogain_96(gspca_dev); - setbrightness_96(gspca_dev); - setcontrast_96(gspca_dev); - setexposure_96(gspca_dev); - setsharpness_96(gspca_dev); - setsatur(gspca_dev); - - ov534_reg_write(gspca_dev, 0xe0, 0x00); - ov534_reg_write(gspca_dev, 0xe0, 0x00); - ov534_set_led(gspca_dev, 1); - return 0; -} - -static void sd_stopN_ov772x(struct gspca_dev *gspca_dev) +static void sd_stopN(struct gspca_dev *gspca_dev) { ov534_reg_write(gspca_dev, 0xe0, 0x09); ov534_set_led(gspca_dev, 0); } -static void sd_stopN_ov965x(struct gspca_dev *gspca_dev) -{ - ov534_reg_write(gspca_dev, 0xe0, 0x01); - ov534_set_led(gspca_dev, 0); - ov534_reg_write(gspca_dev, 0xe0, 0x00); -} - /* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */ #define UVC_STREAM_EOH (1 << 7) #define UVC_STREAM_ERR (1 << 6) @@ -1875,11 +930,9 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, __u32 this_pts; u16 this_fid; int remaining_len = len; - int payload_len; - payload_len = gspca_dev->cam.bulk ? 2048 : 2040; do { - len = min(remaining_len, payload_len); + len = min(remaining_len, 2048); /* Payloads are prefixed with a UVC-style header. We consider a frame to start when the FID toggles, or the PTS @@ -1918,7 +971,17 @@ static void sd_pkt_scan(struct gspca_dev *gspca_dev, data + 12, len - 12); /* If this packet is marked as EOF, end the frame */ } else if (data[1] & UVC_STREAM_EOF) { + struct gspca_frame *frame; + sd->last_pts = 0; + frame = gspca_get_i_frame(gspca_dev); + if (frame == NULL) + goto discard; + if (frame->data_end - frame->data != + gspca_dev->width * gspca_dev->height * 2) { + PDEBUG(D_PACK, "short frame"); + goto discard; + } gspca_frame_add(gspca_dev, LAST_PACKET, data + 12, len - 12); } else { @@ -1965,12 +1028,8 @@ static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val) struct sd *sd = (struct sd *) gspca_dev; sd->exposure = val; - if (gspca_dev->streaming) { - if (sd->sensor == SENSOR_OV772X) - setexposure_77(gspca_dev); - else - setexposure_96(gspca_dev); - } + if (gspca_dev->streaming) + setexposure(gspca_dev); return 0; } @@ -1987,12 +1046,8 @@ static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val) struct sd *sd = (struct sd *) gspca_dev; sd->brightness = val; - if (gspca_dev->streaming) { - if (sd->sensor == SENSOR_OV772X) - setbrightness_77(gspca_dev); - else - setbrightness_96(gspca_dev); - } + if (gspca_dev->streaming) + setbrightness(gspca_dev); return 0; } @@ -2009,12 +1064,8 @@ static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val) struct sd *sd = (struct sd *) gspca_dev; sd->contrast = val; - if (gspca_dev->streaming) { - if (sd->sensor == SENSOR_OV772X) - setcontrast_77(gspca_dev); - else - setcontrast_96(gspca_dev); - } + if (gspca_dev->streaming) + setcontrast(gspca_dev); return 0; } @@ -2026,41 +1077,6 @@ static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val) return 0; } -static int sd_setsatur(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->satur = val; - if (gspca_dev->streaming) - setsatur(gspca_dev); - return 0; -} - -static int sd_getsatur(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->satur; - return 0; -} -static int sd_setfreq(struct gspca_dev *gspca_dev, __s32 val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - sd->lightfreq = val; - if (gspca_dev->streaming) - setfreq(gspca_dev); - return 0; -} - -static int sd_getfreq(struct gspca_dev *gspca_dev, __s32 *val) -{ - struct sd *sd = (struct sd *) gspca_dev; - - *val = sd->lightfreq; - return 0; -} - static int sd_setredblc(struct gspca_dev *gspca_dev, __s32 val) { struct sd *sd = (struct sd *) gspca_dev; @@ -2122,22 +1138,14 @@ static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val) sd->autogain = val; if (gspca_dev->streaming) { - if (sd->sensor == SENSOR_OV772X) { - - /* the auto white balance control works only - * when auto gain is set */ - if (val) - gspca_dev->ctrl_inac &= ~(1 << AWB_77_IDX); - else - gspca_dev->ctrl_inac |= (1 << AWB_77_IDX); - setautogain_77(gspca_dev); - } else { - if (val) - gspca_dev->ctrl_inac |= (1 << EXPO_96_IDX); - else - gspca_dev->ctrl_inac &= ~(1 << EXPO_96_IDX); - setautogain_96(gspca_dev); - } + + /* the auto white balance control works only + * when auto gain is set */ + if (val) + gspca_dev->ctrl_inac &= ~(1 << AWB_IDX); + else + gspca_dev->ctrl_inac |= (1 << AWB_IDX); + setautogain(gspca_dev); } return 0; } @@ -2173,12 +1181,8 @@ static int sd_setsharpness(struct gspca_dev *gspca_dev, __s32 val) struct sd *sd = (struct sd *) gspca_dev; sd->sharpness = val; - if (gspca_dev->streaming) { - if (sd->sensor == SENSOR_OV772X) - setsharpness_77(gspca_dev); - else - setsharpness_96(gspca_dev); - } + if (gspca_dev->streaming) + setsharpness(gspca_dev); return 0; } @@ -2257,7 +1261,7 @@ static int sd_set_streamparm(struct gspca_dev *gspca_dev, /* Set requested framerate */ sd->frame_rate = tpf->denominator / tpf->numerator; - if (gspca_dev->streaming && sd->sensor == SENSOR_OV772X) + if (gspca_dev->streaming) set_frame_rate(gspca_dev); /* Return the actual framerate */ @@ -2267,57 +1271,23 @@ static int sd_set_streamparm(struct gspca_dev *gspca_dev, return 0; } -static int sd_querymenu(struct gspca_dev *gspca_dev, - struct v4l2_querymenu *menu) -{ - switch (menu->id) { - case V4L2_CID_POWER_LINE_FREQUENCY: - switch (menu->index) { - case 0: /* V4L2_CID_POWER_LINE_FREQUENCY_DISABLED */ - strcpy((char *) menu->name, "NoFliker"); - return 0; - case 1: /* V4L2_CID_POWER_LINE_FREQUENCY_50HZ */ - strcpy((char *) menu->name, "50 Hz"); - return 0; - case 2: /* V4L2_CID_POWER_LINE_FREQUENCY_60HZ */ - strcpy((char *) menu->name, "60 Hz"); - return 0; - } - break; - } - return -EINVAL; -} - /* sub-driver description */ -static const struct sd_desc sd_desc_ov772x = { +static const struct sd_desc sd_desc = { .name = MODULE_NAME, - .ctrls = sd_ctrls_ov772x, - .nctrls = ARRAY_SIZE(sd_ctrls_ov772x), + .ctrls = sd_ctrls, + .nctrls = ARRAY_SIZE(sd_ctrls), .config = sd_config, .init = sd_init, - .start = sd_start_ov772x, - .stopN = sd_stopN_ov772x, + .start = sd_start, + .stopN = sd_stopN, .pkt_scan = sd_pkt_scan, .get_streamparm = sd_get_streamparm, .set_streamparm = sd_set_streamparm, }; -static const struct sd_desc sd_desc_ov965x = { - .name = MODULE_NAME, - .ctrls = sd_ctrls_ov965x, - .nctrls = ARRAY_SIZE(sd_ctrls_ov965x), - .config = sd_config, - .init = sd_init, - .start = sd_start_ov965x, - .stopN = sd_stopN_ov965x, - .pkt_scan = sd_pkt_scan, - .querymenu = sd_querymenu, -}; - /* -- module initialisation -- */ static const __devinitdata struct usb_device_id device_table[] = { - {USB_DEVICE(0x06f8, 0x3003), .driver_info = SENSOR_OV965X}, - {USB_DEVICE(0x1415, 0x2000), .driver_info = SENSOR_OV772X}, + {USB_DEVICE(0x1415, 0x2000)}, {} }; @@ -2326,11 +1296,7 @@ MODULE_DEVICE_TABLE(usb, device_table); /* -- device connect -- */ static int sd_probe(struct usb_interface *intf, const struct usb_device_id *id) { - return gspca_dev_probe(intf, id, - id->driver_info == SENSOR_OV772X - ? &sd_desc_ov772x - : &sd_desc_ov965x, - sizeof(struct sd), + return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd), THIS_MODULE); } diff --git a/drivers/media/video/gspca/ov534_9.c b/drivers/media/video/gspca/ov534_9.c new file mode 100644 index 00000000000..464b7f50827 --- /dev/null +++ b/drivers/media/video/gspca/ov534_9.c @@ -0,0 +1,1477 @@ +/* + * ov534-ov965x gspca driver + * + * Copyright (C) 2009-2010 Jean-Francois Moine http://moinejf.free.fr + * Copyright (C) 2008 Antonio Ospite + * Copyright (C) 2008 Jim Paris + * + * Based on a prototype written by Mark Ferrell + * USB protocol reverse engineered by Jim Paris + * https://jim.sh/svn/jim/devl/playstation/ps3/eye/test/ + * + * 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 + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#define MODULE_NAME "ov534_9" + +#include "gspca.h" + +#define OV534_REG_ADDRESS 0xf1 /* sensor address */ +#define OV534_REG_SUBADDR 0xf2 +#define OV534_REG_WRITE 0xf3 +#define OV534_REG_READ 0xf4 +#define OV534_REG_OPERATION 0xf5 +#define OV534_REG_STATUS 0xf6 + +#define OV534_OP_WRITE_3 0x37 +#define OV534_OP_WRITE_2 0x33 +#define OV534_OP_READ_2 0xf9 + +#define CTRL_TIMEOUT 500 + +MODULE_AUTHOR("Jean-Francois Moine "); +MODULE_DESCRIPTION("GSPCA/OV534_9 USB Camera Driver"); +MODULE_LICENSE("GPL"); + +/* specific webcam descriptor */ +struct sd { + struct gspca_dev gspca_dev; /* !! must be the first item */ + __u32 last_pts; + u8 last_fid; + + u8 brightness; + u8 contrast; + u8 autogain; + u8 exposure; + s8 sharpness; + u8 satur; + u8 freq; +}; + +/* V4L2 controls supported by the driver */ +static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val); +static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val); +static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val); +static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val); +static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val); +static int sd_getautogain(struct gspca_dev *gspca_dev, __s32 *val); +static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val); +static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val); +static int sd_setsharpness(struct gspca_dev *gspca_dev, __s32 val); +static int sd_getsharpness(struct gspca_dev *gspca_dev, __s32 *val); +static int sd_setsatur(struct gspca_dev *gspca_dev, __s32 val); +static int sd_getsatur(struct gspca_dev *gspca_dev, __s32 *val); +static int sd_setfreq(struct gspca_dev *gspca_dev, __s32 val); +static int sd_getfreq(struct gspca_dev *gspca_dev, __s32 *val); + +static const struct ctrl sd_ctrls[] = { + { /* 0 */ + { + .id = V4L2_CID_BRIGHTNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Brightness", + .minimum = 0, + .maximum = 15, + .step = 1, +#define BRIGHTNESS_DEF 7 + .default_value = BRIGHTNESS_DEF, + }, + .set = sd_setbrightness, + .get = sd_getbrightness, + }, + { /* 1 */ + { + .id = V4L2_CID_CONTRAST, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Contrast", + .minimum = 0, + .maximum = 15, + .step = 1, +#define CONTRAST_DEF 3 + .default_value = CONTRAST_DEF, + }, + .set = sd_setcontrast, + .get = sd_getcontrast, + }, + { /* 2 */ + { + .id = V4L2_CID_AUTOGAIN, + .type = V4L2_CTRL_TYPE_BOOLEAN, + .name = "Autogain", + .minimum = 0, + .maximum = 1, + .step = 1, +#define AUTOGAIN_DEF 1 + .default_value = AUTOGAIN_DEF, + }, + .set = sd_setautogain, + .get = sd_getautogain, + }, +#define EXPO_IDX 3 + { /* 3 */ + { + .id = V4L2_CID_EXPOSURE, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Exposure", + .minimum = 0, + .maximum = 3, + .step = 1, +#define EXPO_DEF 0 + .default_value = EXPO_DEF, + }, + .set = sd_setexposure, + .get = sd_getexposure, + }, + { /* 4 */ + { + .id = V4L2_CID_SHARPNESS, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Sharpness", + .minimum = -1, /* -1 = auto */ + .maximum = 4, + .step = 1, +#define SHARPNESS_DEF -1 + .default_value = SHARPNESS_DEF, + }, + .set = sd_setsharpness, + .get = sd_getsharpness, + }, + { /* 5 */ + { + .id = V4L2_CID_SATURATION, + .type = V4L2_CTRL_TYPE_INTEGER, + .name = "Saturation", + .minimum = 0, + .maximum = 4, + .step = 1, +#define SATUR_DEF 2 + .default_value = SATUR_DEF, + }, + .set = sd_setsatur, + .get = sd_getsatur, + }, + { + { + .id = V4L2_CID_POWER_LINE_FREQUENCY, + .type = V4L2_CTRL_TYPE_MENU, + .name = "Light frequency filter", + .minimum = 0, + .maximum = 2, /* 0: 0, 1: 50Hz, 2:60Hz */ + .step = 1, +#define FREQ_DEF 0 + .default_value = FREQ_DEF, + }, + .set = sd_setfreq, + .get = sd_getfreq, + }, +}; + +static const struct v4l2_pix_format ov965x_mode[] = { +#define QVGA_MODE 0 + {320, 240, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, + .bytesperline = 320, + .sizeimage = 320 * 240 * 3 / 8 + 590, + .colorspace = V4L2_COLORSPACE_JPEG}, +#define VGA_MODE 1 + {640, 480, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, + .bytesperline = 640, + .sizeimage = 640 * 480 * 3 / 8 + 590, + .colorspace = V4L2_COLORSPACE_JPEG}, +#define SVGA_MODE 2 + {800, 600, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, + .bytesperline = 800, + .sizeimage = 800 * 600 * 3 / 8 + 590, + .colorspace = V4L2_COLORSPACE_JPEG}, +#define XGA_MODE 3 + {1024, 768, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, + .bytesperline = 1024, + .sizeimage = 1024 * 768 * 3 / 8 + 590, + .colorspace = V4L2_COLORSPACE_JPEG}, +#define SXGA_MODE 4 + {1280, 1024, V4L2_PIX_FMT_JPEG, V4L2_FIELD_NONE, + .bytesperline = 1280, + .sizeimage = 1280 * 1024 * 3 / 8 + 590, + .colorspace = V4L2_COLORSPACE_JPEG}, +}; + +static const u8 bridge_init[][2] = { + {0x88, 0xf8}, + {0x89, 0xff}, + {0x76, 0x03}, + {0x92, 0x03}, + {0x95, 0x10}, + {0xe2, 0x00}, + {0xe7, 0x3e}, + {0x8d, 0x1c}, + {0x8e, 0x00}, + {0x8f, 0x00}, + {0x1f, 0x00}, + {0xc3, 0xf9}, + {0x89, 0xff}, + {0x88, 0xf8}, + {0x76, 0x03}, + {0x92, 0x01}, + {0x93, 0x18}, + {0x1c, 0x0a}, + {0x1d, 0x48}, + {0xc0, 0x50}, + {0xc1, 0x3c}, + {0x34, 0x05}, + {0xc2, 0x0c}, + {0xc3, 0xf9}, + {0x34, 0x05}, + {0xe7, 0x2e}, + {0x31, 0xf9}, + {0x35, 0x02}, + {0xd9, 0x10}, + {0x25, 0x42}, + {0x94, 0x11}, +}; + +static const u8 sensor_init[][2] = { + {0x12, 0x80}, /* com7 - SSCB reset */ + {0x00, 0x00}, /* gain */ + {0x01, 0x80}, /* blue */ + {0x02, 0x80}, /* red */ + {0x03, 0x1b}, /* vref */ + {0x04, 0x03}, /* com1 - exposure low bits */ + {0x0b, 0x57}, /* ver */ + {0x0e, 0x61}, /* com5 */ + {0x0f, 0x42}, /* com6 */ + {0x11, 0x00}, /* clkrc */ + {0x12, 0x02}, /* com7 - 15fps VGA YUYV */ + {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ + {0x14, 0x28}, /* com9 */ + {0x16, 0x24}, /* reg16 */ + {0x17, 0x1d}, /* hstart*/ + {0x18, 0xbd}, /* hstop */ + {0x19, 0x01}, /* vstrt */ + {0x1a, 0x81}, /* vstop*/ + {0x1e, 0x04}, /* mvfp */ + {0x24, 0x3c}, /* aew */ + {0x25, 0x36}, /* aeb */ + {0x26, 0x71}, /* vpt */ + {0x27, 0x08}, /* bbias */ + {0x28, 0x08}, /* gbbias */ + {0x29, 0x15}, /* gr com */ + {0x2a, 0x00}, /* exhch */ + {0x2b, 0x00}, /* exhcl */ + {0x2c, 0x08}, /* rbias */ + {0x32, 0xff}, /* href */ + {0x33, 0x00}, /* chlf */ + {0x34, 0x3f}, /* aref1 */ + {0x35, 0x00}, /* aref2 */ + {0x36, 0xf8}, /* aref3 */ + {0x38, 0x72}, /* adc2 */ + {0x39, 0x57}, /* aref4 */ + {0x3a, 0x80}, /* tslb - yuyv */ + {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ + {0x3d, 0x99}, /* com13 */ + {0x3f, 0xc1}, /* edge */ + {0x40, 0xc0}, /* com15 */ + {0x41, 0x40}, /* com16 */ + {0x42, 0xc0}, /* com17 */ + {0x43, 0x0a}, /* rsvd */ + {0x44, 0xf0}, + {0x45, 0x46}, + {0x46, 0x62}, + {0x47, 0x2a}, + {0x48, 0x3c}, + {0x4a, 0xfc}, + {0x4b, 0xfc}, + {0x4c, 0x7f}, + {0x4d, 0x7f}, + {0x4e, 0x7f}, + {0x4f, 0x98}, /* matrix */ + {0x50, 0x98}, + {0x51, 0x00}, + {0x52, 0x28}, + {0x53, 0x70}, + {0x54, 0x98}, + {0x58, 0x1a}, /* matrix coef sign */ + {0x59, 0x85}, /* AWB control */ + {0x5a, 0xa9}, + {0x5b, 0x64}, + {0x5c, 0x84}, + {0x5d, 0x53}, + {0x5e, 0x0e}, + {0x5f, 0xf0}, /* AWB blue limit */ + {0x60, 0xf0}, /* AWB red limit */ + {0x61, 0xf0}, /* AWB green limit */ + {0x62, 0x00}, /* lcc1 */ + {0x63, 0x00}, /* lcc2 */ + {0x64, 0x02}, /* lcc3 */ + {0x65, 0x16}, /* lcc4 */ + {0x66, 0x01}, /* lcc5 */ + {0x69, 0x02}, /* hv */ + {0x6b, 0x5a}, /* dbvl */ + {0x6c, 0x04}, + {0x6d, 0x55}, + {0x6e, 0x00}, + {0x6f, 0x9d}, + {0x70, 0x21}, /* dnsth */ + {0x71, 0x78}, + {0x72, 0x00}, /* poidx */ + {0x73, 0x01}, /* pckdv */ + {0x74, 0x3a}, /* xindx */ + {0x75, 0x35}, /* yindx */ + {0x76, 0x01}, + {0x77, 0x02}, + {0x7a, 0x12}, /* gamma curve */ + {0x7b, 0x08}, + {0x7c, 0x16}, + {0x7d, 0x30}, + {0x7e, 0x5e}, + {0x7f, 0x72}, + {0x80, 0x82}, + {0x81, 0x8e}, + {0x82, 0x9a}, + {0x83, 0xa4}, + {0x84, 0xac}, + {0x85, 0xb8}, + {0x86, 0xc3}, + {0x87, 0xd6}, + {0x88, 0xe6}, + {0x89, 0xf2}, + {0x8a, 0x03}, + {0x8c, 0x89}, /* com19 */ + {0x14, 0x28}, /* com9 */ + {0x90, 0x7d}, + {0x91, 0x7b}, + {0x9d, 0x03}, /* lcc6 */ + {0x9e, 0x04}, /* lcc7 */ + {0x9f, 0x7a}, + {0xa0, 0x79}, + {0xa1, 0x40}, /* aechm */ + {0xa4, 0x50}, /* com21 */ + {0xa5, 0x68}, /* com26 */ + {0xa6, 0x4a}, /* AWB green */ + {0xa8, 0xc1}, /* refa8 */ + {0xa9, 0xef}, /* refa9 */ + {0xaa, 0x92}, + {0xab, 0x04}, + {0xac, 0x80}, /* black level control */ + {0xad, 0x80}, + {0xae, 0x80}, + {0xaf, 0x80}, + {0xb2, 0xf2}, + {0xb3, 0x20}, + {0xb4, 0x20}, /* ctrlb4 */ + {0xb5, 0x00}, + {0xb6, 0xaf}, + {0xbb, 0xae}, + {0xbc, 0x7f}, /* ADC channel offsets */ + {0xdb, 0x7f}, + {0xbe, 0x7f}, + {0xbf, 0x7f}, + {0xc0, 0xe2}, + {0xc1, 0xc0}, + {0xc2, 0x01}, + {0xc3, 0x4e}, + {0xc6, 0x85}, + {0xc7, 0x80}, /* com24 */ + {0xc9, 0xe0}, + {0xca, 0xe8}, + {0xcb, 0xf0}, + {0xcc, 0xd8}, + {0xcd, 0xf1}, + {0x4f, 0x98}, /* matrix */ + {0x50, 0x98}, + {0x51, 0x00}, + {0x52, 0x28}, + {0x53, 0x70}, + {0x54, 0x98}, + {0x58, 0x1a}, + {0xff, 0x41}, /* read 41, write ff 00 */ + {0x41, 0x40}, /* com16 */ + + {0xc5, 0x03}, /* 60 Hz banding filter */ + {0x6a, 0x02}, /* 50 Hz banding filter */ + + {0x12, 0x62}, /* com7 - 30fps VGA YUV */ + {0x36, 0xfa}, /* aref3 */ + {0x69, 0x0a}, /* hv */ + {0x8c, 0x89}, /* com22 */ + {0x14, 0x28}, /* com9 */ + {0x3e, 0x0c}, + {0x41, 0x40}, /* com16 */ + {0x72, 0x00}, + {0x73, 0x00}, + {0x74, 0x3a}, + {0x75, 0x35}, + {0x76, 0x01}, + {0xc7, 0x80}, + {0x03, 0x12}, /* vref */ + {0x17, 0x16}, /* hstart */ + {0x18, 0x02}, /* hstop */ + {0x19, 0x01}, /* vstrt */ + {0x1a, 0x3d}, /* vstop */ + {0x32, 0xff}, /* href */ + {0xc0, 0xaa}, +}; + +static const u8 bridge_init_2[][2] = { + {0x94, 0xaa}, + {0xf1, 0x60}, + {0xe5, 0x04}, + {0xc0, 0x50}, + {0xc1, 0x3c}, + {0x8c, 0x00}, + {0x8d, 0x1c}, + {0x34, 0x05}, + + {0xc2, 0x0c}, + {0xc3, 0xf9}, + {0xda, 0x01}, + {0x50, 0x00}, + {0x51, 0xa0}, + {0x52, 0x3c}, + {0x53, 0x00}, + {0x54, 0x00}, + {0x55, 0x00}, + {0x57, 0x00}, + {0x5c, 0x00}, + {0x5a, 0xa0}, + {0x5b, 0x78}, + {0x35, 0x02}, + {0xd9, 0x10}, + {0x94, 0x11}, +}; + +static const u8 sensor_init_2[][2] = { + {0x3b, 0xc4}, + {0x1e, 0x04}, /* mvfp */ + {0x13, 0xe0}, /* com8 */ + {0x00, 0x00}, /* gain */ + {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ + {0x11, 0x03}, /* clkrc */ + {0x6b, 0x5a}, /* dblv */ + {0x6a, 0x05}, + {0xc5, 0x07}, + {0xa2, 0x4b}, + {0xa3, 0x3e}, + {0x2d, 0x00}, + {0xff, 0x42}, /* read 42, write ff 00 */ + {0x42, 0xc0}, /* com17 */ + {0x2d, 0x00}, + {0xff, 0x42}, /* read 42, write ff 00 */ + {0x42, 0xc1}, /* com17 */ +/* sharpness */ + {0x3f, 0x01}, + {0xff, 0x42}, /* read 42, write ff 00 */ + {0x42, 0xc1}, /* com17 */ +/* saturation */ + {0x4f, 0x98}, /* matrix */ + {0x50, 0x98}, + {0x51, 0x00}, + {0x52, 0x28}, + {0x53, 0x70}, + {0x54, 0x98}, + {0x58, 0x1a}, + {0xff, 0x41}, /* read 41, write ff 00 */ + {0x41, 0x40}, /* com16 */ +/* contrast */ + {0x56, 0x40}, +/* brightness */ + {0x55, 0x8f}, +/* expo */ + {0x10, 0x25}, /* aech - exposure high bits */ + {0xff, 0x13}, /* read 13, write ff 00 */ + {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ +}; + +static const u8 sensor_start_1_vga[][2] = { /* same for qvga */ + {0x12, 0x62}, /* com7 - 30fps VGA YUV */ + {0x36, 0xfa}, /* aref3 */ + {0x69, 0x0a}, /* hv */ + {0x8c, 0x89}, /* com22 */ + {0x14, 0x28}, /* com9 */ + {0x3e, 0x0c}, /* com14 */ + {0x41, 0x40}, /* com16 */ + {0x72, 0x00}, + {0x73, 0x00}, + {0x74, 0x3a}, + {0x75, 0x35}, + {0x76, 0x01}, + {0xc7, 0x80}, /* com24 */ + {0x03, 0x12}, /* vref */ + {0x17, 0x16}, /* hstart */ + {0x18, 0x02}, /* hstop */ + {0x19, 0x01}, /* vstrt */ + {0x1a, 0x3d}, /* vstop */ + {0x32, 0xff}, /* href */ + {0xc0, 0xaa}, +}; + +static const u8 sensor_start_1_svga[][2] = { + {0x12, 0x02}, /* com7 - YUYV - VGA 15 full resolution */ + {0x36, 0xf8}, /* aref3 */ + {0x69, 0x02}, /* hv */ + {0x8c, 0x0d}, /* com22 */ + {0x3e, 0x0c}, /* com14 */ + {0x41, 0x40}, /* com16 */ + {0x72, 0x00}, + {0x73, 0x01}, + {0x74, 0x3a}, + {0x75, 0x35}, + {0x76, 0x01}, + {0xc7, 0x80}, /* com24 */ + {0x03, 0x1b}, /* vref */ + {0x17, 0x1d}, /* hstart */ + {0x18, 0xbd}, /* hstop */ + {0x19, 0x01}, /* vstrt */ + {0x1a, 0x81}, /* vstop */ + {0x32, 0xff}, /* href */ + {0xc0, 0xe2}, +}; + +static const u8 sensor_start_1_xga[][2] = { + {0x12, 0x02}, /* com7 */ + {0x36, 0xf8}, /* aref3 */ + {0x69, 0x02}, /* hv */ + {0x8c, 0x89}, /* com22 */ + {0x14, 0x28}, /* com9 */ + {0x3e, 0x0c}, /* com14 */ + {0x41, 0x40}, /* com16 */ + {0x72, 0x00}, + {0x73, 0x01}, + {0x74, 0x3a}, + {0x75, 0x35}, + {0x76, 0x01}, + {0xc7, 0x80}, /* com24 */ + {0x03, 0x1b}, /* vref */ + {0x17, 0x1d}, /* hstart */ + {0x18, 0xbd}, /* hstop */ + {0x19, 0x01}, /* vstrt */ + {0x1a, 0x81}, /* vstop */ + {0x32, 0xff}, /* href */ + {0xc0, 0xe2}, +}; + +static const u8 sensor_start_1_sxga[][2] = { + {0x12, 0x02}, /* com7 */ + {0x36, 0xf8}, /* aref3 */ + {0x69, 0x02}, /* hv */ + {0x8c, 0x89}, /* com22 */ + {0x14, 0x28}, /* com9 */ + {0x3e, 0x0c}, /* com14 */ + {0x41, 0x40}, /* com16 */ + {0x72, 0x00}, + {0x73, 0x01}, + {0x74, 0x3a}, + {0x75, 0x35}, + {0x76, 0x01}, + {0xc7, 0x80}, /* com24 */ + {0x03, 0x1b}, /* vref */ + {0x17, 0x1d}, /* hstart */ + {0x18, 0x02}, /* hstop */ + {0x19, 0x01}, /* vstrt */ + {0x1a, 0x81}, /* vstop */ + {0x32, 0xff}, /* href */ + {0xc0, 0xe2}, +}; + +static const u8 bridge_start_qvga[][2] = { + {0x94, 0xaa}, + {0xf1, 0x60}, + {0xe5, 0x04}, + {0xc0, 0x50}, + {0xc1, 0x3c}, + {0x8c, 0x00}, + {0x8d, 0x1c}, + {0x34, 0x05}, + + {0xc2, 0x4c}, + {0xc3, 0xf9}, + {0xda, 0x00}, + {0x50, 0x00}, + {0x51, 0xa0}, + {0x52, 0x78}, + {0x53, 0x00}, + {0x54, 0x00}, + {0x55, 0x00}, + {0x57, 0x00}, + {0x5c, 0x00}, + {0x5a, 0x50}, + {0x5b, 0x3c}, + {0x35, 0x02}, + {0xd9, 0x10}, + {0x94, 0x11}, +}; + +static const u8 bridge_start_vga[][2] = { + {0x94, 0xaa}, + {0xf1, 0x60}, + {0xe5, 0x04}, + {0xc0, 0x50}, + {0xc1, 0x3c}, + {0x8c, 0x00}, + {0x8d, 0x1c}, + {0x34, 0x05}, + {0xc2, 0x0c}, + {0xc3, 0xf9}, + {0xda, 0x01}, + {0x50, 0x00}, + {0x51, 0xa0}, + {0x52, 0x3c}, + {0x53, 0x00}, + {0x54, 0x00}, + {0x55, 0x00}, + {0x57, 0x00}, + {0x5c, 0x00}, + {0x5a, 0xa0}, + {0x5b, 0x78}, + {0x35, 0x02}, + {0xd9, 0x10}, + {0x94, 0x11}, +}; + +static const u8 bridge_start_svga[][2] = { + {0x94, 0xaa}, + {0xf1, 0x60}, + {0xe5, 0x04}, + {0xc0, 0xa0}, + {0xc1, 0x80}, + {0x8c, 0x00}, + {0x8d, 0x1c}, + {0x34, 0x05}, + {0xc2, 0x4c}, + {0xc3, 0xf9}, + {0x50, 0x00}, + {0x51, 0x40}, + {0x52, 0x00}, + {0x53, 0x00}, + {0x54, 0x00}, + {0x55, 0x88}, + {0x57, 0x00}, + {0x5c, 0x00}, + {0x5a, 0xc8}, + {0x5b, 0x96}, + {0x35, 0x02}, + {0xd9, 0x10}, + {0xda, 0x00}, + {0x94, 0x11}, +}; + +static const u8 bridge_start_xga[][2] = { + {0x94, 0xaa}, + {0xf1, 0x60}, + {0xe5, 0x04}, + {0xc0, 0xa0}, + {0xc1, 0x80}, + {0x8c, 0x00}, + {0x8d, 0x1c}, + {0x34, 0x05}, + {0xc2, 0x4c}, + {0xc3, 0xf9}, + {0x50, 0x00}, + {0x51, 0x40}, + {0x52, 0x00}, + {0x53, 0x00}, + {0x54, 0x00}, + {0x55, 0x88}, + {0x57, 0x00}, + {0x5c, 0x01}, + {0x5a, 0x00}, + {0x5b, 0xc0}, + {0x35, 0x02}, + {0xd9, 0x10}, + {0xda, 0x01}, + {0x94, 0x11}, +}; + +static const u8 bridge_start_sxga[][2] = { + {0x94, 0xaa}, + {0xf1, 0x60}, + {0xe5, 0x04}, + {0xc0, 0xa0}, + {0xc1, 0x80}, + {0x8c, 0x00}, + {0x8d, 0x1c}, + {0x34, 0x05}, + {0xc2, 0x0c}, + {0xc3, 0xf9}, + {0xda, 0x00}, + {0x35, 0x02}, + {0xd9, 0x10}, + {0x94, 0x11}, +}; + +static const u8 sensor_start_2_qvga[][2] = { + {0x3b, 0xe4}, /* com11 - night mode 1/4 frame rate */ + {0x1e, 0x04}, /* mvfp */ + {0x13, 0xe0}, /* com8 */ + {0x00, 0x00}, + {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ + {0x11, 0x01}, /* clkrc */ + {0x6b, 0x5a}, /* dblv */ + {0x6a, 0x02}, /* 50 Hz banding filter */ + {0xc5, 0x03}, /* 60 Hz banding filter */ + {0xa2, 0x96}, /* bd50 */ + {0xa3, 0x7d}, /* bd60 */ + + {0xff, 0x13}, /* read 13, write ff 00 */ + {0x13, 0xe7}, + {0x3a, 0x80}, /* tslb - yuyv */ +}; + +static const u8 sensor_start_2_vga[][2] = { + {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ + {0x1e, 0x04}, /* mvfp */ + {0x13, 0xe0}, /* com8 */ + {0x00, 0x00}, + {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ + {0x11, 0x03}, /* clkrc */ + {0x6b, 0x5a}, /* dblv */ + {0x6a, 0x05}, /* 50 Hz banding filter */ + {0xc5, 0x07}, /* 60 Hz banding filter */ + {0xa2, 0x4b}, /* bd50 */ + {0xa3, 0x3e}, /* bd60 */ + + {0x2d, 0x00}, /* advfl */ +}; + +static const u8 sensor_start_2_svga[][2] = { /* same for xga */ + {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ + {0x1e, 0x04}, /* mvfp */ + {0x13, 0xe0}, /* com8 */ + {0x00, 0x00}, + {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ + {0x11, 0x01}, /* clkrc */ + {0x6b, 0x5a}, /* dblv */ + {0x6a, 0x0c}, /* 50 Hz banding filter */ + {0xc5, 0x0f}, /* 60 Hz banding filter */ + {0xa2, 0x4e}, /* bd50 */ + {0xa3, 0x41}, /* bd60 */ +}; + +static const u8 sensor_start_2_sxga[][2] = { + {0x13, 0xe0}, /* com8 */ + {0x00, 0x00}, + {0x13, 0xe7}, /* com8 - everything (AGC, AWB and AEC) */ + {0x3b, 0xc4}, /* com11 - night mode 1/4 frame rate */ + {0x1e, 0x04}, /* mvfp */ + {0x11, 0x01}, /* clkrc */ + {0x6b, 0x5a}, /* dblv */ + {0x6a, 0x0c}, /* 50 Hz banding filter */ + {0xc5, 0x0f}, /* 60 Hz banding filter */ + {0xa2, 0x4e}, /* bd50 */ + {0xa3, 0x41}, /* bd60 */ +}; + +static void reg_w_i(struct gspca_dev *gspca_dev, u16 reg, u8 val) +{ + struct usb_device *udev = gspca_dev->dev; + int ret; + + if (gspca_dev->usb_err < 0) + return; + gspca_dev->usb_buf[0] = val; + ret = usb_control_msg(udev, + usb_sndctrlpipe(udev, 0), + 0x01, + USB_DIR_OUT | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT); + if (ret < 0) { + PDEBUG(D_ERR, "reg_w failed %d", ret); + gspca_dev->usb_err = ret; + } +} + +static void reg_w(struct gspca_dev *gspca_dev, u16 reg, u8 val) +{ + PDEBUG(D_USBO, "reg_w [%04x] = %02x", reg, val); + reg_w_i(gspca_dev, reg, val); +} + +static u8 reg_r(struct gspca_dev *gspca_dev, u16 reg) +{ + struct usb_device *udev = gspca_dev->dev; + int ret; + + if (gspca_dev->usb_err < 0) + return 0; + ret = usb_control_msg(udev, + usb_rcvctrlpipe(udev, 0), + 0x01, + USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_DEVICE, + 0x00, reg, gspca_dev->usb_buf, 1, CTRL_TIMEOUT); + PDEBUG(D_USBI, "reg_r [%04x] -> %02x", reg, gspca_dev->usb_buf[0]); + if (ret < 0) { + PDEBUG(D_ERR, "reg_r err %d", ret); + gspca_dev->usb_err = ret; + } + return gspca_dev->usb_buf[0]; +} + +static int sccb_check_status(struct gspca_dev *gspca_dev) +{ + u8 data; + int i; + + for (i = 0; i < 5; i++) { + data = reg_r(gspca_dev, OV534_REG_STATUS); + + switch (data) { + case 0x00: + return 1; + case 0x04: + return 0; + case 0x03: + break; + default: + PDEBUG(D_USBI|D_USBO, + "sccb status 0x%02x, attempt %d/5", + data, i + 1); + } + } + return 0; +} + +static void sccb_write(struct gspca_dev *gspca_dev, u8 reg, u8 val) +{ + PDEBUG(D_USBO, "sccb_write [%02x] = %02x", reg, val); + reg_w_i(gspca_dev, OV534_REG_SUBADDR, reg); + reg_w_i(gspca_dev, OV534_REG_WRITE, val); + reg_w_i(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_3); + + if (!sccb_check_status(gspca_dev)) + PDEBUG(D_ERR, "sccb_write failed"); +} + +static u8 sccb_read(struct gspca_dev *gspca_dev, u16 reg) +{ + reg_w(gspca_dev, OV534_REG_SUBADDR, reg); + reg_w(gspca_dev, OV534_REG_OPERATION, OV534_OP_WRITE_2); + if (!sccb_check_status(gspca_dev)) + PDEBUG(D_ERR, "sccb_read failed 1"); + + reg_w(gspca_dev, OV534_REG_OPERATION, OV534_OP_READ_2); + if (!sccb_check_status(gspca_dev)) + PDEBUG(D_ERR, "sccb_read failed 2"); + + return reg_r(gspca_dev, OV534_REG_READ); +} + +/* output a bridge sequence (reg - val) */ +static void reg_w_array(struct gspca_dev *gspca_dev, + const u8 (*data)[2], int len) +{ + while (--len >= 0) { + reg_w(gspca_dev, (*data)[0], (*data)[1]); + data++; + } +} + +/* output a sensor sequence (reg - val) */ +static void sccb_w_array(struct gspca_dev *gspca_dev, + const u8 (*data)[2], int len) +{ + while (--len >= 0) { + if ((*data)[0] != 0xff) { + sccb_write(gspca_dev, (*data)[0], (*data)[1]); + } else { + sccb_read(gspca_dev, (*data)[1]); + sccb_write(gspca_dev, 0xff, 0x00); + } + data++; + } +} + +/* Two bits control LED: 0x21 bit 7 and 0x23 bit 7. + * (direction and output)? */ +static void set_led(struct gspca_dev *gspca_dev, int status) +{ + u8 data; + + PDEBUG(D_CONF, "led status: %d", status); + + data = reg_r(gspca_dev, 0x21); + data |= 0x80; + reg_w(gspca_dev, 0x21, data); + + data = reg_r(gspca_dev, 0x23); + if (status) + data |= 0x80; + else + data &= ~0x80; + + reg_w(gspca_dev, 0x23, data); + + if (!status) { + data = reg_r(gspca_dev, 0x21); + data &= ~0x80; + reg_w(gspca_dev, 0x21, data); + } +} + +static void setbrightness(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + u8 val; + + val = sd->brightness; + if (val < 8) + val = 15 - val; /* f .. 8 */ + else + val = val - 8; /* 0 .. 7 */ + sccb_write(gspca_dev, 0x55, /* brtn - brightness adjustment */ + 0x0f | (val << 4)); +} + +static void setcontrast(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sccb_write(gspca_dev, 0x56, /* cnst1 - contrast 1 ctrl coeff */ + sd->contrast << 4); +} + +static void setautogain(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + u8 val; + +/*fixme: should adjust agc/awb/aec by different controls */ + val = sd->autogain; + val = sccb_read(gspca_dev, 0x13); /* com8 */ + sccb_write(gspca_dev, 0xff, 0x00); + if (sd->autogain) + val |= 0x05; /* agc & aec */ + else + val &= 0xfa; + sccb_write(gspca_dev, 0x13, val); +} + +static void setexposure(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + u8 val; + static const u8 expo[4] = {0x00, 0x25, 0x38, 0x5e}; + + sccb_write(gspca_dev, 0x10, /* aec[9:2] */ + expo[sd->exposure]); + + val = sccb_read(gspca_dev, 0x13); /* com8 */ + sccb_write(gspca_dev, 0xff, 0x00); + sccb_write(gspca_dev, 0x13, val); + + val = sccb_read(gspca_dev, 0xa1); /* aech */ + sccb_write(gspca_dev, 0xff, 0x00); + sccb_write(gspca_dev, 0xa1, val & 0xe0); /* aec[15:10] = 0 */ +} + +static void setsharpness(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + s8 val; + + val = sd->sharpness; + if (val < 0) { /* auto */ + val = sccb_read(gspca_dev, 0x42); /* com17 */ + sccb_write(gspca_dev, 0xff, 0x00); + sccb_write(gspca_dev, 0x42, val | 0x40); + /* Edge enhancement strength auto adjust */ + return; + } + if (val != 0) + val = 1 << (val - 1); + sccb_write(gspca_dev, 0x3f, /* edge - edge enhance. factor */ + val); + val = sccb_read(gspca_dev, 0x42); /* com17 */ + sccb_write(gspca_dev, 0xff, 0x00); + sccb_write(gspca_dev, 0x42, val & 0xbf); +} + +static void setsatur(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + u8 val1, val2, val3; + static const u8 matrix[5][2] = { + {0x14, 0x38}, + {0x1e, 0x54}, + {0x28, 0x70}, + {0x32, 0x8c}, + {0x48, 0x90} + }; + + val1 = matrix[sd->satur][0]; + val2 = matrix[sd->satur][1]; + val3 = val1 + val2; + sccb_write(gspca_dev, 0x4f, val3); /* matrix coeff */ + sccb_write(gspca_dev, 0x50, val3); + sccb_write(gspca_dev, 0x51, 0x00); + sccb_write(gspca_dev, 0x52, val1); + sccb_write(gspca_dev, 0x53, val2); + sccb_write(gspca_dev, 0x54, val3); + sccb_write(gspca_dev, 0x58, 0x1a); /* mtxs - coeff signs */ + + val1 = sccb_read(gspca_dev, 0x41); /* com16 */ + sccb_write(gspca_dev, 0xff, 0x00); + sccb_write(gspca_dev, 0x41, val1); +} + +static void setfreq(struct gspca_dev *gspca_dev) +{ + struct sd *sd = (struct sd *) gspca_dev; + u8 val; + + val = sccb_read(gspca_dev, 0x13); /* com8 */ + sccb_write(gspca_dev, 0xff, 0x00); + if (sd->freq == 0) { + sccb_write(gspca_dev, 0x13, val & 0xdf); + return; + } + sccb_write(gspca_dev, 0x13, val | 0x20); + + val = sccb_read(gspca_dev, 0x42); /* com17 */ + sccb_write(gspca_dev, 0xff, 0x00); + if (sd->freq == 1) + val |= 0x01; + else + val &= 0xfe; + sccb_write(gspca_dev, 0x42, val); +} + +/* this function is called at probe time */ +static int sd_config(struct gspca_dev *gspca_dev, + const struct usb_device_id *id) +{ + struct sd *sd = (struct sd *) gspca_dev; + struct cam *cam; + + cam = &gspca_dev->cam; + + cam->cam_mode = ov965x_mode; + cam->nmodes = ARRAY_SIZE(ov965x_mode); + + sd->brightness = BRIGHTNESS_DEF; + sd->contrast = CONTRAST_DEF; +#if AUTOGAIN_DEF != 0 + sd->autogain = AUTOGAIN_DEF; + gspca_dev->ctrl_inac |= (1 << EXPO_IDX); +#endif +#if EXPO_DEF != 0 + sd->exposure = EXPO_DEF; +#endif +#if SHARPNESS_DEF != 0 + sd->sharpness = SHARPNESS_DEF; +#endif + sd->satur = SATUR_DEF; + sd->freq = FREQ_DEF; + + return 0; +} + +/* this function is called at probe and resume time */ +static int sd_init(struct gspca_dev *gspca_dev) +{ + u16 sensor_id; + + /* reset bridge */ + reg_w(gspca_dev, 0xe7, 0x3a); + reg_w(gspca_dev, 0xe0, 0x08); + msleep(100); + + /* initialize the sensor address */ + reg_w(gspca_dev, OV534_REG_ADDRESS, 0x60); + + /* reset sensor */ + sccb_write(gspca_dev, 0x12, 0x80); + msleep(10); + + /* probe the sensor */ + sccb_read(gspca_dev, 0x0a); + sensor_id = sccb_read(gspca_dev, 0x0a) << 8; + sccb_read(gspca_dev, 0x0b); + sensor_id |= sccb_read(gspca_dev, 0x0b); + PDEBUG(D_PROBE, "Sensor ID: %04x", sensor_id); + + /* initialize */ + reg_w_array(gspca_dev, bridge_init, + ARRAY_SIZE(bridge_init)); + sccb_w_array(gspca_dev, sensor_init, + ARRAY_SIZE(sensor_init)); + reg_w_array(gspca_dev, bridge_init_2, + ARRAY_SIZE(bridge_init_2)); + sccb_w_array(gspca_dev, sensor_init_2, + ARRAY_SIZE(sensor_init_2)); + reg_w(gspca_dev, 0xe0, 0x00); + reg_w(gspca_dev, 0xe0, 0x01); + set_led(gspca_dev, 0); + reg_w(gspca_dev, 0xe0, 0x00); + + return 0; +} + +static int sd_start(struct gspca_dev *gspca_dev) +{ + switch (gspca_dev->curr_mode) { + case QVGA_MODE: /* 320x240 */ + sccb_w_array(gspca_dev, sensor_start_1_vga, + ARRAY_SIZE(sensor_start_1_vga)); + reg_w_array(gspca_dev, bridge_start_qvga, + ARRAY_SIZE(bridge_start_qvga)); + sccb_w_array(gspca_dev, sensor_start_2_qvga, + ARRAY_SIZE(sensor_start_2_qvga)); + break; + case VGA_MODE: /* 640x480 */ + sccb_w_array(gspca_dev, sensor_start_1_vga, + ARRAY_SIZE(sensor_start_1_vga)); + reg_w_array(gspca_dev, bridge_start_vga, + ARRAY_SIZE(bridge_start_vga)); + sccb_w_array(gspca_dev, sensor_start_2_vga, + ARRAY_SIZE(sensor_start_2_vga)); + break; + case SVGA_MODE: /* 800x600 */ + sccb_w_array(gspca_dev, sensor_start_1_svga, + ARRAY_SIZE(sensor_start_1_svga)); + reg_w_array(gspca_dev, bridge_start_svga, + ARRAY_SIZE(bridge_start_svga)); + sccb_w_array(gspca_dev, sensor_start_2_svga, + ARRAY_SIZE(sensor_start_2_svga)); + break; + case XGA_MODE: /* 1024x768 */ + sccb_w_array(gspca_dev, sensor_start_1_xga, + ARRAY_SIZE(sensor_start_1_xga)); + reg_w_array(gspca_dev, bridge_start_xga, + ARRAY_SIZE(bridge_start_xga)); + sccb_w_array(gspca_dev, sensor_start_2_svga, + ARRAY_SIZE(sensor_start_2_svga)); + break; + default: +/* case SXGA_MODE: * 1280x1024 */ + sccb_w_array(gspca_dev, sensor_start_1_sxga, + ARRAY_SIZE(sensor_start_1_sxga)); + reg_w_array(gspca_dev, bridge_start_sxga, + ARRAY_SIZE(bridge_start_sxga)); + sccb_w_array(gspca_dev, sensor_start_2_sxga, + ARRAY_SIZE(sensor_start_2_sxga)); + break; + } + setfreq(gspca_dev); + setautogain(gspca_dev); + setbrightness(gspca_dev); + setcontrast(gspca_dev); + setexposure(gspca_dev); + setsharpness(gspca_dev); + setsatur(gspca_dev); + + reg_w(gspca_dev, 0xe0, 0x00); + reg_w(gspca_dev, 0xe0, 0x00); + set_led(gspca_dev, 1); + return 0; +} + +static void sd_stopN(struct gspca_dev *gspca_dev) +{ + reg_w(gspca_dev, 0xe0, 0x01); + set_led(gspca_dev, 0); + reg_w(gspca_dev, 0xe0, 0x00); +} + +/* Values for bmHeaderInfo (Video and Still Image Payload Headers, 2.4.3.3) */ +#define UVC_STREAM_EOH (1 << 7) +#define UVC_STREAM_ERR (1 << 6) +#define UVC_STREAM_STI (1 << 5) +#define UVC_STREAM_RES (1 << 4) +#define UVC_STREAM_SCR (1 << 3) +#define UVC_STREAM_PTS (1 << 2) +#define UVC_STREAM_EOF (1 << 1) +#define UVC_STREAM_FID (1 << 0) + +static void sd_pkt_scan(struct gspca_dev *gspca_dev, + u8 *data, int len) +{ + struct sd *sd = (struct sd *) gspca_dev; + __u32 this_pts; + u8 this_fid; + int remaining_len = len; + + do { + len = min(remaining_len, 2040); + + /* Payloads are prefixed with a UVC-style header. We + consider a frame to start when the FID toggles, or the PTS + changes. A frame ends when EOF is set, and we've received + the correct number of bytes. */ + + /* Verify UVC header. Header length is always 12 */ + if (data[0] != 12 || len < 12) { + PDEBUG(D_PACK, "bad header"); + goto discard; + } + + /* Check errors */ + if (data[1] & UVC_STREAM_ERR) { + PDEBUG(D_PACK, "payload error"); + goto discard; + } + + /* Extract PTS and FID */ + if (!(data[1] & UVC_STREAM_PTS)) { + PDEBUG(D_PACK, "PTS not present"); + goto discard; + } + this_pts = (data[5] << 24) | (data[4] << 16) + | (data[3] << 8) | data[2]; + this_fid = data[1] & UVC_STREAM_FID; + + /* If PTS or FID has changed, start a new frame. */ + if (this_pts != sd->last_pts || this_fid != sd->last_fid) { + if (gspca_dev->last_packet_type == INTER_PACKET) + gspca_frame_add(gspca_dev, LAST_PACKET, + NULL, 0); + sd->last_pts = this_pts; + sd->last_fid = this_fid; + gspca_frame_add(gspca_dev, FIRST_PACKET, + data + 12, len - 12); + /* If this packet is marked as EOF, end the frame */ + } else if (data[1] & UVC_STREAM_EOF) { + sd->last_pts = 0; + gspca_frame_add(gspca_dev, LAST_PACKET, + data + 12, len - 12); + } else { + + /* Add the data from this payload */ + gspca_frame_add(gspca_dev, INTER_PACKET, + data + 12, len - 12); + } + + /* Done this payload */ + goto scan_next; + +discard: + /* Discard data until a new frame starts. */ + gspca_dev->last_packet_type = DISCARD_PACKET; + +scan_next: + remaining_len -= len; + data += len; + } while (remaining_len > 0); +} + +/* controls */ +static int sd_setbrightness(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sd->brightness = val; + if (gspca_dev->streaming) + setbrightness(gspca_dev); + return 0; +} + +static int sd_getbrightness(struct gspca_dev *gspca_dev, __s32 *val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + *val = sd->brightness; + return 0; +} + +static int sd_setcontrast(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sd->contrast = val; + if (gspca_dev->streaming) + setcontrast(gspca_dev); + return 0; +} + +static int sd_getcontrast(struct gspca_dev *gspca_dev, __s32 *val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + *val = sd->contrast; + return 0; +} + +static int sd_setautogain(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sd->autogain = val; + + if (gspca_dev->streaming) { + if (val) + gspca_dev->ctrl_inac |= (1 << EXPO_IDX); + else + gspca_dev->ctrl_inac &= ~(1 << EXPO_IDX); + setautogain(gspca_dev); + } + return 0; +} + +static int sd_getautogain(struct gspca_dev *gspca_dev, __s32 *val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + *val = sd->autogain; + return 0; +} + +static int sd_setexposure(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sd->exposure = val; + if (gspca_dev->streaming) + setexposure(gspca_dev); + return 0; +} + +static int sd_getexposure(struct gspca_dev *gspca_dev, __s32 *val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + *val = sd->exposure; + return 0; +} + +static int sd_setsharpness(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sd->sharpness = val; + if (gspca_dev->streaming) + setsharpness(gspca_dev); + return 0; +} + +static int sd_getsharpness(struct gspca_dev *gspca_dev, __s32 *val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + *val = sd->sharpness; + return 0; +} + +static int sd_setsatur(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sd->satur = val; + if (gspca_dev->streaming) + setsatur(gspca_dev); + return 0; +} + +static int sd_getsatur(struct gspca_dev *gspca_dev, __s32 *val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + *val = sd->satur; + return 0; +} +static int sd_setfreq(struct gspca_dev *gspca_dev, __s32 val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + sd->freq = val; + if (gspca_dev->streaming) + setfreq(gspca_dev); + return 0; +} + +static int sd_getfreq(struct gspca_dev *gspca_dev, __s32 *val) +{ + struct sd *sd = (struct sd *) gspca_dev; + + *val = sd->freq; + return 0; +} + +static int sd_querymenu(struct gspca_dev *gspca_dev, + struct v4l2_querymenu *menu) +{ + switch (menu->id) { + case V4L2_CID_POWER_LINE_FREQUENCY: + switch (menu->index) { + case 0: /* V4L2_CID_POWER_LINE_FREQUENCY_DISABLED */ + strcpy((char *) menu->name, "NoFliker"); + return 0; + case 1: /* V4L2_CID_POWER_LINE_FREQUENCY_50HZ */ + strcpy((char *) menu->name, "50 Hz"); + return 0; + case 2: /* V4L2_CID_POWER_LINE_FREQUENCY_60HZ */ + strcpy((char *) menu->name, "60 Hz"); + return 0; + } + break; + } + return -EINVAL; +} + +/* sub-driver description */ +static const struct sd_desc sd_desc = { + .name = MODULE_NAME, + .ctrls = sd_ctrls, + .nctrls = ARRAY_SIZE(sd_ctrls), + .config = sd_config, + .init = sd_init, + .start = sd_start, + .stopN = sd_stopN, + .pkt_scan = sd_pkt_scan, + .querymenu = sd_querymenu, +}; + +/* -- module initialisation -- */ +static const __devinitdata struct usb_device_id device_table[] = { + {USB_DEVICE(0x06f8, 0x3003)}, + {} +}; + +MODULE_DEVICE_TABLE(usb, device_table); + +/* -- device connect -- */ +static int sd_probe(struct usb_interface *intf, const struct usb_device_id *id) +{ + return gspca_dev_probe(intf, id, &sd_desc, sizeof(struct sd), + THIS_MODULE); +} + +static struct usb_driver sd_driver = { + .name = MODULE_NAME, + .id_table = device_table, + .probe = sd_probe, + .disconnect = gspca_disconnect, +#ifdef CONFIG_PM + .suspend = gspca_suspend, + .resume = gspca_resume, +#endif +}; + +/* -- module insert / remove -- */ +static int __init sd_mod_init(void) +{ + int ret; + + ret = usb_register(&sd_driver); + if (ret < 0) + return ret; + PDEBUG(D_PROBE, "registered"); + return 0; +} + +static void __exit sd_mod_exit(void) +{ + usb_deregister(&sd_driver); + PDEBUG(D_PROBE, "deregistered"); +} + +module_init(sd_mod_init); +module_exit(sd_mod_exit); -- cgit v1.2.3-70-g09d2 From 9336960d075839b41bef89a0c68899c4025e8289 Mon Sep 17 00:00:00 2001 From: Theodore Kilgore Date: Fri, 25 Dec 2009 05:19:24 -0300 Subject: V4L/DVB (13993): gspca.txt: add cams supported by mr97310a, sq905(c) and sn9c2028 drivers gspca.txt: add cams supported by mr97310a, sq905(c) and sn9c2028 drivers Signed-off-by: Theodore Kilgore Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/gspca.txt | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt index c6364faa15a..9de9db03f9d 100644 --- a/Documentation/video4linux/gspca.txt +++ b/Documentation/video4linux/gspca.txt @@ -42,6 +42,7 @@ ov519 041e:4064 Creative Live! VISTA VF0420 ov519 041e:4067 Creative Live! Cam Video IM (VF0350) ov519 041e:4068 Creative Live! VISTA VF0470 spca561 0458:7004 Genius VideoCAM Express V2 +sn9c2028 0458:7005 Genius Smart 300, version 2 sunplus 0458:7006 Genius Dsc 1.3 Smart zc3xx 0458:7007 Genius VideoCam V2 zc3xx 0458:700c Genius VideoCam V3 @@ -226,7 +227,8 @@ sunplus 08ca:2050 Medion MD 41437 sunplus 08ca:2060 Aiptek PocketDV5300 tv8532 0923:010f ICM532 cams mars 093a:050f Mars-Semi Pc-Camera -mr97310a 093a:010f Sakar Digital no. 77379 +mr97310a 093a:010e All four known CIF cams with this ID +mr97310a 093a:010f All four known VGA cams with this ID pac207 093a:2460 Qtec Webcam 100 pac207 093a:2461 HP Webcam pac207 093a:2463 Philips SPC 220 NC @@ -326,6 +328,10 @@ sn9c20x 0c45:62b0 PC Camera (SN9C202 + MT9V011/MT9V111/MT9V112) sn9c20x 0c45:62b3 PC Camera (SN9C202 + OV9655) sn9c20x 0c45:62bb PC Camera (SN9C202 + OV7660) sn9c20x 0c45:62bc PC Camera (SN9C202 + HV7131R) +sn9c2028 0c45:8001 Wild Planet Digital Spy Camera +sn9c2028 0c45:8003 Sakar #11199, #6637x, #67480 keychain cams +sn9c2028 0c45:8008 Mini-Shotz ms-350 +sn9c2028 0c45:800a Vivitar Vivicam 3350B sunplus 0d64:0303 Sunplus FashionCam DXG ov519 0e96:c001 TRUST 380 USB2 SPACEC@M etoms 102c:6151 Qcam Sangha CIF @@ -343,10 +349,11 @@ spca501 1776:501c Arowana 300K CMOS Camera t613 17a1:0128 TASCORP JPEG Webcam, NGS Cyclops vc032x 17ef:4802 Lenovo Vc0323+MI1310_SOC pac207 2001:f115 D-Link DSB-C120 -sq905c 2770:9050 sq905c -sq905c 2770:905c DualCamera -sq905 2770:9120 Argus Digital Camera DC1512 -sq905c 2770:913d sq905c +sq905c 2770:9050 Disney pix micro (CIF) +sq905c 2770:9052 Disney pix micro 2 (VGA) +sq905c 2770:905c All 11 known cameras with this ID +sq905 2770:9120 All 24 known cameras with this ID +sq905c 2770:913d All 4 known cameras with this ID spca500 2899:012c Toptro Industrial ov519 8020:ef04 ov519 spca508 8086:0110 Intel Easy PC Camera -- cgit v1.2.3-70-g09d2 From 0b32d65cd7938d31eebd9c62aab6a59a3c4cf0f8 Mon Sep 17 00:00:00 2001 From: Kusanagi Kouichi Date: Fri, 22 Jan 2010 04:55:28 -0300 Subject: V4L/DVB: cx23885: Add support for LEADTEK WinFast PxTV1200 I tested only tv and composite. Video works fine but no audio. Signed-off-by: Kusanagi Kouichi Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/CARDLIST.cx23885 | 1 + drivers/media/video/cx23885/cx23885-cards.c | 32 +++++++++++++++++++++++++++++ drivers/media/video/cx23885/cx23885-video.c | 13 ++++++++++++ drivers/media/video/cx23885/cx23885.h | 1 + 4 files changed, 47 insertions(+) (limited to 'Documentation') diff --git a/Documentation/video4linux/CARDLIST.cx23885 b/Documentation/video4linux/CARDLIST.cx23885 index 7539e8fa1ff..16ca030e118 100644 --- a/Documentation/video4linux/CARDLIST.cx23885 +++ b/Documentation/video4linux/CARDLIST.cx23885 @@ -26,3 +26,4 @@ 25 -> Compro VideoMate E800 [1858:e800] 26 -> Hauppauge WinTV-HVR1290 [0070:8551] 27 -> Mygica X8558 PRO DMB-TH [14f1:8578] + 28 -> LEADTEK WinFast PxTV1200 [107d:6f22] diff --git a/drivers/media/video/cx23885/cx23885-cards.c b/drivers/media/video/cx23885/cx23885-cards.c index 1ec48169277..d639186f645 100644 --- a/drivers/media/video/cx23885/cx23885-cards.c +++ b/drivers/media/video/cx23885/cx23885-cards.c @@ -274,6 +274,31 @@ struct cx23885_board cx23885_boards[] = { .portb = CX23885_MPEG_DVB, .portc = CX23885_MPEG_DVB, }, + [CX23885_BOARD_LEADTEK_WINFAST_PXTV1200] = { + .name = "LEADTEK WinFast PxTV1200", + .porta = CX23885_ANALOG_VIDEO, + .tuner_type = TUNER_XC2028, + .tuner_addr = 0x61, + .input = {{ + .type = CX23885_VMUX_TELEVISION, + .vmux = CX25840_VIN2_CH1 | + CX25840_VIN5_CH2 | + CX25840_NONE0_CH3, + }, { + .type = CX23885_VMUX_COMPOSITE1, + .vmux = CX25840_COMPOSITE1, + }, { + .type = CX23885_VMUX_SVIDEO, + .vmux = CX25840_SVIDEO_LUMA3 | + CX25840_SVIDEO_CHROMA4, + }, { + .type = CX23885_VMUX_COMPONENT, + .vmux = CX25840_VIN7_CH1 | + CX25840_VIN6_CH2 | + CX25840_VIN8_CH3 | + CX25840_COMPONENT_ON, + } }, + }, }; const unsigned int cx23885_bcount = ARRAY_SIZE(cx23885_boards); @@ -417,6 +442,10 @@ struct cx23885_subid cx23885_subids[] = { .subvendor = 0x14f1, .subdevice = 0x8578, .card = CX23885_BOARD_MYGICA_X8558PRO, + }, { + .subvendor = 0x107d, + .subdevice = 0x6f22, + .card = CX23885_BOARD_LEADTEK_WINFAST_PXTV1200, }, }; const unsigned int cx23885_idcount = ARRAY_SIZE(cx23885_subids); @@ -617,6 +646,7 @@ int cx23885_tuner_callback(void *priv, int component, int command, int arg) case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: /* Tuner Reset Command */ bitmask = 0x04; break; @@ -769,6 +799,7 @@ void cx23885_gpio_setup(struct cx23885_dev *dev) case CX23885_BOARD_LEADTEK_WINFAST_PXDVR3200_H: case CX23885_BOARD_COMPRO_VIDEOMATE_E650F: case CX23885_BOARD_COMPRO_VIDEOMATE_E800: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: /* GPIO-2 xc3028 tuner reset */ /* The following GPIO's are on the internal AVCore (cx25840) */ @@ -1076,6 +1107,7 @@ void cx23885_card_setup(struct cx23885_dev *dev) case CX23885_BOARD_MYGICA_X8506: case CX23885_BOARD_MAGICPRO_PROHDTVE2: case CX23885_BOARD_HAUPPAUGE_HVR1290: + case CX23885_BOARD_LEADTEK_WINFAST_PXTV1200: dev->sd_cx25840 = v4l2_i2c_new_subdev(&dev->v4l2_dev, &dev->i2c_bus[2].i2c_adap, "cx25840", "cx25840", 0x88 >> 1, NULL); diff --git a/drivers/media/video/cx23885/cx23885-video.c b/drivers/media/video/cx23885/cx23885-video.c index 8934d61cf66..2d3ac8b83dc 100644 --- a/drivers/media/video/cx23885/cx23885-video.c +++ b/drivers/media/video/cx23885/cx23885-video.c @@ -36,6 +36,7 @@ #include #include #include "cx23885-ioctl.h" +#include "tuner-xc2028.h" MODULE_DESCRIPTION("v4l2 driver module for cx23885 based TV cards"); MODULE_AUTHOR("Steven Toth "); @@ -1505,6 +1506,18 @@ int cx23885_video_register(struct cx23885_dev *dev) tun_setup.tuner_callback = cx23885_tuner_callback; v4l2_subdev_call(sd, tuner, s_type_addr, &tun_setup); + + if (dev->board == CX23885_BOARD_LEADTEK_WINFAST_PXTV1200) { + struct xc2028_ctrl ctrl = { + .fname = XC2028_DEFAULT_FIRMWARE, + .max_len = 64 + }; + struct v4l2_priv_tun_config cfg = { + .tuner = dev->tuner_type, + .priv = &ctrl + }; + v4l2_subdev_call(sd, tuner, s_config, &cfg); + } } } diff --git a/drivers/media/video/cx23885/cx23885.h b/drivers/media/video/cx23885/cx23885.h index 08b3f6b136a..0e3a98d243c 100644 --- a/drivers/media/video/cx23885/cx23885.h +++ b/drivers/media/video/cx23885/cx23885.h @@ -81,6 +81,7 @@ #define CX23885_BOARD_COMPRO_VIDEOMATE_E800 25 #define CX23885_BOARD_HAUPPAUGE_HVR1290 26 #define CX23885_BOARD_MYGICA_X8558PRO 27 +#define CX23885_BOARD_LEADTEK_WINFAST_PXTV1200 28 #define GPIO_0 0x00000001 #define GPIO_1 0x00000002 -- cgit v1.2.3-70-g09d2 From e7e41d3b59877475e4c19ddd0d33cb3ef5298f87 Mon Sep 17 00:00:00 2001 From: Oliver Endriss Date: Fri, 5 Feb 2010 07:52:44 -0300 Subject: V4L/DVB: get_dvb_firmware: Add function to retrieve nGene firmwares Commiter: Oliver Endriss Use 'get_dvb_firmware ngene' to download 'ngene_15.fw' and 'ngene_17.fw'. Signed-off-by: Oliver Endriss Signed-off-by: Mauro Carvalho Chehab --- Documentation/dvb/get_dvb_firmware | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/dvb/get_dvb_firmware b/Documentation/dvb/get_dvb_firmware index 14b7b5a3bcb..f550fdaa983 100644 --- a/Documentation/dvb/get_dvb_firmware +++ b/Documentation/dvb/get_dvb_firmware @@ -26,7 +26,7 @@ use IO::Handle; "dec3000s", "vp7041", "dibusb", "nxt2002", "nxt2004", "or51211", "or51132_qam", "or51132_vsb", "bluebird", "opera1", "cx231xx", "cx18", "cx23885", "pvrusb2", "mpc718", - "af9015"); + "af9015", "ngene"); # Check args syntax() if (scalar(@ARGV) != 1); @@ -549,6 +549,24 @@ sub af9015 { close INFILE; } +sub ngene { + my $url = "http://www.digitaldevices.de/download/"; + my $file1 = "ngene_15.fw"; + my $hash1 = "d798d5a757121174f0dbc5f2833c0c85"; + my $file2 = "ngene_17.fw"; + my $hash2 = "26b687136e127b8ac24b81e0eeafc20b"; + + checkstandard(); + + wgetfile($file1, $url . $file1); + verify($file1, $hash1); + + wgetfile($file2, $url . $file2); + verify($file2, $hash2); + + "$file1, $file2"; +} + # --------------------------------------------------------------- # Utilities -- cgit v1.2.3-70-g09d2 From 87147ff03a8aa27b9cc94872b195e6f8bb922feb Mon Sep 17 00:00:00 2001 From: Oliver Endriss Date: Fri, 5 Feb 2010 07:57:58 -0300 Subject: V4L/DVB: get_dvb_firmware: Fix typo, sort list of components Commiter: Oliver Endriss Fix typo. Sort list of components for better readability. Signed-off-by: Oliver Endriss Signed-off-by: Mauro Carvalho Chehab --- Documentation/dvb/get_dvb_firmware | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'Documentation') diff --git a/Documentation/dvb/get_dvb_firmware b/Documentation/dvb/get_dvb_firmware index f550fdaa983..239cbdbf4d1 100644 --- a/Documentation/dvb/get_dvb_firmware +++ b/Documentation/dvb/get_dvb_firmware @@ -39,7 +39,7 @@ for ($i=0; $i < scalar(@components); $i++) { die $@ if $@; print STDERR <\n"; print STDERR "Supported components:\n"; + @components = sort @components; for($i=0; $i < scalar(@components); $i++) { print STDERR "\t" . $components[$i] . "\n"; } -- cgit v1.2.3-70-g09d2 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 'Documentation') diff --git a/Documentation/video4linux/README.tlg2300 b/Documentation/video4linux/README.tlg2300 new file mode 100644 index 00000000000..82417db3256 --- /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 2533fc45a44..f427294b85e 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 64682bff228..2f9c57d5fda 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 2af68ee8412..5163289e13e 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 00000000000..2c29ec659b4 --- /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 00000000000..81bb7fdd1e3 --- /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 00000000000..6f42621ad47 --- /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 00000000000..619fd009e96 --- /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 00000000000..4133aee568b --- /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 00000000000..6df93803e3a --- /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 00000000000..bdbb0c11b3a --- /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 00000000000..5f0300ac465 --- /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 00000000000..ba6f4ae3b2c --- /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-70-g09d2 From 007ad830364e795316d2825f1ab68b3a53a3d56c Mon Sep 17 00:00:00 2001 From: Huang Shijie Date: Thu, 11 Feb 2010 03:53:51 -0300 Subject: V4L/DVB: tlg2300: remove the country code for analog tv and radio video : use the V4L2_STD macros to select the proper audio setting. radio : add preemphasis ctr. test it by the command: v4l2-ctl -d /dev/radio0 --set-ctrl=pre_emphasis_settings=1 [mchehab@redhat.com: folded documentation patch] Signed-off-by: Huang Shijie Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/README.tlg2300 | 198 ++----------------------------- drivers/media/video/tlg2300/pd-common.h | 4 +- drivers/media/video/tlg2300/pd-main.c | 36 ------ drivers/media/video/tlg2300/pd-radio.c | 107 ++++++++++++++--- drivers/media/video/tlg2300/pd-video.c | 59 +++++---- 5 files changed, 135 insertions(+), 269 deletions(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/README.tlg2300 b/Documentation/video4linux/README.tlg2300 index 82417db3256..416ccb93d8c 100644 --- a/Documentation/video4linux/README.tlg2300 +++ b/Documentation/video4linux/README.tlg2300 @@ -37,195 +37,11 @@ TESTED APPLICATIONS: --------------------------------------------------------------------------- KNOWN PROBLEMS: +about preemphasis: + You can set the preemphasis for radio by the following command: + #v4l2-ctl -d /dev/radio0 --set-ctrl=pre_emphasis_settings=1 + + "pre_emphasis_settings=1" means that you select the 50us. If you want + to select the 75us, please use "pre_emphasis_settings=2" + -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/drivers/media/video/tlg2300/pd-common.h b/drivers/media/video/tlg2300/pd-common.h index 619fd009e96..ae9cb6c581a 100644 --- a/drivers/media/video/tlg2300/pd-common.h +++ b/drivers/media/video/tlg2300/pd-common.h @@ -118,6 +118,7 @@ struct radio_data { __u32 fm_freq; int users; unsigned int is_radio_streaming; + int pre_emphasis; struct video_device *fm_dev; }; @@ -185,7 +186,6 @@ struct poseidon { struct pd_dvb_adapter dvb_data; /* DVB */ u32 state; - int country_code; struct file *file_for_stream; /* the active stream*/ #ifdef CONFIG_PM @@ -240,7 +240,6 @@ struct video_device *vdev_init(struct poseidon *, struct video_device *); 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, @@ -252,7 +251,6 @@ 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); diff --git a/drivers/media/video/tlg2300/pd-main.c b/drivers/media/video/tlg2300/pd-main.c index 6df93803e3a..fdcc5007a70 100644 --- a/drivers/media/video/tlg2300/pd-main.c +++ b/drivers/media/video/tlg2300/pd-main.c @@ -189,41 +189,6 @@ int set_tuner_mode(struct poseidon *pd, unsigned char mode) 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); @@ -462,7 +427,6 @@ static int poseidon_probe(struct usb_interface *interface, struct device *dev = &interface->dev; logpm(pd); - pd->country_code = 86; mutex_init(&pd->lock); /* register v4l2 device */ diff --git a/drivers/media/video/tlg2300/pd-radio.c b/drivers/media/video/tlg2300/pd-radio.c index bdbb0c11b3a..755766b1515 100644 --- a/drivers/media/video/tlg2300/pd-radio.c +++ b/drivers/media/video/tlg2300/pd-radio.c @@ -22,9 +22,16 @@ static int poseidon_fm_open(struct file *filp); #define TUNER_FREQ_MIN_FM 76000000 #define TUNER_FREQ_MAX_FM 108000000 +#define MAX_PREEMPHASIS (V4L2_PREEMPHASIS_75_uS + 1) +static int preemphasis[MAX_PREEMPHASIS] = { + TLG_TUNE_ASTD_NONE, /* V4L2_PREEMPHASIS_DISABLED */ + TLG_TUNE_ASTD_FM_EUR, /* V4L2_PREEMPHASIS_50_uS */ + TLG_TUNE_ASTD_FM_US, /* V4L2_PREEMPHASIS_75_uS */ +}; + static int poseidon_check_mode_radio(struct poseidon *p) { - int ret, radiomode; + int ret; u32 status; set_current_state(TASK_INTERRUPTIBLE); @@ -38,8 +45,8 @@ static int poseidon_check_mode_radio(struct poseidon *p) 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_ANA_STD, + p->radio_data.pre_emphasis, &status); ret |= send_set_req(p, TUNER_AUD_MODE, TLG_TUNE_TVAUDIO_MODE_STEREO, &status); ret |= send_set_req(p, AUDIO_SAMPLE_RATE_SEL, @@ -91,7 +98,9 @@ static int poseidon_fm_open(struct file *filp) usb_autopm_get_interface(p->interface); if (0 == p->state) { - p->country_code = country_code; + /* default pre-emphasis */ + if (p->radio_data.pre_emphasis == 0) + p->radio_data.pre_emphasis = TLG_TUNE_ASTD_FM_EUR; set_debug_mode(vfd, debug_mode); ret = poseidon_check_mode_radio(p); @@ -205,13 +214,12 @@ int fm_get_freq(struct file *file, void *priv, struct v4l2_frequency *argp) static int set_frequency(struct poseidon *p, __u32 frequency) { __u32 freq ; - int ret, status, radiomode; + int ret, status; 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); + ret = send_set_req(p, TUNER_AUD_ANA_STD, + p->radio_data.pre_emphasis, &status); freq = (frequency * 125) * 500 / 1000;/* kHZ */ if (freq < TUNER_FREQ_MIN_FM/1000 || freq > TUNER_FREQ_MAX_FM/1000) { @@ -253,27 +261,86 @@ int fm_set_freq(struct file *file, void *priv, struct v4l2_frequency *argp) int tlg_fm_vidioc_g_ctrl(struct file *file, void *priv, struct v4l2_control *arg) { - return 0; + return 0; +} + +int tlg_fm_vidioc_g_exts_ctrl(struct file *file, void *fh, + struct v4l2_ext_controls *ctrls) +{ + struct poseidon *p = file->private_data; + int i; + + if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) + return -EINVAL; + + for (i = 0; i < ctrls->count; i++) { + struct v4l2_ext_control *ctrl = ctrls->controls + i; + + if (ctrl->id != V4L2_CID_TUNE_PREEMPHASIS) + continue; + + if (i < MAX_PREEMPHASIS) + ctrl->value = p->radio_data.pre_emphasis; + } + return 0; } -int tlg_fm_vidioc_exts_ctrl(struct file *file, void *fh, - struct v4l2_ext_controls *a) +int tlg_fm_vidioc_s_exts_ctrl(struct file *file, void *fh, + struct v4l2_ext_controls *ctrls) { - return 0; + int i; + + if (ctrls->ctrl_class != V4L2_CTRL_CLASS_FM_TX) + return -EINVAL; + + for (i = 0; i < ctrls->count; i++) { + struct v4l2_ext_control *ctrl = ctrls->controls + i; + + if (ctrl->id != V4L2_CID_TUNE_PREEMPHASIS) + continue; + + if (ctrl->value >= 0 && ctrl->value < MAX_PREEMPHASIS) { + struct poseidon *p = file->private_data; + int pre_emphasis = preemphasis[ctrl->value]; + u32 status; + + send_set_req(p, TUNER_AUD_ANA_STD, + pre_emphasis, &status); + p->radio_data.pre_emphasis = pre_emphasis; + } + } + return 0; } int tlg_fm_vidioc_s_ctrl(struct file *file, void *priv, - struct v4l2_control *arg) + struct v4l2_control *ctrl) { - return 0; + return 0; } int tlg_fm_vidioc_queryctrl(struct file *file, void *priv, - struct v4l2_queryctrl *arg) + struct v4l2_queryctrl *ctrl) { - arg->minimum = 0; - arg->maximum = 65535; - return 0; + if (!(ctrl->id & V4L2_CTRL_FLAG_NEXT_CTRL)) + return -EINVAL; + + ctrl->id &= ~V4L2_CTRL_FLAG_NEXT_CTRL; + if (ctrl->id != V4L2_CID_TUNE_PREEMPHASIS) { + /* return the next supported control */ + ctrl->id = V4L2_CID_TUNE_PREEMPHASIS; + v4l2_ctrl_query_fill(ctrl, V4L2_PREEMPHASIS_DISABLED, + V4L2_PREEMPHASIS_75_uS, 1, + V4L2_PREEMPHASIS_50_uS); + ctrl->flags = V4L2_CTRL_FLAG_UPDATE; + return 0; + } + return -EINVAL; +} + +int tlg_fm_vidioc_querymenu(struct file *file, void *fh, + struct v4l2_querymenu *qmenu) +{ + return v4l2_ctrl_query_menu(qmenu, NULL, NULL); } static int vidioc_s_tuner(struct file *file, void *priv, struct v4l2_tuner *vt) @@ -311,9 +378,11 @@ static const struct v4l2_ioctl_ops poseidon_fm_ioctl_ops = { .vidioc_g_input = vidioc_g_input, .vidioc_s_input = vidioc_s_input, .vidioc_queryctrl = tlg_fm_vidioc_queryctrl, + .vidioc_querymenu = tlg_fm_vidioc_querymenu, .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_ext_ctrls = tlg_fm_vidioc_s_exts_ctrl, + .vidioc_g_ext_ctrls = tlg_fm_vidioc_g_exts_ctrl, .vidioc_s_tuner = vidioc_s_tuner, .vidioc_g_tuner = tlg_fm_vidioc_g_tuner, .vidioc_g_frequency = fm_get_freq, diff --git a/drivers/media/video/tlg2300/pd-video.c b/drivers/media/video/tlg2300/pd-video.c index 5f0300ac465..becfba6a304 100644 --- a/drivers/media/video/tlg2300/pd-video.c +++ b/drivers/media/video/tlg2300/pd-video.c @@ -15,10 +15,6 @@ 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"); @@ -93,27 +89,53 @@ 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, }, }; +struct video_std_to_audio_std { + v4l2_std_id video_std; + int audio_std; +}; + +static const struct video_std_to_audio_std video_to_audio_map[] = { + /* country : { 27, 32, 33, 34, 36, 44, 45, 46, 47, 48, 64, + 65, 86, 351, 352, 353, 354, 358, 372, 852, 972 } */ + { (V4L2_STD_PAL_I | V4L2_STD_PAL_B | V4L2_STD_PAL_D | + V4L2_STD_SECAM_L | V4L2_STD_SECAM_D), TLG_TUNE_ASTD_NICAM }, + + /* country : { 1, 52, 54, 55, 886 } */ + {V4L2_STD_NTSC_M | V4L2_STD_PAL_N | V4L2_STD_PAL_M, TLG_TUNE_ASTD_BTSC}, + + /* country : { 81 } */ + { V4L2_STD_NTSC_M_JP, TLG_TUNE_ASTD_EIAJ }, + + /* other country : TLG_TUNE_ASTD_A2 */ +}; +static const unsigned int map_size = ARRAY_SIZE(video_to_audio_map); + +static int get_audio_std(v4l2_std_id v4l2_std) +{ + int i = 0; + + for (; i < map_size; i++) { + if (v4l2_std & video_to_audio_map[i].video_std) + return video_to_audio_map[i].audio_std; + } + return TLG_TUNE_ASTD_A2; +} + static int vidioc_querycap(struct file *file, void *fh, struct v4l2_capability *cap) { @@ -1067,7 +1089,7 @@ static int pd_vidioc_s_tuner(struct poseidon *pd, int index) 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); + audiomode = get_audio_std(pd->video_data.context.tvnormid); ret |= send_set_req(pd, TUNER_AUD_ANA_STD, audiomode, &cmd_status); if (!ret) @@ -1255,9 +1277,7 @@ static int vidioc_streamoff(struct file *file, void *fh, return videobuf_streamoff(&front->q); } -/* - * Set the firmware' default values : need altersetting and country code - */ +/* Set the firmware's default values : need altersetting */ static int pd_video_checkmode(struct poseidon *pd) { s32 ret = 0, cmd_status, audiomode; @@ -1286,8 +1306,8 @@ static int pd_video_checkmode(struct poseidon *pd) 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); + /* set the audio */ + audiomode = get_audio_std(pd->video_data.context.tvnormid); 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); @@ -1392,7 +1412,6 @@ static int pd_video_open(struct file *file) 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); -- cgit v1.2.3-70-g09d2 From 995f5fefb0c6abba3688b3aadf40e422b64b814a Mon Sep 17 00:00:00 2001 From: Hans Verkuil Date: Sat, 20 Feb 2010 09:41:03 -0300 Subject: V4L/DVB: media-spec: Fix documentation mistakes regarding I/O streaming The media spec contains several errors in the description of the I/O streaming ioctls, in particular with respect to the userptr I/O method. The most important change is that you really need to set count and index in v4l2_requestbuffer and v4l2_buffer when dealing with user pointer streaming. Signed-off-by: Hans Verkuil Signed-off-by: Mauro Carvalho Chehab --- Documentation/DocBook/v4l/io.xml | 3 +- Documentation/DocBook/v4l/vidioc-qbuf.xml | 40 +++++++++++++++------------ Documentation/DocBook/v4l/vidioc-querybuf.xml | 7 +++-- Documentation/DocBook/v4l/vidioc-reqbufs.xml | 36 +++++++++--------------- 4 files changed, 42 insertions(+), 44 deletions(-) (limited to 'Documentation') diff --git a/Documentation/DocBook/v4l/io.xml b/Documentation/DocBook/v4l/io.xml index f92f24323b2..e870330cbf7 100644 --- a/Documentation/DocBook/v4l/io.xml +++ b/Documentation/DocBook/v4l/io.xml @@ -589,7 +589,8 @@ number of a video input as in &v4l2-input; field A place holder for future extensions and custom (driver defined) buffer types -V4L2_BUF_TYPE_PRIVATE and higher. +V4L2_BUF_TYPE_PRIVATE and higher. Applications +should set this to 0. diff --git a/Documentation/DocBook/v4l/vidioc-qbuf.xml b/Documentation/DocBook/v4l/vidioc-qbuf.xml index 18708177815..b843bd7b389 100644 --- a/Documentation/DocBook/v4l/vidioc-qbuf.xml +++ b/Documentation/DocBook/v4l/vidioc-qbuf.xml @@ -54,12 +54,10 @@ to enqueue an empty (capturing) or filled (output) buffer in the driver's incoming queue. The semantics depend on the selected I/O method. - To enqueue a memory mapped -buffer applications set the type field of a -&v4l2-buffer; to the same buffer type as previously &v4l2-format; -type and &v4l2-requestbuffers; -type, the memory -field to V4L2_MEMORY_MMAP and the + To enqueue a buffer applications set the type +field of a &v4l2-buffer; to the same buffer type as was previously used +with &v4l2-format; type and &v4l2-requestbuffers; +type. Applications must also set the index field. Valid index numbers range from zero to the number of buffers allocated with &VIDIOC-REQBUFS; (&v4l2-requestbuffers; count) minus one. The @@ -70,8 +68,19 @@ intended for output (type is V4L2_BUF_TYPE_VBI_OUTPUT) applications must also initialize the bytesused, field and -timestamp fields. See for details. When +timestamp fields, see for details. +Applications must also set flags to 0. If a driver +supports capturing from specific video inputs and you want to specify a video +input, then flags should be set to +V4L2_BUF_FLAG_INPUT and the field +input must be initialized to the desired input. +The reserved field must be set to 0. + + + To enqueue a memory mapped +buffer applications set the memory +field to V4L2_MEMORY_MMAP. When VIDIOC_QBUF is called with a pointer to this structure the driver sets the V4L2_BUF_FLAG_MAPPED and @@ -81,14 +90,10 @@ structure the driver sets the &EINVAL;. To enqueue a user pointer -buffer applications set the type field of a -&v4l2-buffer; to the same buffer type as previously &v4l2-format; -type and &v4l2-requestbuffers; -type, the memory -field to V4L2_MEMORY_USERPTR and the +buffer applications set the memory +field to V4L2_MEMORY_USERPTR, the m.userptr field to the address of the -buffer and length to its size. When the -buffer is intended for output additional fields must be set as above. +buffer and length to its size. When VIDIOC_QBUF is called with a pointer to this structure the driver sets the V4L2_BUF_FLAG_QUEUED flag and clears the V4L2_BUF_FLAG_MAPPED and @@ -96,13 +101,14 @@ flag and clears the V4L2_BUF_FLAG_MAPPED and flags field, or it returns an error code. This ioctl locks the memory pages of the buffer in physical memory, they cannot be swapped out to disk. Buffers remain locked until -dequeued, until the &VIDIOC-STREAMOFF; or &VIDIOC-REQBUFS; ioctl are +dequeued, until the &VIDIOC-STREAMOFF; or &VIDIOC-REQBUFS; ioctl is called, or until the device is closed. Applications call the VIDIOC_DQBUF ioctl to dequeue a filled (capturing) or displayed (output) buffer from the driver's outgoing queue. They just set the -type and memory +type, memory +and reserved fields of a &v4l2-buffer; as above, when VIDIOC_DQBUF is called with a pointer to this structure the driver fills the remaining fields or returns an error code. diff --git a/Documentation/DocBook/v4l/vidioc-querybuf.xml b/Documentation/DocBook/v4l/vidioc-querybuf.xml index d834993e619..e649805a490 100644 --- a/Documentation/DocBook/v4l/vidioc-querybuf.xml +++ b/Documentation/DocBook/v4l/vidioc-querybuf.xml @@ -54,12 +54,13 @@ buffer at any time after buffers have been allocated with the &VIDIOC-REQBUFS; ioctl. Applications set the type field - of a &v4l2-buffer; to the same buffer type as previously + of a &v4l2-buffer; to the same buffer type as was previously used with &v4l2-format; type and &v4l2-requestbuffers; type, and the index field. Valid index numbers range from zero to the number of buffers allocated with &VIDIOC-REQBUFS; (&v4l2-requestbuffers; count) minus one. +The reserved field should to set to 0. After calling VIDIOC_QUERYBUF with a pointer to this structure drivers return an error code or fill the rest of the structure. @@ -68,8 +69,8 @@ the structure. V4L2_BUF_FLAG_MAPPED, V4L2_BUF_FLAG_QUEUED and V4L2_BUF_FLAG_DONE flags will be valid. The -memory field will be set to -V4L2_MEMORY_MMAP, the m.offset +memory field will be set to the current +I/O method, the m.offset contains the offset of the buffer from the start of the device memory, the length field its size. The driver may or may not set the remaining fields and flags, they are meaningless in diff --git a/Documentation/DocBook/v4l/vidioc-reqbufs.xml b/Documentation/DocBook/v4l/vidioc-reqbufs.xml index bab38084454..1c081637207 100644 --- a/Documentation/DocBook/v4l/vidioc-reqbufs.xml +++ b/Documentation/DocBook/v4l/vidioc-reqbufs.xml @@ -54,23 +54,23 @@ I/O. Memory mapped buffers are located in device memory and must be allocated with this ioctl before they can be mapped into the application's address space. User buffers are allocated by applications themselves, and this ioctl is merely used to switch the -driver into user pointer I/O mode. +driver into user pointer I/O mode and to setup some internal structures. - To allocate device buffers applications initialize three -fields of a v4l2_requestbuffers structure. + To allocate device buffers applications initialize all +fields of the v4l2_requestbuffers structure. They set the type field to the respective stream or buffer type, the count field to -the desired number of buffers, and memory -must be set to V4L2_MEMORY_MMAP. When the ioctl -is called with a pointer to this structure the driver attempts to -allocate the requested number of buffers and stores the actual number +the desired number of buffers, memory +must be set to the requested I/O method and the reserved array +must be zeroed. When the ioctl +is called with a pointer to this structure the driver will attempt to allocate +the requested number of buffers and it stores the actual number allocated in the count field. It can be smaller than the number requested, even zero, when the driver runs out -of free memory. A larger number is possible when the driver requires -more buffers to function correctly. - For example video output requires at least two buffers, +of free memory. A larger number is also possible when the driver requires +more buffers to function correctly. For example video output requires at least two buffers, one displayed and one filled by the application. - When memory mapping I/O is not supported the ioctl + When the I/O method is not supported the ioctl returns an &EINVAL;. Applications can call VIDIOC_REQBUFS @@ -81,14 +81,6 @@ in progress, an implicit &VIDIOC-STREAMOFF;. - To negotiate user pointer I/O, applications initialize only -the type field and set -memory to -V4L2_MEMORY_USERPTR. When the ioctl is called -with a pointer to this structure the driver prepares for user pointer -I/O, when this I/O method is not supported the ioctl returns an -&EINVAL;. - struct <structname>v4l2_requestbuffers</structname> @@ -97,9 +89,7 @@ I/O, when this I/O method is not supported the ioctl returns an __u32 count - The number of buffers requested or granted. This -field is only used when memory is set to -V4L2_MEMORY_MMAP. + The number of buffers requested or granted. &v4l2-buf-type; @@ -120,7 +110,7 @@ as the &v4l2-format; type field. See reserved[2] A place holder for future extensions and custom (driver defined) buffer types V4L2_BUF_TYPE_PRIVATE and -higher. +higher. This array should be zeroed by applications. -- cgit v1.2.3-70-g09d2 From 4b586a38b048b0d78874721e5b26cb6476fafb60 Mon Sep 17 00:00:00 2001 From: Jonathan Corbet Date: Mon, 22 Feb 2010 17:47:46 -0300 Subject: V4L/DVB: V4L2: Add a document describing the videobuf layer Videobuf is a moderately complex API which most V4L2 drivers should use, but its documentation is...sparse. This document attempts to improve the situation. Signed-off-by: Jonathan Corbet Reviewed-by: Randy Dunlap Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/v4l2-framework.txt | 107 +------- Documentation/video4linux/videobuf | 360 +++++++++++++++++++++++++++ 2 files changed, 371 insertions(+), 96 deletions(-) create mode 100644 Documentation/video4linux/videobuf (limited to 'Documentation') diff --git a/Documentation/video4linux/v4l2-framework.txt b/Documentation/video4linux/v4l2-framework.txt index 74d677c8b03..90b0a08ea47 100644 --- a/Documentation/video4linux/v4l2-framework.txt +++ b/Documentation/video4linux/v4l2-framework.txt @@ -599,99 +599,14 @@ video_device::minor fields. video buffer helper functions ----------------------------- -The v4l2 core API provides a standard method for dealing with video -buffers. Those methods allow a driver to implement read(), mmap() and -overlay() on a consistent way. - -There are currently methods for using video buffers on devices that -supports DMA with scatter/gather method (videobuf-dma-sg), DMA with -linear access (videobuf-dma-contig), and vmalloced buffers, mostly -used on USB drivers (videobuf-vmalloc). - -Any driver using videobuf should provide operations (callbacks) for -four handlers: - -ops->buf_setup - calculates the size of the video buffers and avoid they - to waste more than some maximum limit of RAM; -ops->buf_prepare - fills the video buffer structs and calls - videobuf_iolock() to alloc and prepare mmaped memory; -ops->buf_queue - advices the driver that another buffer were - requested (by read() or by QBUF); -ops->buf_release - frees any buffer that were allocated. - -In order to use it, the driver need to have a code (generally called at -interrupt context) that will properly handle the buffer request lists, -announcing that a new buffer were filled. - -The irq handling code should handle the videobuf task lists, in order -to advice videobuf that a new frame were filled, in order to honor to a -request. The code is generally like this one: - if (list_empty(&dma_q->active)) - return; - - buf = list_entry(dma_q->active.next, struct vbuffer, vb.queue); - - if (!waitqueue_active(&buf->vb.done)) - return; - - /* Some logic to handle the buf may be needed here */ - - list_del(&buf->vb.queue); - do_gettimeofday(&buf->vb.ts); - wake_up(&buf->vb.done); - -Those are the videobuffer functions used on drivers, implemented on -videobuf-core: - -- Videobuf init functions - videobuf_queue_sg_init() - Initializes the videobuf infrastructure. This function should be - called before any other videobuf function on drivers that uses DMA - Scatter/Gather buffers. - - videobuf_queue_dma_contig_init - Initializes the videobuf infrastructure. This function should be - called before any other videobuf function on drivers that need DMA - contiguous buffers. - - videobuf_queue_vmalloc_init() - Initializes the videobuf infrastructure. This function should be - called before any other videobuf function on USB (and other drivers) - that need a vmalloced type of videobuf. - -- videobuf_iolock() - Prepares the videobuf memory for the proper method (read, mmap, overlay). - -- videobuf_queue_is_busy() - Checks if a videobuf is streaming. - -- videobuf_queue_cancel() - Stops video handling. - -- videobuf_mmap_free() - frees mmap buffers. - -- videobuf_stop() - Stops video handling, ends mmap and frees mmap and other buffers. - -- V4L2 api functions. Those functions correspond to VIDIOC_foo ioctls: - videobuf_reqbufs(), videobuf_querybuf(), videobuf_qbuf(), - videobuf_dqbuf(), videobuf_streamon(), videobuf_streamoff(). - -- V4L1 api function (corresponds to VIDIOCMBUF ioctl): - videobuf_cgmbuf() - This function is used to provide backward compatibility with V4L1 - API. - -- Some help functions for read()/poll() operations: - videobuf_read_stream() - For continuous stream read() - videobuf_read_one() - For snapshot read() - videobuf_poll_stream() - polling help function - -The better way to understand it is to take a look at vivi driver. One -of the main reasons for vivi is to be a videobuf usage example. the -vivi_thread_tick() does the task that the IRQ callback would do on PCI -drivers (or the irq callback on USB). +The v4l2 core API provides a set of standard methods (called "videobuf") +for dealing with video buffers. Those methods allow a driver to implement +read(), mmap() and overlay() in a consistent way. There are currently +methods for using video buffers on devices that supports DMA with +scatter/gather method (videobuf-dma-sg), DMA with linear access +(videobuf-dma-contig), and vmalloced buffers, mostly used on USB drivers +(videobuf-vmalloc). + +Please see Documentation/video4linux/videobuf for more information on how +to use the videobuf layer. + diff --git a/Documentation/video4linux/videobuf b/Documentation/video4linux/videobuf new file mode 100644 index 00000000000..ba4ca991c55 --- /dev/null +++ b/Documentation/video4linux/videobuf @@ -0,0 +1,360 @@ +An introduction to the videobuf layer +Jonathan Corbet +Current as of 2.6.33 + +The videobuf layer functions as a sort of glue layer between a V4L2 driver +and user space. It handles the allocation and management of buffers for +the storage of video frames. There is a set of functions which can be used +to implement many of the standard POSIX I/O system calls, including read(), +poll(), and, happily, mmap(). Another set of functions can be used to +implement the bulk of the V4L2 ioctl() calls related to streaming I/O, +including buffer allocation, queueing and dequeueing, and streaming +control. Using videobuf imposes a few design decisions on the driver +author, but the payback comes in the form of reduced code in the driver and +a consistent implementation of the V4L2 user-space API. + +Buffer types + +Not all video devices use the same kind of buffers. In fact, there are (at +least) three common variations: + + - Buffers which are scattered in both the physical and (kernel) virtual + address spaces. (Almost) all user-space buffers are like this, but it + makes great sense to allocate kernel-space buffers this way as well when + it is possible. Unfortunately, it is not always possible; working with + this kind of buffer normally requires hardware which can do + scatter/gather DMA operations. + + - Buffers which are physically scattered, but which are virtually + contiguous; buffers allocated with vmalloc(), in other words. These + buffers are just as hard to use for DMA operations, but they can be + useful in situations where DMA is not available but virtually-contiguous + buffers are convenient. + + - Buffers which are physically contiguous. Allocation of this kind of + buffer can be unreliable on fragmented systems, but simpler DMA + controllers cannot deal with anything else. + +Videobuf can work with all three types of buffers, but the driver author +must pick one at the outset and design the driver around that decision. + +[It's worth noting that there's a fourth kind of buffer: "overlay" buffers +which are located within the system's video memory. The overlay +functionality is considered to be deprecated for most use, but it still +shows up occasionally in system-on-chip drivers where the performance +benefits merit the use of this technique. Overlay buffers can be handled +as a form of scattered buffer, but there are very few implementations in +the kernel and a description of this technique is currently beyond the +scope of this document.] + +Data structures, callbacks, and initialization + +Depending on which type of buffers are being used, the driver should +include one of the following files: + + /* Physically scattered */ + /* vmalloc() buffers */ + /* Physically contiguous */ + +The driver's data structure describing a V4L2 device should include a +struct videobuf_queue instance for the management of the buffer queue, +along with a list_head for the queue of available buffers. There will also +need to be an interrupt-safe spinlock which is used to protect (at least) +the queue. + +The next step is to write four simple callbacks to help videobuf deal with +the management of buffers: + + struct videobuf_queue_ops { + int (*buf_setup)(struct videobuf_queue *q, + unsigned int *count, unsigned int *size); + int (*buf_prepare)(struct videobuf_queue *q, + struct videobuf_buffer *vb, + enum v4l2_field field); + void (*buf_queue)(struct videobuf_queue *q, + struct videobuf_buffer *vb); + void (*buf_release)(struct videobuf_queue *q, + struct videobuf_buffer *vb); + }; + +buf_setup() is called early in the I/O process, when streaming is being +initiated; its purpose is to tell videobuf about the I/O stream. The count +parameter will be a suggested number of buffers to use; the driver should +check it for rationality and adjust it if need be. As a practical rule, a +minimum of two buffers are needed for proper streaming, and there is +usually a maximum (which cannot exceed 32) which makes sense for each +device. The size parameter should be set to the expected (maximum) size +for each frame of data. + +Each buffer (in the form of a struct videobuf_buffer pointer) will be +passed to buf_prepare(), which should set the buffer's size, width, height, +and field fields properly. If the buffer's state field is +VIDEOBUF_NEEDS_INIT, the driver should pass it to: + + int videobuf_iolock(struct videobuf_queue* q, struct videobuf_buffer *vb, + struct v4l2_framebuffer *fbuf); + +Among other things, this call will usually allocate memory for the buffer. +Finally, the buf_prepare() function should set the buffer's state to +VIDEOBUF_PREPARED. + +When a buffer is queued for I/O, it is passed to buf_queue(), which should +put it onto the driver's list of available buffers and set its state to +VIDEOBUF_QUEUED. Note that this function is called with the queue spinlock +held; if it tries to acquire it as well things will come to a screeching +halt. Yes, this is the voice of experience. Note also that videobuf may +wait on the first buffer in the queue; placing other buffers in front of it +could again gum up the works. So use list_add_tail() to enqueue buffers. + +Finally, buf_release() is called when a buffer is no longer intended to be +used. The driver should ensure that there is no I/O active on the buffer, +then pass it to the appropriate free routine(s): + + /* Scatter/gather drivers */ + int videobuf_dma_unmap(struct videobuf_queue *q, + struct videobuf_dmabuf *dma); + int videobuf_dma_free(struct videobuf_dmabuf *dma); + + /* vmalloc drivers */ + void videobuf_vmalloc_free (struct videobuf_buffer *buf); + + /* Contiguous drivers */ + void videobuf_dma_contig_free(struct videobuf_queue *q, + struct videobuf_buffer *buf); + +One way to ensure that a buffer is no longer under I/O is to pass it to: + + int videobuf_waiton(struct videobuf_buffer *vb, int non_blocking, int intr); + +Here, vb is the buffer, non_blocking indicates whether non-blocking I/O +should be used (it should be zero in the buf_release() case), and intr +controls whether an interruptible wait is used. + +File operations + +At this point, much of the work is done; much of the rest is slipping +videobuf calls into the implementation of the other driver callbacks. The +first step is in the open() function, which must initialize the +videobuf queue. The function to use depends on the type of buffer used: + + void videobuf_queue_sg_init(struct videobuf_queue *q, + struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv); + + void videobuf_queue_vmalloc_init(struct videobuf_queue *q, + struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv); + + void videobuf_queue_dma_contig_init(struct videobuf_queue *q, + struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv); + +In each case, the parameters are the same: q is the queue structure for the +device, ops is the set of callbacks as described above, dev is the device +structure for this video device, irqlock is an interrupt-safe spinlock to +protect access to the data structures, type is the buffer type used by the +device (cameras will use V4L2_BUF_TYPE_VIDEO_CAPTURE, for example), field +describes which field is being captured (often V4L2_FIELD_NONE for +progressive devices), msize is the size of any containing structure used +around struct videobuf_buffer, and priv is a private data pointer which +shows up in the priv_data field of struct videobuf_queue. Note that these +are void functions which, evidently, are immune to failure. + +V4L2 capture drivers can be written to support either of two APIs: the +read() system call and the rather more complicated streaming mechanism. As +a general rule, it is necessary to support both to ensure that all +applications have a chance of working with the device. Videobuf makes it +easy to do that with the same code. To implement read(), the driver need +only make a call to one of: + + ssize_t videobuf_read_one(struct videobuf_queue *q, + char __user *data, size_t count, + loff_t *ppos, int nonblocking); + + ssize_t videobuf_read_stream(struct videobuf_queue *q, + char __user *data, size_t count, + loff_t *ppos, int vbihack, int nonblocking); + +Either one of these functions will read frame data into data, returning the +amount actually read; the difference is that videobuf_read_one() will only +read a single frame, while videobuf_read_stream() will read multiple frames +if they are needed to satisfy the count requested by the application. A +typical driver read() implementation will start the capture engine, call +one of the above functions, then stop the engine before returning (though a +smarter implementation might leave the engine running for a little while in +anticipation of another read() call happening in the near future). + +The poll() function can usually be implemented with a direct call to: + + unsigned int videobuf_poll_stream(struct file *file, + struct videobuf_queue *q, + poll_table *wait); + +Note that the actual wait queue eventually used will be the one associated +with the first available buffer. + +When streaming I/O is done to kernel-space buffers, the driver must support +the mmap() system call to enable user space to access the data. In many +V4L2 drivers, the often-complex mmap() implementation simplifies to a +single call to: + + int videobuf_mmap_mapper(struct videobuf_queue *q, + struct vm_area_struct *vma); + +Everything else is handled by the videobuf code. + +The release() function requires two separate videobuf calls: + + void videobuf_stop(struct videobuf_queue *q); + int videobuf_mmap_free(struct videobuf_queue *q); + +The call to videobuf_stop() terminates any I/O in progress - though it is +still up to the driver to stop the capture engine. The call to +videobuf_mmap_free() will ensure that all buffers have been unmapped; if +so, they will all be passed to the buf_release() callback. If buffers +remain mapped, videobuf_mmap_free() returns an error code instead. The +purpose is clearly to cause the closing of the file descriptor to fail if +buffers are still mapped, but every driver in the 2.6.32 kernel cheerfully +ignores its return value. + +ioctl() operations + +The V4L2 API includes a very long list of driver callbacks to respond to +the many ioctl() commands made available to user space. A number of these +- those associated with streaming I/O - turn almost directly into videobuf +calls. The relevant helper functions are: + + int videobuf_reqbufs(struct videobuf_queue *q, + struct v4l2_requestbuffers *req); + int videobuf_querybuf(struct videobuf_queue *q, struct v4l2_buffer *b); + int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b); + int videobuf_dqbuf(struct videobuf_queue *q, struct v4l2_buffer *b, + int nonblocking); + int videobuf_streamon(struct videobuf_queue *q); + int videobuf_streamoff(struct videobuf_queue *q); + int videobuf_cgmbuf(struct videobuf_queue *q, struct video_mbuf *mbuf, + int count); + +So, for example, a VIDIOC_REQBUFS call turns into a call to the driver's +vidioc_reqbufs() callback which, in turn, usually only needs to locate the +proper struct videobuf_queue pointer and pass it to videobuf_reqbufs(). +These support functions can replace a great deal of buffer management +boilerplate in a lot of V4L2 drivers. + +The vidioc_streamon() and vidioc_streamoff() functions will be a bit more +complex, of course, since they will also need to deal with starting and +stopping the capture engine. videobuf_cgmbuf(), called from the driver's +vidiocgmbuf() function, only exists if the V4L1 compatibility module has +been selected with CONFIG_VIDEO_V4L1_COMPAT, so its use must be surrounded +with #ifdef directives. + +Buffer allocation + +Thus far, we have talked about buffers, but have not looked at how they are +allocated. The scatter/gather case is the most complex on this front. For +allocation, the driver can leave buffer allocation entirely up to the +videobuf layer; in this case, buffers will be allocated as anonymous +user-space pages and will be very scattered indeed. If the application is +using user-space buffers, no allocation is needed; the videobuf layer will +take care of calling get_user_pages() and filling in the scatterlist array. + +If the driver needs to do its own memory allocation, it should be done in +the vidioc_reqbufs() function, *after* calling videobuf_reqbufs(). The +first step is a call to: + + struct videobuf_dmabuf *videobuf_to_dma(struct videobuf_buffer *buf); + +The returned videobuf_dmabuf structure (defined in +) includes a couple of relevant fields: + + struct scatterlist *sglist; + int sglen; + +The driver must allocate an appropriately-sized scatterlist array and +populate it with pointers to the pieces of the allocated buffer; sglen +should be set to the length of the array. + +Drivers using the vmalloc() method need not (and cannot) concern themselves +with buffer allocation at all; videobuf will handle those details. The +same is normally true of contiguous-DMA drivers as well; videobuf will +allocate the buffers (with dma_alloc_coherent()) when it sees fit. That +means that these drivers may be trying to do high-order allocations at any +time, an operation which is not always guaranteed to work. Some drivers +play tricks by allocating DMA space at system boot time; videobuf does not +currently play well with those drivers. + +As of 2.6.31, contiguous-DMA drivers can work with a user-supplied buffer, +as long as that buffer is physically contiguous. Normal user-space +allocations will not meet that criterion, but buffers obtained from other +kernel drivers, or those contained within huge pages, will work with these +drivers. + +Filling the buffers + +The final part of a videobuf implementation has no direct callback - it's +the portion of the code which actually puts frame data into the buffers, +usually in response to interrupts from the device. For all types of +drivers, this process works approximately as follows: + + - Obtain the next available buffer and make sure that somebody is actually + waiting for it. + + - Get a pointer to the memory and put video data there. + + - Mark the buffer as done and wake up the process waiting for it. + +Step (1) above is done by looking at the driver-managed list_head structure +- the one which is filled in the buf_queue() callback. Because starting +the engine and enqueueing buffers are done in separate steps, it's possible +for the engine to be running without any buffers available - in the +vmalloc() case especially. So the driver should be prepared for the list +to be empty. It is equally possible that nobody is yet interested in the +buffer; the driver should not remove it from the list or fill it until a +process is waiting on it. That test can be done by examining the buffer's +done field (a wait_queue_head_t structure) with waitqueue_active(). + +A buffer's state should be set to VIDEOBUF_ACTIVE before being mapped for +DMA; that ensures that the videobuf layer will not try to do anything with +it while the device is transferring data. + +For scatter/gather drivers, the needed memory pointers will be found in the +scatterlist structure described above. Drivers using the vmalloc() method +can get a memory pointer with: + + void *videobuf_to_vmalloc(struct videobuf_buffer *buf); + +For contiguous DMA drivers, the function to use is: + + dma_addr_t videobuf_to_dma_contig(struct videobuf_buffer *buf); + +The contiguous DMA API goes out of its way to hide the kernel-space address +of the DMA buffer from drivers. + +The final step is to set the size field of the relevant videobuf_buffer +structure to the actual size of the captured image, set state to +VIDEOBUF_DONE, then call wake_up() on the done queue. At this point, the +buffer is owned by the videobuf layer and the driver should not touch it +again. + +Developers who are interested in more information can go into the relevant +header files; there are a few low-level functions declared there which have +not been talked about here. Also worthwhile is the vivi driver +(drivers/media/video/vivi.c), which is maintained as an example of how V4L2 +drivers should be written. Vivi only uses the vmalloc() API, but it's good +enough to get started with. Note also that all of these calls are exported +GPL-only, so they will not be available to non-GPL kernel modules. -- cgit v1.2.3-70-g09d2 From 7cae112ebe10e186c3bdae1f20865941717e37a2 Mon Sep 17 00:00:00 2001 From: Mauro Carvalho Chehab Date: Mon, 22 Feb 2010 18:55:00 -0300 Subject: V4L/DVB: V4L2 docs: replace spaces by tabs CC: Jonathan Corbet CC: Reviewed-by: Randy Dunlap Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/v4l2-framework.txt | 1 - Documentation/video4linux/videobuf | 44 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 23 deletions(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/v4l2-framework.txt b/Documentation/video4linux/v4l2-framework.txt index 90b0a08ea47..5155700c206 100644 --- a/Documentation/video4linux/v4l2-framework.txt +++ b/Documentation/video4linux/v4l2-framework.txt @@ -609,4 +609,3 @@ scatter/gather method (videobuf-dma-sg), DMA with linear access Please see Documentation/video4linux/videobuf for more information on how to use the videobuf layer. - diff --git a/Documentation/video4linux/videobuf b/Documentation/video4linux/videobuf index ba4ca991c55..17a1f9abf26 100644 --- a/Documentation/video4linux/videobuf +++ b/Documentation/video4linux/videobuf @@ -112,7 +112,7 @@ then pass it to the appropriate free routine(s): /* Scatter/gather drivers */ int videobuf_dma_unmap(struct videobuf_queue *q, - struct videobuf_dmabuf *dma); + struct videobuf_dmabuf *dma); int videobuf_dma_free(struct videobuf_dmabuf *dma); /* vmalloc drivers */ @@ -120,7 +120,7 @@ then pass it to the appropriate free routine(s): /* Contiguous drivers */ void videobuf_dma_contig_free(struct videobuf_queue *q, - struct videobuf_buffer *buf); + struct videobuf_buffer *buf); One way to ensure that a buffer is no longer under I/O is to pass it to: @@ -138,21 +138,21 @@ first step is in the open() function, which must initialize the videobuf queue. The function to use depends on the type of buffer used: void videobuf_queue_sg_init(struct videobuf_queue *q, - struct videobuf_queue_ops *ops, - struct device *dev, - spinlock_t *irqlock, - enum v4l2_buf_type type, - enum v4l2_field field, - unsigned int msize, - void *priv); + struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, + void *priv); void videobuf_queue_vmalloc_init(struct videobuf_queue *q, - struct videobuf_queue_ops *ops, - struct device *dev, - spinlock_t *irqlock, - enum v4l2_buf_type type, - enum v4l2_field field, - unsigned int msize, + struct videobuf_queue_ops *ops, + struct device *dev, + spinlock_t *irqlock, + enum v4l2_buf_type type, + enum v4l2_field field, + unsigned int msize, void *priv); void videobuf_queue_dma_contig_init(struct videobuf_queue *q, @@ -183,11 +183,11 @@ easy to do that with the same code. To implement read(), the driver need only make a call to one of: ssize_t videobuf_read_one(struct videobuf_queue *q, - char __user *data, size_t count, + char __user *data, size_t count, loff_t *ppos, int nonblocking); ssize_t videobuf_read_stream(struct videobuf_queue *q, - char __user *data, size_t count, + char __user *data, size_t count, loff_t *ppos, int vbihack, int nonblocking); Either one of these functions will read frame data into data, returning the @@ -240,15 +240,15 @@ the many ioctl() commands made available to user space. A number of these calls. The relevant helper functions are: int videobuf_reqbufs(struct videobuf_queue *q, - struct v4l2_requestbuffers *req); + struct v4l2_requestbuffers *req); int videobuf_querybuf(struct videobuf_queue *q, struct v4l2_buffer *b); int videobuf_qbuf(struct videobuf_queue *q, struct v4l2_buffer *b); - int videobuf_dqbuf(struct videobuf_queue *q, struct v4l2_buffer *b, - int nonblocking); + int videobuf_dqbuf(struct videobuf_queue *q, struct v4l2_buffer *b, + int nonblocking); int videobuf_streamon(struct videobuf_queue *q); int videobuf_streamoff(struct videobuf_queue *q); - int videobuf_cgmbuf(struct videobuf_queue *q, struct video_mbuf *mbuf, - int count); + int videobuf_cgmbuf(struct videobuf_queue *q, struct video_mbuf *mbuf, + int count); So, for example, a VIDIOC_REQBUFS call turns into a call to the driver's vidioc_reqbufs() callback which, in turn, usually only needs to locate the -- cgit v1.2.3-70-g09d2 From dbb9de9bc137e08dc47db960d5730e3251932e2b Mon Sep 17 00:00:00 2001 From: Andy Walls Date: Wed, 10 Feb 2010 19:02:58 -0300 Subject: V4L/DVB: tuner-types: Add Sony BTF-Pxn01Z tuner type used on GigaPocket cards Sony makes custome tuners for its GigaPocket line of ivtv based capture cards. This adds an entry to the tuner-types list for such tuners. Parameters are based on experiments by Eric Anderson . Signed-off-by: Andy Walls Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/CARDLIST.tuner | 1 + drivers/media/common/tuners/tuner-types.c | 21 +++++++++++++++++++++ include/media/tuner.h | 1 + 3 files changed, 23 insertions(+) (limited to 'Documentation') diff --git a/Documentation/video4linux/CARDLIST.tuner b/Documentation/video4linux/CARDLIST.tuner index e0d298fe883..9b2e0dd6017 100644 --- a/Documentation/video4linux/CARDLIST.tuner +++ b/Documentation/video4linux/CARDLIST.tuner @@ -81,3 +81,4 @@ tuner=80 - Philips FQ1216LME MK3 PAL/SECAM w/active loopthrough tuner=81 - Partsnic (Daewoo) PTI-5NF05 tuner=82 - Philips CU1216L tuner=83 - NXP TDA18271 +tuner=84 - Sony BTF-Pxn01Z diff --git a/drivers/media/common/tuners/tuner-types.c b/drivers/media/common/tuners/tuner-types.c index 2b876f3988c..d9aaaca620c 100644 --- a/drivers/media/common/tuners/tuner-types.c +++ b/drivers/media/common/tuners/tuner-types.c @@ -1337,6 +1337,22 @@ static struct tuner_params tuner_philips_cu1216l_params[] = { }, }; +/* ---------------------- TUNER_SONY_BTF_PXN01Z ------------------------ */ + +static struct tuner_range tuner_sony_btf_pxn01z_ranges[] = { + { 16 * 137.25 /*MHz*/, 0x8e, 0x01, }, + { 16 * 367.25 /*MHz*/, 0x8e, 0x02, }, + { 16 * 999.99 , 0x8e, 0x04, }, +}; + +static struct tuner_params tuner_sony_btf_pxn01z_params[] = { + { + .type = TUNER_PARAM_TYPE_NTSC, + .ranges = tuner_sony_btf_pxn01z_ranges, + .count = ARRAY_SIZE(tuner_sony_btf_pxn01z_ranges), + }, +}; + /* --------------------------------------------------------------------- */ struct tunertype tuners[] = { @@ -1805,6 +1821,11 @@ struct tunertype tuners[] = { .name = "NXP TDA18271", /* see tda18271-fe.c for details */ }, + [TUNER_SONY_BTF_PXN01Z] = { + .name = "Sony BTF-Pxn01Z", + .params = tuner_sony_btf_pxn01z_params, + .count = ARRAY_SIZE(tuner_sony_btf_pxn01z_params), + }, }; EXPORT_SYMBOL(tuners); diff --git a/include/media/tuner.h b/include/media/tuner.h index 4d5b53ff17d..5505c5360ca 100644 --- a/include/media/tuner.h +++ b/include/media/tuner.h @@ -129,6 +129,7 @@ #define TUNER_PARTSNIC_PTI_5NF05 81 #define TUNER_PHILIPS_CU1216L 82 #define TUNER_NXP_TDA18271 83 +#define TUNER_SONY_BTF_PXN01Z 84 /* tv card specific */ #define TDA9887_PRESENT (1<<0) -- cgit v1.2.3-70-g09d2 From 4f3a89e48a9975ab3f777ea15ac161267e4b8794 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Sun, 17 Jan 2010 10:45:13 -0300 Subject: V4L/DVB: gscpa Documentation: add cpia1 cameras gscpa Documentation: add cpia1 cameras Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/gspca.txt | 2 ++ 1 file changed, 2 insertions(+) (limited to 'Documentation') diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt index 9de9db03f9d..939dda99e7d 100644 --- a/Documentation/video4linux/gspca.txt +++ b/Documentation/video4linux/gspca.txt @@ -154,6 +154,7 @@ sunplus 0546:3191 Polaroid Ion 80 sunplus 0546:3273 Polaroid PDC2030 ov519 054c:0154 Sonny toy4 ov519 054c:0155 Sonny toy5 +cpia1 0553:0002 CPIA CPiA (version1) based cameras zc3xx 055f:c005 Mustek Wcam300A spca500 055f:c200 Mustek Gsmart 300 sunplus 055f:c211 Kowa Bs888e Microcamera @@ -206,6 +207,7 @@ sunplus 0733:2221 Mercury Digital Pro 3.1p sunplus 0733:3261 Concord 3045 spca536a sunplus 0733:3281 Cyberpix S550V spca506 0734:043b 3DeMon USB Capture aka +cpia1 0813:0001 QX3 camera ov519 0813:0002 Dual Mode USB Camera Plus spca500 084d:0003 D-Link DSC-350 spca500 08ca:0103 Aiptek PocketDV -- cgit v1.2.3-70-g09d2 From 606f8428ca1c2fbc665f726b4f810b8d50b87954 Mon Sep 17 00:00:00 2001 From: Hans de Goede Date: Wed, 10 Feb 2010 06:49:23 -0300 Subject: V4L/DVB: Documentation: gspca.txt: update known mr97310a cams Signed-off-by: Hans de Goede Signed-off-by: Mauro Carvalho Chehab --- Documentation/video4linux/gspca.txt | 4 ++-- drivers/media/video/gspca/sonixb.c | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/video4linux/gspca.txt b/Documentation/video4linux/gspca.txt index 939dda99e7d..181b9e6fd98 100644 --- a/Documentation/video4linux/gspca.txt +++ b/Documentation/video4linux/gspca.txt @@ -229,8 +229,8 @@ sunplus 08ca:2050 Medion MD 41437 sunplus 08ca:2060 Aiptek PocketDV5300 tv8532 0923:010f ICM532 cams mars 093a:050f Mars-Semi Pc-Camera -mr97310a 093a:010e All four known CIF cams with this ID -mr97310a 093a:010f All four known VGA cams with this ID +mr97310a 093a:010e All known CIF cams with this ID +mr97310a 093a:010f All known VGA cams with this ID pac207 093a:2460 Qtec Webcam 100 pac207 093a:2461 HP Webcam pac207 093a:2463 Philips SPC 220 NC diff --git a/drivers/media/video/gspca/sonixb.c b/drivers/media/video/gspca/sonixb.c index fda14200a05..81a7dd6a724 100644 --- a/drivers/media/video/gspca/sonixb.c +++ b/drivers/media/video/gspca/sonixb.c @@ -240,7 +240,7 @@ static const struct ctrl sd_ctrls[] = { .minimum = 0, .maximum = 2, /* 0: 0, 1: 50Hz, 2:60Hz */ .step = 1, -#define FREQ_DEF 1 +#define FREQ_DEF 0 .default_value = FREQ_DEF, }, .set = sd_setfreq, @@ -944,7 +944,7 @@ static void do_autogain(struct gspca_dev *gspca_dev) desired_avg_lum = 5000; } else { deadzone = 1500; - desired_avg_lum = 23000; + desired_avg_lum = 18000; } if (sensor_data[sd->sensor].flags & F_COARSE_EXPO) -- cgit v1.2.3-70-g09d2 From 971cb7fba0c84dd74c39020f4596b08398f58199 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:03:22 +0100 Subject: PM: Document device power attributes in sysfs There are sysfs attributes in /sys/devices/.../power/ that haven't been documented yet in Documentation/ABI/. Document them as appropriate. Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-devices-power | 53 +++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 Documentation/ABI/testing/sysfs-devices-power (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power new file mode 100644 index 00000000000..431bfd7e65c --- /dev/null +++ b/Documentation/ABI/testing/sysfs-devices-power @@ -0,0 +1,53 @@ +What: /sys/devices/.../power/ +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../power directory contains attributes + allowing the user space to check and modify some power + management related properties of given device. + +What: /sys/devices/.../power/wakeup +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../power/wakeup attribute allows the user + space to check if the device is enabled to wake up the system + from sleep states, such as the memory sleep state (suspend to + RAM) and hibernation (suspend to disk), and to enable or disable + it to do that as desired. + + Some devices support "wakeup" events, which are hardware signals + used to activate the system from a sleep state. Such devices + have one of the following two values for the sysfs power/wakeup + file: + + + "enabled\n" to issue the events; + + "disabled\n" not to do so; + + In that cases the user space can change the setting represented + by the contents of this file by writing either "enabled", or + "disabled" to it. + + For the devices that are not capable of generating system wakeup + events this file contains "\n". In that cases the user space + cannot modify the contents of this file and the device cannot be + enabled to wake up the system. + +What: /sys/devices/.../power/control +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../power/control attribute allows the user + space to control the run-time power management of the device. + + All devices have one of the following two values for the + power/control file: + + + "auto\n" to allow the device to be power managed at run time; + + "on\n" to prevent the device from being power managed; + + The default for all devices is "auto", which means that they may + be subject to automatic power management, depending on their + drivers. Changing this attribute to "on" prevents the driver + from power managing the device at run time. Doing that while + the device is suspended causes it to be woken up. -- cgit v1.2.3-70-g09d2 From 0e06b4a891c6a108412fe24b4500f499da2cf8a1 Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:25:15 +0100 Subject: PM: Add a switch for disabling/enabling asynchronous suspend/resume Add sysfs attribute /sys/power/pm_async allowing the user space to disable/enable asynchronous suspend/resume of devices. Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-power | 13 +++++++++++++ drivers/base/power/main.c | 7 ++++--- drivers/base/power/power.h | 6 +++--- kernel/power/main.c | 31 ++++++++++++++++++++++++++++++- 4 files changed, 50 insertions(+), 7 deletions(-) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-power b/Documentation/ABI/testing/sysfs-power index dcff4d0623a..d6a801f45b4 100644 --- a/Documentation/ABI/testing/sysfs-power +++ b/Documentation/ABI/testing/sysfs-power @@ -101,3 +101,16 @@ Description: CAUTION: Using it will cause your machine's real-time (CMOS) clock to be set to a random invalid time after a resume. + +What: /sys/power/pm_async +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/power/pm_async file controls the switch allowing the + user space to enable or disable asynchronous suspend and resume + of devices. If enabled, this feature will cause some device + drivers' suspend and resume callbacks to be executed in parallel + with each other and with the main suspend thread. It is enabled + if this file contains "1", which is the default. It may be + disabled by writing "0" to this file, in which case all devices + will be suspended and resumed synchronously. diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 3b44c201dda..7e79201b09b 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -201,7 +201,7 @@ static void dpm_wait(struct device *dev, bool async) if (!dev) return; - if (async || dev->power.async_suspend) + if (async || (pm_async_enabled && dev->power.async_suspend)) wait_for_completion(&dev->power.completion); } @@ -563,7 +563,8 @@ static int device_resume(struct device *dev) { INIT_COMPLETION(dev->power.completion); - if (dev->power.async_suspend && !pm_trace_is_enabled()) { + if (pm_async_enabled && dev->power.async_suspend + && !pm_trace_is_enabled()) { get_device(dev); async_schedule(async_resume, dev); return 0; @@ -867,7 +868,7 @@ static int device_suspend(struct device *dev) { INIT_COMPLETION(dev->power.completion); - if (dev->power.async_suspend) { + if (pm_async_enabled && dev->power.async_suspend) { get_device(dev); async_schedule(async_suspend, dev); return 0; diff --git a/drivers/base/power/power.h b/drivers/base/power/power.h index b8fa1aa5225..c0bd03c83b9 100644 --- a/drivers/base/power/power.h +++ b/drivers/base/power/power.h @@ -12,10 +12,10 @@ static inline void pm_runtime_remove(struct device *dev) {} #ifdef CONFIG_PM_SLEEP -/* - * main.c - */ +/* kernel/power/main.c */ +extern int pm_async_enabled; +/* drivers/base/power/main.c */ extern struct list_head dpm_list; /* The active device list */ static inline struct device *to_device(struct list_head *entry) diff --git a/kernel/power/main.c b/kernel/power/main.c index 0998c713905..b58800b21fc 100644 --- a/kernel/power/main.c +++ b/kernel/power/main.c @@ -44,6 +44,32 @@ int pm_notifier_call_chain(unsigned long val) == NOTIFY_BAD) ? -EINVAL : 0; } +/* If set, devices may be suspended and resumed asynchronously. */ +int pm_async_enabled = 1; + +static ssize_t pm_async_show(struct kobject *kobj, struct kobj_attribute *attr, + char *buf) +{ + return sprintf(buf, "%d\n", pm_async_enabled); +} + +static ssize_t pm_async_store(struct kobject *kobj, struct kobj_attribute *attr, + const char *buf, size_t n) +{ + unsigned long val; + + if (strict_strtoul(buf, 10, &val)) + return -EINVAL; + + if (val > 1) + return -EINVAL; + + pm_async_enabled = val; + return n; +} + +power_attr(pm_async); + #ifdef CONFIG_PM_DEBUG int pm_test_level = TEST_NONE; @@ -208,8 +234,11 @@ static struct attribute * g[] = { #ifdef CONFIG_PM_TRACE &pm_trace_attr.attr, #endif -#if defined(CONFIG_PM_SLEEP) && defined(CONFIG_PM_DEBUG) +#ifdef CONFIG_PM_SLEEP + &pm_async_attr.attr, +#ifdef CONFIG_PM_DEBUG &pm_test_attr.attr, +#endif #endif NULL, }; -- cgit v1.2.3-70-g09d2 From 5a2eb8585f3b38e01e30aacaa8b985a1520a993d Mon Sep 17 00:00:00 2001 From: "Rafael J. Wysocki" Date: Sat, 23 Jan 2010 22:25:23 +0100 Subject: PM: Add facility for advanced testing of async suspend/resume Add configuration switch CONFIG_PM_ADVANCED_DEBUG for compiling in extra PM debugging/testing code allowing one to access some PM-related attributes of devices from the user space via sysfs. If CONFIG_PM_ADVANCED_DEBUG is set, add sysfs attribute power/async for every device allowing the user space to access the device's power.async_suspend flag and modify it, if desired. Signed-off-by: Rafael J. Wysocki --- Documentation/ABI/testing/sysfs-devices-power | 26 ++++++++++++++ drivers/base/power/sysfs.c | 49 +++++++++++++++++++++++++++ include/linux/device.h | 11 ++++++ kernel/power/Kconfig | 14 ++++++++ 4 files changed, 100 insertions(+) (limited to 'Documentation') diff --git a/Documentation/ABI/testing/sysfs-devices-power b/Documentation/ABI/testing/sysfs-devices-power index 431bfd7e65c..6123c523bfd 100644 --- a/Documentation/ABI/testing/sysfs-devices-power +++ b/Documentation/ABI/testing/sysfs-devices-power @@ -51,3 +51,29 @@ Description: drivers. Changing this attribute to "on" prevents the driver from power managing the device at run time. Doing that while the device is suspended causes it to be woken up. + +What: /sys/devices/.../power/async +Date: January 2009 +Contact: Rafael J. Wysocki +Description: + The /sys/devices/.../async attribute allows the user space to + enable or diasble the device's suspend and resume callbacks to + be executed asynchronously (ie. in separate threads, in parallel + with the main suspend/resume thread) during system-wide power + transitions (eg. suspend to RAM, hibernation). + + All devices have one of the following two values for the + power/async file: + + + "enabled\n" to permit the asynchronous suspend/resume; + + "disabled\n" to forbid it; + + The value of this attribute may be changed by writing either + "enabled", or "disabled" to it. + + It generally is unsafe to permit the asynchronous suspend/resume + of a device unless it is certain that all of the PM dependencies + of the device are known to the PM core. However, for some + devices this attribute is set to "enabled" by bus type code or + device drivers and in that cases it should be safe to leave the + default value. diff --git a/drivers/base/power/sysfs.c b/drivers/base/power/sysfs.c index c011ff15632..86fd9373447 100644 --- a/drivers/base/power/sysfs.c +++ b/drivers/base/power/sysfs.c @@ -54,6 +54,24 @@ * wakeup events internally (unless they are disabled), keeping * their hardware in low power modes whenever they're unused. This * saves runtime power, without requiring system-wide sleep states. + * + * async - Report/change current async suspend setting for the device + * + * Asynchronous suspend and resume of the device during system-wide power + * state transitions can be enabled by writing "enabled" to this file. + * Analogously, if "disabled" is written to this file, the device will be + * suspended and resumed synchronously. + * + * All devices have one of the following two values for power/async: + * + * + "enabled\n" to permit the asynchronous suspend/resume of the device; + * + "disabled\n" to forbid it; + * + * NOTE: It generally is unsafe to permit the asynchronous suspend/resume + * of a device unless it is certain that all of the PM dependencies of the + * device are known to the PM core. However, for some devices this + * attribute is set to "enabled" by bus type code or device drivers and in + * that cases it should be safe to leave the default value. */ static const char enabled[] = "enabled"; @@ -125,12 +143,43 @@ wake_store(struct device * dev, struct device_attribute *attr, static DEVICE_ATTR(wakeup, 0644, wake_show, wake_store); +#ifdef CONFIG_PM_SLEEP_ADVANCED_DEBUG +static ssize_t async_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + return sprintf(buf, "%s\n", + device_async_suspend_enabled(dev) ? enabled : disabled); +} + +static ssize_t async_store(struct device *dev, struct device_attribute *attr, + const char *buf, size_t n) +{ + char *cp; + int len = n; + + cp = memchr(buf, '\n', n); + if (cp) + len = cp - buf; + if (len == sizeof enabled - 1 && strncmp(buf, enabled, len) == 0) + device_enable_async_suspend(dev); + else if (len == sizeof disabled - 1 && strncmp(buf, disabled, len) == 0) + device_disable_async_suspend(dev); + else + return -EINVAL; + return n; +} + +static DEVICE_ATTR(async, 0644, async_show, async_store); +#endif /* CONFIG_PM_SLEEP_ADVANCED_DEBUG */ static struct attribute * power_attrs[] = { #ifdef CONFIG_PM_RUNTIME &dev_attr_control.attr, #endif &dev_attr_wakeup.attr, +#ifdef CONFIG_PM_SLEEP_ADVANCED_DEBUG + &dev_attr_async.attr, +#endif NULL, }; static struct attribute_group pm_attr_group = { diff --git a/include/linux/device.h b/include/linux/device.h index 70adc5f3f50..b30527db3ac 100644 --- a/include/linux/device.h +++ b/include/linux/device.h @@ -478,6 +478,17 @@ static inline void device_enable_async_suspend(struct device *dev) dev->power.async_suspend = true; } +static inline void device_disable_async_suspend(struct device *dev) +{ + if (dev->power.status == DPM_ON) + dev->power.async_suspend = false; +} + +static inline bool device_async_suspend_enabled(struct device *dev) +{ + return !!dev->power.async_suspend; +} + void driver_init(void); /* diff --git a/kernel/power/Kconfig b/kernel/power/Kconfig index 4c9cffcf69c..5c36ea9d55d 100644 --- a/kernel/power/Kconfig +++ b/kernel/power/Kconfig @@ -27,6 +27,15 @@ config PM_DEBUG code. This is helpful when debugging and reporting PM bugs, like suspend support. +config PM_ADVANCED_DEBUG + bool "Extra PM attributes in sysfs for low-level debugging/testing" + depends on PM_DEBUG + default n + ---help--- + Add extra sysfs attributes allowing one to access some Power Management + fields of device objects from user space. If you are not a kernel + developer interested in debugging/testing Power Management, say "no". + config PM_VERBOSE bool "Verbose Power Management debugging" depends on PM_DEBUG @@ -85,6 +94,11 @@ config PM_SLEEP depends on SUSPEND || HIBERNATION || XEN_SAVE_RESTORE default y +config PM_SLEEP_ADVANCED_DEBUG + bool + depends on PM_ADVANCED_DEBUG + default n + config SUSPEND bool "Suspend to RAM and standby" depends on PM && ARCH_SUSPEND_POSSIBLE -- cgit v1.2.3-70-g09d2 From b694e52ebdd439e4591d4a6651658026196174f6 Mon Sep 17 00:00:00 2001 From: Jiri Slaby Date: Wed, 27 Jan 2010 23:47:50 +0100 Subject: PM / Hibernate: Really deprecate deprecated user ioctls They were deprecated and removed from exported headers more than 2 years ago. Inform users about their removal in the future now. (Switch cases needed to be reorderded for an easy fall through.) And add an entry to feature-removal-schedule. Signed-off-by: Jiri Slaby Acked-by: Pavel Machek Signed-off-by: Rafael J. Wysocki --- Documentation/feature-removal-schedule.txt | 11 +++++++++++ kernel/power/user.c | 23 +++++++++++++++++++---- 2 files changed, 30 insertions(+), 4 deletions(-) (limited to 'Documentation') diff --git a/Documentation/feature-removal-schedule.txt b/Documentation/feature-removal-schedule.txt index 0a46833c1b7..b9eba900e0f 100644 --- a/Documentation/feature-removal-schedule.txt +++ b/Documentation/feature-removal-schedule.txt @@ -64,6 +64,17 @@ Who: Robin Getz & Matt Mackall --------------------------- +What: Deprecated snapshot ioctls +When: 2.6.36 + +Why: The ioctls in kernel/power/user.c were marked as deprecated long time + ago. Now they notify users about that so that they need to replace + their userspace. After some more time, remove them completely. + +Who: Jiri Slaby + +--------------------------- + What: The ieee80211_regdom module parameter When: March 2010 / desktop catchup diff --git a/kernel/power/user.c b/kernel/power/user.c index bf0014d6a5f..4d2289626a8 100644 --- a/kernel/power/user.c +++ b/kernel/power/user.c @@ -195,6 +195,15 @@ static ssize_t snapshot_write(struct file *filp, const char __user *buf, return res; } +static void snapshot_deprecated_ioctl(unsigned int cmd) +{ + if (printk_ratelimit()) + printk(KERN_NOTICE "%pf: ioctl '%.8x' is deprecated and will " + "be removed soon, update your suspend-to-disk " + "utilities\n", + __builtin_return_address(0), cmd); +} + static long snapshot_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) { @@ -246,8 +255,9 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, data->frozen = 0; break; - case SNAPSHOT_CREATE_IMAGE: case SNAPSHOT_ATOMIC_SNAPSHOT: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_CREATE_IMAGE: if (data->mode != O_RDONLY || !data->frozen || data->ready) { error = -EPERM; break; @@ -275,8 +285,9 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, data->ready = 0; break; - case SNAPSHOT_PREF_IMAGE_SIZE: case SNAPSHOT_SET_IMAGE_SIZE: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_PREF_IMAGE_SIZE: image_size = arg; break; @@ -290,15 +301,17 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, error = put_user(size, (loff_t __user *)arg); break; - case SNAPSHOT_AVAIL_SWAP_SIZE: case SNAPSHOT_AVAIL_SWAP: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_AVAIL_SWAP_SIZE: size = count_swap_pages(data->swap, 1); size <<= PAGE_SHIFT; error = put_user(size, (loff_t __user *)arg); break; - case SNAPSHOT_ALLOC_SWAP_PAGE: case SNAPSHOT_GET_SWAP_PAGE: + snapshot_deprecated_ioctl(cmd); + case SNAPSHOT_ALLOC_SWAP_PAGE: if (data->swap < 0 || data->swap >= MAX_SWAPFILES) { error = -ENODEV; break; @@ -321,6 +334,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, break; case SNAPSHOT_SET_SWAP_FILE: /* This ioctl is deprecated */ + snapshot_deprecated_ioctl(cmd); if (!swsusp_swap_in_use()) { /* * User space encodes device types as two-byte values, @@ -362,6 +376,7 @@ static long snapshot_ioctl(struct file *filp, unsigned int cmd, break; case SNAPSHOT_PMOPS: /* This ioctl is deprecated */ + snapshot_deprecated_ioctl(cmd); error = -EINVAL; switch (arg) { -- cgit v1.2.3-70-g09d2 From 6f5d09a0e9731a39a4d52a5902daec72c1e43692 Mon Sep 17 00:00:00 2001 From: Ursula Braun Date: Fri, 26 Feb 2010 22:37:23 +0100 Subject: [S390] adapt text to cu3088-removal cu3088 layer for lcs and ctcm has been removed. Thus the reference to cu3088 in this text is to be removed as well. Signed-off-by: Ursula Braun Signed-off-by: Martin Schwidefsky --- Documentation/s390/driver-model.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'Documentation') diff --git a/Documentation/s390/driver-model.txt b/Documentation/s390/driver-model.txt index bde473df748..ed265cf54cd 100644 --- a/Documentation/s390/driver-model.txt +++ b/Documentation/s390/driver-model.txt @@ -223,8 +223,8 @@ touched by the driver - it should use the ccwgroup device's driver_data for its private data. To implement a ccwgroup driver, please refer to include/asm/ccwgroup.h. Keep in -mind that most drivers will need to implement both a ccwgroup and a ccw driver -(unless you have a meta ccw driver, like cu3088 for lcs and ctc). +mind that most drivers will need to implement both a ccwgroup and a ccw +driver. 2. Channel paths -- cgit v1.2.3-70-g09d2 From 879acca58a904c25487c89ab11e23eb556fb13d3 Mon Sep 17 00:00:00 2001 From: Sebastian Ott Date: Fri, 26 Feb 2010 22:37:25 +0100 Subject: [S390] cio: introduce cio_settle This patch introduces a proc file cio_settle. A write request to this file is blocked until all queued cio actions are handled. This will allow userspace to wait for pending work affecting device availability after changing cio_ignore or the hardware configuration. Signed-off-by: Sebastian Ott Signed-off-by: Martin Schwidefsky --- Documentation/s390/CommonIO | 6 ++++++ drivers/s390/cio/css.c | 47 ++++++++++++++++++++++++++++++++++++++++----- 2 files changed, 48 insertions(+), 5 deletions(-) (limited to 'Documentation') diff --git a/Documentation/s390/CommonIO b/Documentation/s390/CommonIO index 339207d11d9..d378cba6645 100644 --- a/Documentation/s390/CommonIO +++ b/Documentation/s390/CommonIO @@ -87,6 +87,12 @@ Command line parameters compatibility, by the device number in hexadecimal (0xabcd or abcd). Device numbers given as 0xabcd will be interpreted as 0.0.abcd. +* /proc/cio_settle + + A write request to this file is blocked until all queued cio actions are + handled. This will allow userspace to wait for pending work affecting + device availability after changing cio_ignore or the hardware configuration. + * For some of the information present in the /proc filesystem in 2.4 (namely, /proc/subchannels and /proc/chpids), see driver-model.txt. Information formerly in /proc/irq_count is now in /proc/interrupts. diff --git a/drivers/s390/cio/css.c b/drivers/s390/cio/css.c index 99fcf9d0ea1..366affcd9bb 100644 --- a/drivers/s390/cio/css.c +++ b/drivers/s390/cio/css.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include @@ -1019,6 +1020,18 @@ static int css_settle(struct device_driver *drv, void *unused) return 0; } +static inline void css_complete_work(void) +{ + int ret; + + /* Wait for the evaluation of subchannels to finish. */ + wait_event(css_eval_wq, atomic_read(&css_eval_scheduled) == 0); + flush_workqueue(cio_work_q); + /* Wait for the subchannel type specific initialization to finish */ + ret = bus_for_each_drv(&css_bus_type, NULL, NULL, css_settle); +} + + /* * Wait for the initialization of devices to finish, to make sure we are * done with our setup if the search for the root device starts. @@ -1027,14 +1040,38 @@ static int __init channel_subsystem_init_sync(void) { /* Start initial subchannel evaluation. */ css_schedule_eval_all(); - /* Wait for the evaluation of subchannels to finish. */ - wait_event(css_eval_wq, atomic_read(&css_eval_scheduled) == 0); - flush_workqueue(cio_work_q); - /* Wait for the subchannel type specific initialization to finish */ - return bus_for_each_drv(&css_bus_type, NULL, NULL, css_settle); + css_complete_work(); + return 0; } subsys_initcall_sync(channel_subsystem_init_sync); +#ifdef CONFIG_PROC_FS +static ssize_t cio_settle_write(struct file *file, const char __user *buf, + size_t count, loff_t *ppos) +{ + /* Handle pending CRW's. */ + crw_wait_for_channel_report(); + css_complete_work(); + return count; +} + +static const struct file_operations cio_settle_proc_fops = { + .write = cio_settle_write, +}; + +static int __init cio_settle_init(void) +{ + struct proc_dir_entry *entry; + + entry = proc_create("cio_settle", S_IWUSR, NULL, + &cio_settle_proc_fops); + if (!entry) + return -ENOMEM; + return 0; +} +device_initcall(cio_settle_init); +#endif /*CONFIG_PROC_FS*/ + int sch_is_pseudo_sch(struct subchannel *sch) { return sch == to_css(sch->dev.parent)->pseudo_subchannel; -- cgit v1.2.3-70-g09d2