On Thu, 2010-04-01 at 14:56 -0300, Mauro Carvalho Chehab wrote:
> Adds a method to pass IR raw pulse/code events into ir-core. This is
> needed in order to support LIRC. It also helps to move common code
> from the drivers into the core.
> 
> In order to allow testing, it implements a simple NEC protocol decoder
> at ir-nec-decoder.c file. The logic is about the same used at saa7134
> driver that handles Avermedia M135A and Encore FM53 boards.
> 
> Signed-off-by: Mauro Carvalho Chehab <mche...@redhat.com>
> 
>  create mode 100644 drivers/media/IR/ir-nec-decoder.c
>  create mode 100644 drivers/media/IR/ir-raw-event.c

Hi Mauro,

I haven't taken a very hard look at this since I'm very busy this month.

It looks OK so far.  I do have some comments....


> diff --git a/drivers/media/IR/Makefile b/drivers/media/IR/Makefile
> index 171890e..18794c7 100644
> --- a/drivers/media/IR/Makefile
> +++ b/drivers/media/IR/Makefile
> @@ -1,5 +1,5 @@
>  ir-common-objs  := ir-functions.o ir-keymaps.o
> -ir-core-objs := ir-keytable.o ir-sysfs.o
> +ir-core-objs := ir-keytable.o ir-sysfs.o ir-raw-event.o ir-nec-decoder.o
>  
>  obj-$(CONFIG_IR_CORE) += ir-core.o
>  obj-$(CONFIG_VIDEO_IR) += ir-common.o
> diff --git a/drivers/media/IR/ir-nec-decoder.c 
> b/drivers/media/IR/ir-nec-decoder.c
> new file mode 100644
> index 0000000..16360eb
> --- /dev/null
> +++ b/drivers/media/IR/ir-nec-decoder.c
> @@ -0,0 +1,131 @@
> +/* ir-raw-event.c - handle IR Pulse/Space event
> + *
> + * Copyright (C) 2010 by Mauro Carvalho Chehab <mche...@redhat.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + */
> +
> +#include <media/ir-core.h>
> +
> +/* Start time: 4.5 ms  */
> +#define MIN_START_TIME       3900000
> +#define MAX_START_TIME       5100000

Hmmm.

An NEC header pulse is nominally        16 * 560 us = 8.96 ms
An NEC header space is nominally         8 * 560 us = 4.48 ms
An NEC repeat header space is nominally  4 * 560 us = 2.24 ms

I think you need a more explicit name than {MIN,MAX}_START_TIME.


> +/* Pulse time: 560 us  */
> +#define MIN_PULSE_TIME       460000
> +#define MAX_PULSE_TIME       660000
> +
> +/* Bit 1 space time: 2.25ms-560 us */
> +#define MIN_BIT1_TIME        1490000
> +#define MAX_BIT1_TIME        1890000
> +
> +/* Bit 0 space time: 1.12ms-560 us */
> +#define MIN_BIT0_TIME        360000
> +#define MAX_BIT0_TIME        760000
> +

The fundamental unit of time in the NEC protocol is ideally:

        4192/197 cycles / 38 kHz = 559978.6 ns ~= 560 ns

All other time durations in the NEC protocol are multiples of this unit.
See:

        http://linuxtv.org/hg/~awalls/cx23885-ir2/rev/2cfef53b95a2#l1.96

If you define the your above constants in terms of that time unit, it
makes the tolerances you added in explicitly visible when reading the
source.


