[<prev] [next>] [day] [month] [year] [list]
Message-ID: <1361982341-5322-1-git-send-email-michail.kurachkin@promwad.com>
Date: Wed, 27 Feb 2013 19:25:41 +0300
From: Michail Kurachkin <michail.kurachkin@...mwad.com>
To: "linux-kernel@...r.kernel.org" <linux-kernel@...r.kernel.org>,
Greg Kroah-Hartman <gregkh@...uxfoundation.org>,
Kuten Ivan <Ivan.Kuten@...mwad.com>,
"benavi@...vell.com" <benavi@...vell.com>,
Palstsiuk Viktar <Viktar.Palstsiuk@...mwad.com>,
Dmitriy Gorokh <dmitriy.gorokh@...mwad.com>
CC: Michail Kurochkin <michail.kurachkin@...mwad.com>
Subject: [PATCH 5/9] refactoring request/free voice channels
From: Michail Kurochkin <michail.kurachkin@...mwad.com>
Signed-off-by: Michail Kurochkin <michail.kurachkin@...mwad.com>
---
drivers/staging/tdm/kirkwood_tdm.c | 162 +++++++++++++++++++++++++-----------
drivers/staging/tdm/kirkwood_tdm.h | 10 ++-
drivers/staging/tdm/tdm.h | 5 +-
drivers/staging/tdm/tdm_core.c | 65 +++++++--------
4 files changed, 154 insertions(+), 88 deletions(-)
diff --git a/drivers/staging/tdm/kirkwood_tdm.c b/drivers/staging/tdm/kirkwood_tdm.c
index 4366d38..e4a9106 100644
--- a/drivers/staging/tdm/kirkwood_tdm.c
+++ b/drivers/staging/tdm/kirkwood_tdm.c
@@ -75,7 +75,7 @@ static int kirkwood_tdm_hw_init(struct tdm_controller *tdm)
u32 control_val = 0;
u32 pclk_freq;
unsigned long flags;
- spinlock_t lock;
+ static spinlock_t lock;
spin_lock_init(&lock);
@@ -192,13 +192,14 @@ static void kirkwood_tdm_hw_deinit(struct tdm_controller *tdm)
}
/**
- * Setup hardware voice channel
+ * Initialization hardware voice channel
+ * for specify TDM device
+ * @param tdm_dev - specify TDM device
* @param ch - voice hardware channel
* @return 0 - OK
*/
-int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
+static int kirkwood_init_voice_channel(struct tdm_device *tdm_dev, struct tdm_voice_channel* ch)
{
- struct tdm_device *tdm_dev = to_tdm_device(ch->dev);
struct tdm_controller *tdm = tdm_dev->controller;
struct tdm_controller_hw_settings *hw = tdm->settings;
struct kirkwood_tdm *onchip_tdm =
@@ -208,15 +209,15 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
unsigned long timeout;
u32 state;
int i;
-
+ int rc = 0;
u8 voice_num = ch->channel_num;
/* calculate buffer size */
ch->buffer_len = tdm_dev->buffer_sample_count * hw->channel_size;
/* Request timeslot for voice channel */
- writeb(ch->tdm_channel, (u8*)®s->chan_time_slot_ctrl + 2 * voice_num);
- writeb(ch->tdm_channel, (u8*)®s->chan_time_slot_ctrl + 1 + 2 * voice_num);
+ writeb(tdm_dev->tdm_channel_num, (u8*)®s->chan_time_slot_ctrl + 2 * voice_num);
+ writeb(tdm_dev->tdm_channel_num, (u8*)®s->chan_time_slot_ctrl + 1 + 2 * voice_num);
/* FIXME: move coherent_dma_mask to board specific */
tdm_dev->dev.coherent_dma_mask = 0xffffffff;
@@ -226,11 +227,12 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
/* allocate memory for DMA receiver */
onchip_ch->rx_buf[i] =
dma_alloc_coherent(&tdm_dev->dev,
- ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_DMA);
+ ch->buffer_len, onchip_ch->rx_buff_phy + i, GFP_KERNEL);
if (onchip_ch->rx_buf == NULL) {
dev_err(ch->dev, "Can't allocate memory for TDM receiver DMA buffer\n");
- return -ENOMEM;
+ rc = -ENOMEM;
+ goto out1;
}
memset(onchip_ch->rx_buf[i], 0, ch->buffer_len);
@@ -238,20 +240,16 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
/* allocate memory for DMA transmitter */
onchip_ch->tx_buf[i] =
dma_alloc_coherent(&tdm_dev->dev,
- ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_DMA);
+ ch->buffer_len, onchip_ch->tx_buff_phy + i, GFP_KERNEL);
if (onchip_ch->tx_buf == NULL) {
dev_err(ch->dev, "Can't allocate memory for TDM transmitter DMA buffer\n");
- return -ENOMEM;
+ rc = -ENOMEM;
+ goto out1;
}
memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
}
- atomic_set(&onchip_ch->write_rx_buf_num, 0);
- atomic_set(&onchip_ch->write_tx_buf_num, 0);
- atomic_set(&onchip_ch->read_rx_buf_num, 0);
- atomic_set(&onchip_ch->read_tx_buf_num, 0);
-
/* Set length for DMA */
writel(((tdm_dev->buffer_sample_count - 32) << 8) | tdm_dev->buffer_sample_count,
®s->chan0_total_sample + voice_num);
@@ -262,8 +260,10 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
state = readl(®s->chan0_buff_ownership + 4 * voice_num) & 0x100;
if(time_after(jiffies, timeout)) {
dev_err(ch->dev, "Can`t program DMA tx buffer\n");
- return -EBUSY;
+ rc = -EBUSY;
+ goto out1;
}
+ schedule();
} while(state);
/* Set DMA buffers fo transmitter */
@@ -277,8 +277,10 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
state = readl(®s->chan0_buff_ownership + 4 * voice_num) & 1;
if(time_after(jiffies, timeout)) {
dev_err(ch->dev, "Can`t program DMA rx buffer\n");
- return -EBUSY;
+ rc = -EBUSY;
+ goto out1;
}
+ schedule();
} while(state);
/* Set DMA buffers for receiver */
@@ -286,11 +288,51 @@ int kirkwood_setup_voice_channel(struct tdm_voice_channel* ch)
®s->chan0_receive_start_addr + 4 * voice_num);
writeb(0x1, (u8*)(®s->chan0_buff_ownership + 4 * voice_num) );
- return 0;
+out0:
+ return rc;
+
+out1:
+ kirkwood_reset_voice_channel(tdm_dev);
+ return rc;
}
/**
+ * Release and reset hardware voice channel
+ * free DMA buffers.
+ * @param tdm_dev - TDM device early requested voice channel
+ * @return 0 - OK
+ */
+int kirkwood_release_voice_channel(struct tdm_device *tdm_dev)
+{
+ struct tdm_voice_channel* ch = tdm_dev->ch;
+ struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
+ int i;
+
+ if (!tdm_dev->ch)
+ return -ENODEV;
+
+ for(i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++)
+ {
+ if (onchip_ch->rx_buf[i])
+ {
+ dma_free_coherent(&ch->dev,
+ ch->buffer_len, onchip_ch->rx_buf[i], onchip_ch->rx_buff_phy + i);
+ onchip_ch->rx_buf[i] = 0;
+ }
+
+ if (onchip_ch->tx_buf[i])
+ {
+ dma_free_coherent(&ch->dev,
+ ch->buffer_len, onchip_ch->tx_buf[i], onchip_ch->tx_buff_phy + i);
+ onchip_ch->tx_buf[i] = 0;
+ }
+ }
+
+ return 0;
+}
+
+/**
* Run tdm transmitter and receiver
* @param tdm_dev - tdm device
* @return 0 - ok
@@ -304,8 +346,8 @@ int kirkwood_tdm_run_audio(struct tdm_device *tdm_dev)
struct tdm_voice_channel *ch = tdm_dev->ch;
struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
- memset(onchip_ch->tx_buf[0], 0, ch->buffer_len);
- memset(onchip_ch->tx_buf[1], 0, ch->buffer_len);
+ for (i = 0; i < COUNT_DMA_BUFFERS_PER_CHANNEL; i++)
+ memset(onchip_ch->tx_buf[i], 0, ch->buffer_len);
writeb(0x1, (u8 *)(®s->chan0_enable_disable + 4 * ch->channel_num) + 1);
writeb(0x1, (u8 *)(®s->chan0_enable_disable + 4 * ch->channel_num) );
@@ -364,11 +406,18 @@ int kirkwood_tdm_stop_audio(struct tdm_device *tdm_dev)
*/
int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
{
- if (atomic_read(&onchip_ch->read_tx_buf_num) <= atomic_read(&onchip_ch->write_tx_buf_num))
- return atomic_read(&onchip_ch->write_tx_buf_num) - atomic_read(&onchip_ch->read_tx_buf_num);
+ unsigned long flags;
+ int latency;
+
+ spin_lock_irqsave(&onchip_ch->lock, flags);
+ if (onchip_ch->read_tx_buf_num <= onchip_ch->write_tx_buf_num)
+ latency = onchip_ch->write_tx_buf_num - onchip_ch->read_tx_buf_num;
else
- return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_tx_buf_num)
- + atomic_read(&onchip_ch->write_tx_buf_num);
+ latency = COUNT_DMA_BUFFERS_PER_CHANNEL - onchip_ch->read_tx_buf_num
+ + onchip_ch->write_tx_buf_num;
+ spin_unlock_irqrestore(&onchip_ch->lock, flags);
+
+ return latency;
}
@@ -379,11 +428,18 @@ int get_tx_latency(struct kirkwood_tdm_voice *onchip_ch)
*/
int get_rx_latency(struct kirkwood_tdm_voice *onchip_ch)
{
- if (atomic_read(&onchip_ch->read_rx_buf_num) <= atomic_read(&onchip_ch->write_rx_buf_num))
- return atomic_read(&onchip_ch->write_rx_buf_num) - atomic_read(&onchip_ch->read_rx_buf_num);
+ unsigned long flags;
+ int latency;
+
+ spin_lock_irqsave(&onchip_ch->lock, flags);
+ if (onchip_ch->read_rx_buf_num <= onchip_ch->write_rx_buf_num)
+ latency = onchip_ch->write_rx_buf_num - onchip_ch->read_rx_buf_num;
else
- return COUNT_DMA_BUFFERS_PER_CHANNEL - atomic_read(&onchip_ch->read_rx_buf_num)
- + atomic_read(&onchip_ch->write_rx_buf_num);
+ latency = COUNT_DMA_BUFFERS_PER_CHANNEL - onchip_ch->read_rx_buf_num
+ + onchip_ch->write_rx_buf_num;
+ spin_unlock_irqrestore(&onchip_ch->lock, flags);
+
+ return latency;
}
@@ -421,7 +477,7 @@ int kirkwood_poll_tx(struct tdm_device *tdm_dev)
* @param num - current buffer number
* @return next buffer number
*/
-static int inc_next_dma_buf_num(int num)
+static inline int inc_next_dma_buf_num(int num)
{
num ++;
if (num >= COUNT_DMA_BUFFERS_PER_CHANNEL)
@@ -445,6 +501,7 @@ static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
{
struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
int rc = 0;
+ unsigned long flags;
rc = wait_event_interruptible(ch->tx_queue,
get_tx_latency(onchip_ch) > 1);
@@ -452,10 +509,12 @@ static int kirkwood_send(struct tdm_voice_channel *ch, u8 *data)
if (rc)
return rc;
- memcpy(onchip_ch->tx_buf[atomic_read(&onchip_ch->read_tx_buf_num)], data,
+ spin_lock_irqsave(&onchip_ch->lock, flags);
+ memcpy(onchip_ch->tx_buf[onchip_ch->read_tx_buf_num], data,
ch->buffer_len);
- atomic_set(&onchip_ch->read_tx_buf_num,
- inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
+ onchip_ch->read_tx_buf_num =
+ inc_next_dma_buf_num(onchip_ch->read_tx_buf_num);
+ spin_unlock_irqrestore(&onchip_ch->lock, flags);
return rc;
}
@@ -473,6 +532,7 @@ static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
{
struct kirkwood_tdm_voice *onchip_ch = ch->private_data;
int rc = 0;
+ unsigned long flags;
rc = wait_event_interruptible(ch->rx_queue,
get_rx_latency(onchip_ch) > 1);
@@ -480,10 +540,12 @@ static int kirkwood_recv(struct tdm_voice_channel *ch, u8 *data)
if (rc)
return rc;
- memcpy(data, onchip_ch->rx_buf[atomic_read(&onchip_ch->read_rx_buf_num)],
+ spin_lock_irqsave(&onchip_ch->lock, flags);
+ memcpy(data, onchip_ch->rx_buf[onchip_ch->read_rx_buf_num],
ch->buffer_len);
- atomic_set(&onchip_ch->read_rx_buf_num,
- inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
+ onchip_ch->read_rx_buf_num =
+ inc_next_dma_buf_num(onchip_ch->read_rx_buf_num);
+ spin_unlock_irqrestore(&onchip_ch->lock, flags);
return rc;
}
@@ -595,11 +657,11 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
switch(mode) {
case IRQ_RECEIVE: {
/* get next buffer number, and move write/read pointer */
- next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_rx_buf_num));
- atomic_set(&onchip_ch->write_rx_buf_num, next_buf_num);
- if(next_buf_num == atomic_read(&onchip_ch->read_rx_buf_num))
- atomic_set(&onchip_ch->read_rx_buf_num,
- inc_next_dma_buf_num(atomic_read(&onchip_ch->read_rx_buf_num)));
+ next_buf_num = inc_next_dma_buf_num(onchip_ch->write_rx_buf_num);
+ onchip_ch->write_rx_buf_num = next_buf_num;
+ if(next_buf_num == onchip_ch->read_rx_buf_num)
+ onchip_ch->read_rx_buf_num =
+ inc_next_dma_buf_num(onchip_ch->read_rx_buf_num);
/* if receive overflow event */
if (overflow) {
@@ -631,11 +693,11 @@ static irqreturn_t kirkwood_tdm_irq(s32 irq, void *context_data)
case IRQ_TRANSMIT: {
/* get next buffer number, and move write/read pointer */
- next_buf_num = inc_next_dma_buf_num(atomic_read(&onchip_ch->write_tx_buf_num));
- atomic_set(&onchip_ch->write_tx_buf_num, next_buf_num);
- if(next_buf_num == atomic_read(&onchip_ch->read_tx_buf_num))
- atomic_set(&onchip_ch->read_tx_buf_num,
- inc_next_dma_buf_num(atomic_read(&onchip_ch->read_tx_buf_num)));
+ next_buf_num = inc_next_dma_buf_num(onchip_ch->write_tx_buf_num);
+ onchip_ch->write_tx_buf_num= next_buf_num;
+ if(next_buf_num == onchip_ch->read_tx_buf_num)
+ onchip_ch->read_tx_buf_num =
+ inc_next_dma_buf_num(onchip_ch->read_tx_buf_num);
/* if transmit overflow event */
if (overflow) {
@@ -777,7 +839,8 @@ static int __init kirkwood_tdm_probe(struct platform_device *pdev)
/* Set controller data */
tdm->bus_num = pdev->id;
tdm->settings = hw;
- tdm->setup_voice_channel = kirkwood_setup_voice_channel;
+ tdm->init_voice_channel = kirkwood_init_voice_channel;
+ tdm->release_voice_channel = kirkwood_release_voice_channel;
tdm->recv = kirkwood_recv;
tdm->send = kirkwood_send;
tdm->run_audio = kirkwood_tdm_run_audio;
@@ -787,13 +850,16 @@ static int __init kirkwood_tdm_probe(struct platform_device *pdev)
for (i = 0; i < KIRKWOOD_MAX_VOICE_CHANNELS; i++)
{
+ struct kirkwood_tdm_voice *oncip_ch = onchip_tdm->voice_channels + i;
+
+ spin_lock_init(&oncip_ch->lock);
ch = tdm_alloc_voice_channel();
if (ch == NULL) {
dev_err(&pdev->dev, "Can`t alloc voice channel %d\n", i);
goto out1;
}
- tdm_register_new_voice_channel(tdm, ch, (void *)(onchip_tdm->voice_channels + i));
+ tdm_register_new_voice_channel(tdm, ch, (void *)oncip_ch);
}
diff --git a/drivers/staging/tdm/kirkwood_tdm.h b/drivers/staging/tdm/kirkwood_tdm.h
index 6c7f34d..fe7aadf 100644
--- a/drivers/staging/tdm/kirkwood_tdm.h
+++ b/drivers/staging/tdm/kirkwood_tdm.h
@@ -58,6 +58,8 @@ struct kirkwood_tdm_regs {
* Data specified for kirkwood hardware voice channel
*/
struct kirkwood_tdm_voice {
+ spinlock_t lock;
+
/* Transmitter and receiver buffers split to half.
* While first half buffer is filling by DMA controller,
* second half buffer is used by consumer and etc.
@@ -67,10 +69,10 @@ struct kirkwood_tdm_voice {
dma_addr_t tx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /* two physical pointers to tx_buf */
dma_addr_t rx_buff_phy[COUNT_DMA_BUFFERS_PER_CHANNEL]; /* two physical pointers to rx_buf */
- atomic_t write_tx_buf_num; /* current writing transmit buffer number */
- atomic_t read_tx_buf_num; /* current reading transmit buffer number */
- atomic_t write_rx_buf_num; /* current writing receive buffer number */
- atomic_t read_rx_buf_num; /* current reading receive buffer number */
+ u8 write_tx_buf_num; /* current writing transmit buffer number */
+ u8 read_tx_buf_num; /* current reading transmit buffer number */
+ u8 write_rx_buf_num; /* current writing receive buffer number */
+ u8 read_rx_buf_num; /* current reading receive buffer number */
};
diff --git a/drivers/staging/tdm/tdm.h b/drivers/staging/tdm/tdm.h
index ee7b5bf..80c1460 100644
--- a/drivers/staging/tdm/tdm.h
+++ b/drivers/staging/tdm/tdm.h
@@ -53,7 +53,7 @@ struct tdm_voice_channel {
wait_queue_head_t tx_queue;
wait_queue_head_t rx_queue;
- struct list_head list; /* Union all tdm voice channels by one controller */
+ struct list_head list; /* Union all tdm voice channels by one controller */
struct device *dev; /* device requested voice channel */
};
@@ -106,7 +106,8 @@ struct tdm_controller {
/* TDM hardware settings */
struct tdm_controller_hw_settings *settings;
- int (*setup_voice_channel)(struct tdm_voice_channel *ch);
+ int (*init_voice_channel)(struct tdm_device *tdm_dev, struct tdm_voice_channel* ch);
+ int (*release_voice_channel)(struct tdm_device *tdm_dev);
int (*send)(struct tdm_voice_channel *ch, u8 *data);
int (*recv)(struct tdm_voice_channel *ch, u8 *data);
int (*run_audio)(struct tdm_device *tdm_dev);
diff --git a/drivers/staging/tdm/tdm_core.c b/drivers/staging/tdm/tdm_core.c
index 0182a4f..f3ac6fc 100644
--- a/drivers/staging/tdm/tdm_core.c
+++ b/drivers/staging/tdm/tdm_core.c
@@ -174,53 +174,64 @@ EXPORT_SYMBOL_GPL(tdm_unregister_driver);
/**
- * Request unused voice channel
+ * Request and init unused voice channel
* @param tdm_dev - TDM device requested voice channel
* @return pointer to voice channel
*/
struct tdm_voice_channel *request_voice_channel(struct tdm_device *tdm_dev) {
- struct tdm_controller *tdm = tdm_dev->controller;
- struct tdm_voice_channel *ch;
- unsigned long flags;
+ struct tdm_controller *tdm = tdm_dev->controller;
+ struct tdm_voice_channel *ch;
+ unsigned long flags;
+ int rc;
- spin_lock_irqsave(&tdm->lock, flags);
+ spin_lock_irqsave(&tdm->lock, flags);
list_for_each_entry(ch, &tdm->voice_channels, list)
if (ch->dev == NULL) {
ch->dev = &tdm_dev->dev;
+ ch->mode_wideband = tdm_dev->mode_wideband;
+ ch->tdm_channel = tdm_dev->tdm_channel_num;
tdm_dev->ch = ch;
spin_unlock_irqrestore(&tdm->lock, flags);
+ rc = tdm->init_voice_channel(tdm_dev, ch);
+ if (rc)
+ {
+ dev_err(&tdm_dev->dev, "Can't init TDM voice channel\n");
+ return NULL;
+ }
+
return ch;
}
- spin_unlock_irqrestore(&tdm->lock, flags);
- return NULL;
+ spin_unlock_irqrestore(&tdm->lock, flags);
+ return NULL;
}
/**
- * Release requested voice channel
+ * Release requested early voice channel
* @param TDM device requested early voice channel
* @return 0 - OK
*/
int release_voice_channel(struct tdm_device *tdm_dev)
{
- struct tdm_controller *tdm = tdm_dev->controller;
- struct tdm_voice_channel *ch;
- unsigned long flags;
+ struct tdm_controller *tdm = tdm_dev->controller;
+ struct tdm_voice_channel *ch;
+ unsigned long flags;
- spin_lock_irqsave(&tdm->lock, flags);
+ spin_lock_irqsave(&tdm->lock, flags);
- tdm_dev->ch = NULL;
+ tdm_dev->ch = NULL;
list_for_each_entry(ch, &tdm->voice_channels, list)
- if (ch->dev == &tdm_dev->dev) {
- ch->dev = NULL;
- spin_unlock_irqrestore(&tdm->lock, flags);
- return 0;
- }
+ if (ch->dev == &tdm_dev->dev) {
+ tdm->release_voice_channel(tdm_dev);
+ ch->dev = NULL;
+ spin_unlock_irqrestore(&tdm->lock, flags);
+ return 0;
+ }
- spin_unlock_irqrestore(&tdm->lock, flags);
+ spin_unlock_irqrestore(&tdm->lock, flags);
- return -ENODEV;
+ return -ENODEV;
}
@@ -326,7 +337,6 @@ struct tdm_voice_channel *tdm_alloc_voice_channel(void)
if (!ch)
return NULL;
- memset(ch, 0, sizeof *ch);
init_waitqueue_head(&ch->tx_queue);
init_waitqueue_head(&ch->rx_queue);
@@ -614,19 +624,6 @@ int tdm_add_device(struct tdm_device *tdm_dev)
goto done;
}
- printk("ch = %s\n", dev_name(ch->dev));
-
- /* Configuring voice channel */
- ch->mode_wideband = tdm_dev->mode_wideband;
- ch->tdm_channel = tdm_dev->tdm_channel_num;
-
- /* Run setup voice channel */
- status = tdm_dev->controller->setup_voice_channel(ch);
- if (status < 0) {
- dev_err(dev, "can't setup voice channel, status %d\n", status);
- goto done;
- }
-
/* Device may be bound to an active driver when this returns */
status = device_add(&tdm_dev->dev);
if (status < 0)
--
1.7.5.4
--
To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Please read the FAQ at http://www.tux.org/lkml/
Powered by blists - more mailing lists