> +/** Decode NEC pulsecode. This code can take up to 76.5 ms to run.
> +     Unfortunately, using IRQ to decode pulse didn't work, since it uses
> +     a pulse train of 38KHz. This means one pulse on each 52 us
> +*/
> +
> +int ir_nec_decode(struct input_dev *input_dev,
> +               struct ir_raw_event *evs,
> +               int len)
> +{
> +     int i, count = -1;
> +     int ircode = 0, not_code = 0;
> +#if 0
> +     /* Needed only after porting the event code to the decoder */
> +     struct ir_input_dev *ir = input_get_drvdata(input_dev);
> +#endif
> +
> +     /* Be sure that the first event is an start one and is a pulse */
> +     for (i = 0; i < len; i++) {
> +             if (evs[i].type & (IR_START_EVENT | IR_PULSE))
> +                     break;
> +     }
> +     i++;    /* First event doesn't contain data */
> +
> +     if (i >= len)
> +             return 0;
> +
> +     /* First space should have 4.5 ms otherwise is not NEC protocol */
> +     if ((evs[i].delta.tv_nsec < MIN_START_TIME) |
> +         (evs[i].delta.tv_nsec > MAX_START_TIME) |
> +         (evs[i].type != IR_SPACE))
> +             goto err;
> +
> +     /*
> +      * FIXME: need to implement the repeat sequence
> +      */

I have an NEC protocol decoder here:

        http://linuxtv.org/hg/~awalls/cx23885-ir2/rev/2cfef53b95a2

If you would find it useful, please feel free to borrow ideas or parts
of the code to implement any features you are missing.  (That code works
by converting a mark-space pair to an "nec_symbol", and then taking
action based on the symbol.)

I suspect you will want to implement the repeat sequence.  It is hard
not to get a repeat sequence from a remote.

NEC ideally sends a repeat at intervals of:

        4192 cycles * 38 kHz = 110.316 ms




> +     count = 0;
> +     for (i++; i < len; i++) {
> +             int bit;
> +
> +             if ((evs[i].delta.tv_nsec < MIN_PULSE_TIME) |
> +                 (evs[i].delta.tv_nsec > MAX_PULSE_TIME) |
> +                 (evs[i].type != IR_PULSE))
> +                     goto err;
> +
> +             if (++i >= len)
> +                     goto err;
> +             if (evs[i].type != IR_SPACE)
> +                     goto err;
> +
> +             if ((evs[i].delta.tv_nsec > MIN_BIT1_TIME) &&
> +                 (evs[i].delta.tv_nsec < MAX_BIT1_TIME))
> +                     bit = 1;
> +             else if ((evs[i].delta.tv_nsec > MIN_BIT0_TIME) &&
> +                      (evs[i].delta.tv_nsec < MAX_BIT0_TIME))
> +                     bit = 0;
> +             else
> +                     goto err;
> +
> +             if (bit) {
> +                     int shift = count;
> +                     /* Address first, then command */
> +                     if (shift < 8) {
> +                             shift += 8;
> +                             ircode |= 1 << shift;
> +                     } else if (shift < 16) {
> +                             not_code |= 1 << shift;
> +                     } else if (shift < 24) {
> +                             shift -= 16;
> +                             ircode |= 1 << shift;
> +                     } else {
> +                             shift -= 24;
> +                             not_code |= 1 << shift;
> +                     }
> +             }
> +             if (++count == 32)
> +                     break;
> +     }
> +
> +     /*
> +      * Fixme: may need to accept Extended NEC protocol?
> +      */

Both of the NEC remotes that I own use the extended protocol, IIRC.



> +     if ((ircode & ~not_code) != ircode) {
> +             IR_dprintk(1, "NEC checksum error: code 0x%04x, not-code 
> 0x%04x\n",
> +                        ircode, not_code);
> +             return -EINVAL;
> +     }
> +
> +     IR_dprintk(1, "NEC scancode 0x%04x\n", ircode);
> +
> +     return ircode;
> +err:
> +     IR_dprintk(1, "NEC decoded failed at bit %d while decoding %luus 
> time\n",
> +                count, (evs[i].delta.tv_nsec + 500) / 1000);
> +
> +     return -EINVAL;
> +}
> +EXPORT_SYMBOL_GPL(ir_nec_decode);
> diff --git a/drivers/media/IR/ir-raw-event.c b/drivers/media/IR/ir-raw-event.c
> new file mode 100644
> index 0000000..9c71ac8
> --- /dev/null
> +++ b/drivers/media/IR/ir-raw-event.c
> @@ -0,0 +1,117 @@
> +/* ir-raw-event.c - handle IR Pulse/Space event
> + *
> + * Copyright (C) 2010 by Mauro Carvalho Chehab <mche...@redhat.com>
> + *
> + * This program is free software; you can redistribute it and/or modify
> + *  it under the terms of the GNU General Public License as published by
> + *  the Free Software Foundation version 2 of the License.
> + *
> + *  This program is distributed in the hope that it will be useful,
> + *  but WITHOUT ANY WARRANTY; without even the implied warranty of
> + *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + *  GNU General Public License for more details.
> + */
> +
> +#include <media/ir-core.h>
> +
> +/* Define the max number of bit transitions per IR keycode */
> +#define MAX_IR_EVENT_SIZE    256
> +
> +int ir_raw_event_register(struct input_dev *input_dev)
> +{
> +     struct ir_input_dev *ir = input_get_drvdata(input_dev);
> +     int rc, size;
> +
> +     ir->raw = kzalloc(sizeof(*ir->raw), GFP_KERNEL);
> +
> +     size = sizeof(struct ir_raw_event) * MAX_IR_EVENT_SIZE * 2;
> +     size = roundup_pow_of_two(size);
> +
> +     rc = kfifo_alloc(&ir->raw->kfifo, size, GFP_KERNEL);
> +
> +     return rc;
> +}
> +EXPORT_SYMBOL_GPL(ir_raw_event_register);
> +
> +void ir_raw_event_unregister(struct input_dev *input_dev)
> +{
> +     struct ir_input_dev *ir = input_get_drvdata(input_dev);
> +
> +     if (!ir->raw)
> +             return;
> +
> +     kfifo_free(&ir->raw->kfifo);
> +     kfree(ir->raw);
> +     ir->raw = NULL;
> +}
> +EXPORT_SYMBOL_GPL(ir_raw_event_unregister);
> +
> +int ir_raw_event_store(struct input_dev *input_dev, enum raw_event_type type)
> +{
> +     struct ir_input_dev     *ir = input_get_drvdata(input_dev);
> +     struct timespec         ts;
> +     struct ir_raw_event     event;
> +     int                     rc;
> +
> +     if (!ir->raw)
> +             return -EINVAL;
> +
> +     event.type = type;
> +     event.delta.tv_sec = 0;
> +     event.delta.tv_nsec = 0;
> +
> +     ktime_get_ts(&ts);
> +
> +     if (timespec_equal(&ir->raw->last_event, &event.delta))
> +             event.type |= IR_START_EVENT;
> +     else
> +             event.delta = timespec_sub(ts, ir->raw->last_event);
> +
> +     memcpy(&ir->raw->last_event, &ts, sizeof(ts));
> +
> +     if (event.delta.tv_sec) {
> +             event.type |= IR_START_EVENT;
> +             event.delta.tv_sec = 0;
> +             event.delta.tv_nsec = 0;
> +     }
> +
> +     kfifo_in(&ir->raw->kfifo, &event, sizeof(event));
> +
> +     return rc;
> +}
> +EXPORT_SYMBOL_GPL(ir_raw_event_store);
> +
> +int ir_raw_event_handle(struct input_dev *input_dev)
> +{
> +     struct ir_input_dev             *ir = input_get_drvdata(input_dev);
> +     int                             rc;
> +     struct ir_raw_event             *evs;
> +     int                             len, i;
> +
> +     /*
> +      * Store the events into a temporary buffer. This allows calling more 
> than
> +      * one decoder to deal with the received data
> +      */
> +     len = kfifo_len(&ir->raw->kfifo) / sizeof(*evs);
> +     if (!len)
> +             return 0;
> +     evs = kmalloc(len * sizeof(*evs), GFP_ATOMIC);
> +
> +     for (i = 0; i < len; i++) {
> +             rc = kfifo_out(&ir->raw->kfifo, &evs[i], sizeof(*evs));
> +             if (rc != sizeof(*evs)) {
> +                     IR_dprintk(1, "overflow error: received %d instead of 
> %zd\n",
> +                                rc, sizeof(*evs));
> +                     return -EINVAL;
> +             }
> +             IR_dprintk(2, "event type %d, time before event: %07luus\n",
> +                     evs[i].type, (evs[i].delta.tv_nsec + 500) / 1000);
> +     }
> +
> +     rc = ir_nec_decode(input_dev, evs, len);
> +
> +     kfree(evs);
> +
> +     return rc;
> +}
> +EXPORT_SYMBOL_GPL(ir_raw_event_handle);
> diff --git a/drivers/media/video/saa7134/saa7134-input.c 
> b/drivers/media/video/saa7134/saa7134-input.c
> index 8187928..7382995 100644
> --- a/drivers/media/video/saa7134/saa7134-input.c
> +++ b/drivers/media/video/saa7134/saa7134-input.c
> @@ -67,6 +67,7 @@ MODULE_PARM_DESC(disable_other_ir, "disable full codes of "
>  /* Helper functions for RC5 and NEC decoding at GPIO16 or GPIO18 */
>  static int saa7134_rc5_irq(struct saa7134_dev *dev);
>  static int saa7134_nec_irq(struct saa7134_dev *dev);
> +static int saa7134_raw_decode_irq(struct saa7134_dev *dev);
>  static void nec_task(unsigned long data);
>  static void saa7134_nec_timer(unsigned long data);
>  
> @@ -402,10 +403,12 @@ void saa7134_input_irq(struct saa7134_dev *dev)
>  
>       if (ir->nec_gpio) {
>               saa7134_nec_irq(dev);
> -     } else if (!ir->polling && !ir->rc5_gpio) {
> +     } else if (!ir->polling && !ir->rc5_gpio && !ir->raw_decode) {
>               build_key(dev);
>       } else if (ir->rc5_gpio) {
>               saa7134_rc5_irq(dev);
> +     } else if (ir->raw_decode) {
> +             saa7134_raw_decode_irq(dev);
>       }
>  }
>  
> @@ -418,6 +421,23 @@ static void saa7134_input_timer(unsigned long data)
>       mod_timer(&ir->timer, jiffies + msecs_to_jiffies(ir->polling));
>  }
>  
> +void ir_raw_decode_timer_end(unsigned long data)
> +{
> +     struct saa7134_dev *dev = (struct saa7134_dev *)data;
> +     struct card_ir *ir = dev->remote;
> +     int rc;
> +
> +     /*
> +      * FIXME: the IR key handling code should be called by the decoder,
> +      * after implementing the repeat mode
> +      */
> +     rc = ir_raw_event_handle(dev->remote->dev);
> +     if (rc >= 0) {
> +             ir_input_keydown(ir->dev, &ir->ir, rc);
> +             ir_input_nokey(ir->dev, &ir->ir);
> +     }
> +}
> +
>  void saa7134_ir_start(struct saa7134_dev *dev, struct card_ir *ir)
>  {
>       if (ir->running)
> @@ -446,6 +466,11 @@ void saa7134_ir_start(struct saa7134_dev *dev, struct 
> card_ir *ir)
>               setup_timer(&ir->timer_keyup, saa7134_nec_timer,
>                           (unsigned long)dev);
>               tasklet_init(&ir->tlet, nec_task, (unsigned long)dev);
> +     } else if (ir->raw_decode) {
> +             /* set timer_end for code completion */
> +             init_timer(&ir->timer_end);
> +             ir->timer_end.function = ir_raw_decode_timer_end;
> +             ir->timer_end.data = (unsigned long)dev;
>       }
>  }
>  
> @@ -461,6 +486,9 @@ void saa7134_ir_stop(struct saa7134_dev *dev)
>               del_timer_sync(&ir->timer_end);
>       else if (ir->nec_gpio)
>               tasklet_kill(&ir->tlet);
> +     else if (ir->raw_decode)
> +             del_timer_sync(&ir->timer_end);
> +
>       ir->running = 0;
>  }
>  
> @@ -508,6 +536,7 @@ int saa7134_input_init1(struct saa7134_dev *dev)
>       int polling      = 0;
>       int rc5_gpio     = 0;
>       int nec_gpio     = 0;
> +     int raw_decode   = 0;
>       u64 ir_type = IR_TYPE_OTHER;
>       int err;
>  
> @@ -573,7 +602,7 @@ int saa7134_input_init1(struct saa7134_dev *dev)
>               ir_codes     = &ir_codes_avermedia_m135a_rm_jx_table;
>               mask_keydown = 0x0040000;
>               mask_keycode = 0xffff;
> -             nec_gpio     = 1;
> +             raw_decode   = 1;
>               break;
>       case SAA7134_BOARD_AVERMEDIA_777:
>       case SAA7134_BOARD_AVERMEDIA_A16AR:
> @@ -754,6 +783,7 @@ int saa7134_input_init1(struct saa7134_dev *dev)
>       ir->polling      = polling;
>       ir->rc5_gpio     = rc5_gpio;
>       ir->nec_gpio     = nec_gpio;
> +     ir->raw_decode   = raw_decode;
>  
>       /* init input device */
>       snprintf(ir->name, sizeof(ir->name), "saa7134 IR (%s)",
> @@ -761,7 +791,7 @@ int saa7134_input_init1(struct saa7134_dev *dev)
>       snprintf(ir->phys, sizeof(ir->phys), "pci-%s/ir0",
>                pci_name(dev->pci));
>  
> -     if (ir_codes->ir_type != IR_TYPE_OTHER) {
> +     if (ir_codes->ir_type != IR_TYPE_OTHER && !raw_decode) {
>               ir->props.allowed_protos = IR_TYPE_RC5 | IR_TYPE_NEC;
>               ir->props.priv = dev;
>               ir->props.change_protocol = saa7134_ir_change_protocol;
> @@ -789,6 +819,11 @@ int saa7134_input_init1(struct saa7134_dev *dev)
>       err = ir_input_register(ir->dev, ir_codes, &ir->props, MODULE_NAME);
>       if (err)
>               goto err_out_stop;
> +     if (ir_codes->ir_type != IR_TYPE_OTHER) {
> +             err = ir_raw_event_register(ir->dev);
> +             if (err)
> +                     goto err_out_stop;
> +     }
>  
>       saa7134_ir_start(dev, ir);
>  
> @@ -812,6 +847,7 @@ void saa7134_input_fini(struct saa7134_dev *dev)
>               return;
>  
>       saa7134_ir_stop(dev);
> +     ir_raw_event_unregister(dev->remote->dev);
>       ir_input_unregister(dev->remote->dev);
>       kfree(dev->remote);
>       dev->remote = NULL;
> @@ -918,6 +954,48 @@ void saa7134_probe_i2c_ir(struct saa7134_dev *dev)
>       i2c_new_device(&dev->i2c_adap, &info);
>  }
>  
> +static int saa7134_raw_decode_irq(struct saa7134_dev *dev)
> +{
> +     struct card_ir  *ir = dev->remote;
> +     unsigned long   timeout;
> +     int count, pulse, oldpulse;
> +
> +     /* Disable IR IRQ line */
> +     saa_clearl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO18);
> +
> +     /* Generate initial event */
> +     saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
> +     saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
> +     pulse = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2) & ir->mask_keydown;
> +     ir_raw_event_store(dev->remote->dev, pulse? IR_PULSE : IR_SPACE);
> +
> +#if 1
> +     /* Wait up to 10 ms for event change */
> +     oldpulse = pulse;
> +     for (count = 0; count < 1000; count++)  {
> +             udelay(10);
> +             /* rising SAA7134_GPIO_GPRESCAN reads the status */
> +             saa_clearb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
> +             saa_setb(SAA7134_GPIO_GPMODE3, SAA7134_GPIO_GPRESCAN);
> +             pulse = saa_readl(SAA7134_GPIO_GPSTATUS0 >> 2)
> +                     & ir->mask_keydown;
> +             if (pulse != oldpulse)
> +                     break;
> +     }
> +
> +     /* Store final event */
> +     ir_raw_event_store(dev->remote->dev, pulse? IR_PULSE : IR_SPACE);
> +#endif
> +     /* Wait 15 ms before deciding to do something else */
> +     timeout = jiffies + jiffies_to_msecs(15);
> +     mod_timer(&ir->timer_end, timeout);
> +
> +     /* Enable IR IRQ line */
> +     saa_setl(SAA7134_IRQ2, SAA7134_IRQ2_INTE_GPIO18);
> +
> +     return 1;
> +}
> +
>  static int saa7134_rc5_irq(struct saa7134_dev *dev)
>  {
>       struct card_ir *ir = dev->remote;
> @@ -960,7 +1038,6 @@ static int saa7134_rc5_irq(struct saa7134_dev *dev)
>       return 1;
>  }
>  
> -
>  /* On NEC protocol, One has 2.25 ms, and zero has 1.125 ms
>     The first pulse (start) has 9 + 4.5 ms
>   */
> diff --git a/include/media/ir-common.h b/include/media/ir-common.h
> index 41469b7..87f2ec7 100644
> --- a/include/media/ir-common.h
> +++ b/include/media/ir-common.h
> @@ -82,6 +82,9 @@ struct card_ir {
>       /* NEC decoding */
>       u32                     nec_gpio;
>       struct tasklet_struct   tlet;
> +
> +     /* IR core raw decoding */
> +     u32                     raw_decode;
>  };
>  
>  /* Routines from ir-functions.c */
> diff --git a/include/media/ir-core.h b/include/media/ir-core.h
> index 1eae72d..369969d 100644
> --- a/include/media/ir-core.h
> +++ b/include/media/ir-core.h
> @@ -16,6 +16,8 @@
>  
>  #include <linux/input.h>
>  #include <linux/spinlock.h>
> +#include <linux/kfifo.h>
> +#include <linux/time.h>
>  
>  extern int ir_core_debug;
>  #define IR_dprintk(level, fmt, arg...)       if (ir_core_debug >= level) \
> @@ -27,6 +29,13 @@ extern int ir_core_debug;
>  #define IR_TYPE_NEC  (1  << 2)
>  #define IR_TYPE_OTHER        (((u64)1) << 63l)
>  
> +enum raw_event_type {
> +     IR_SPACE        = (1 << 0),
> +     IR_PULSE        = (1 << 1),
> +     IR_START_EVENT  = (1 << 2),
> +     IR_STOP_EVENT   = (1 << 3),
> +};
> +

Why are these events encoded as bit flags?  Shouldn't they all be
orthogonal?

Regards,
Andy

>  struct ir_scancode {
>       u16     scancode;
>       u32     keycode;
> @@ -46,6 +55,15 @@ struct ir_dev_props {
>       int (*change_protocol)(void *priv, u64 ir_type);
>  };
>  
> +struct ir_raw_event {
> +     struct timespec         delta;  /* Time spent before event */
> +     enum raw_event_type     type;   /* event type */
> +};
> +
> +struct ir_raw_event_ctrl {
> +     struct kfifo                    kfifo;          /* fifo for the 
> pulse/space events */
> +     struct timespec                 last_event;     /* when last event 
> occurred */
> +};
>  
>  struct ir_input_dev {
>       struct device                   dev;            /* device */
> @@ -53,7 +71,9 @@ struct ir_input_dev {
>       struct ir_scancode_table        rc_tab;         /* scan/key table */
>       unsigned long                   devno;          /* device number */
>       const struct ir_dev_props       *props;         /* Device properties */
> +     struct ir_raw_event_ctrl        *raw;           /* for raw pulse/space 
> events */
>  };
> +
>  #define to_ir_input_dev(_attr) container_of(_attr, struct ir_input_dev, attr)
>  
>  /* Routines from ir-keytable.c */
> @@ -72,4 +92,16 @@ void ir_input_unregister(struct input_dev *input_dev);
>  int ir_register_class(struct input_dev *input_dev);
>  void ir_unregister_class(struct input_dev *input_dev);
>  
> +/* Routines from ir-raw-event.c */
> +int ir_raw_event_register(struct input_dev *input_dev);
> +void ir_raw_event_unregister(struct input_dev *input_dev);
> +int ir_raw_event_store(struct input_dev *input_dev, enum raw_event_type 
> type);
> +int ir_raw_event_handle(struct input_dev *input_dev);
> +
> +/* from ir-nec-decoder.c */
> +int ir_nec_decode(struct input_dev *input_dev,
> +               struct ir_raw_event *evs,
> +               int len);
> +
> +
>  #endif

--
To unsubscribe from this list: send the line "unsubscribe linux-media" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to