Hi! I'm taking a stab at adding DMA support to pata_mpc52xx driver.
It's based on patches from Freescale's ltib (old ide driver). The problem is that I can't seem to get any interrupts (not ata, not bestcomm task), when setting up MWDMA/UDMA mode. Ideas? My current patch: --- drivers/ata/pata_mpc52xx.c | 491 +++++++++++++++++++++++++++++++++++++++++++-- include/linux/libata.h | 2 2 files changed, 477 insertions(+), 16 deletions(-) Index: work-powerpc.git/drivers/ata/pata_mpc52xx.c =================================================================== --- work-powerpc.git.orig/drivers/ata/pata_mpc52xx.c +++ work-powerpc.git/drivers/ata/pata_mpc52xx.c @@ -4,6 +4,7 @@ * libata driver for the Freescale MPC52xx on-chip IDE interface * * Copyright (C) 2006 Sylvain Munaut <[EMAIL PROTECTED]> + * Copyright (C) 2005,2006 Freescale - Bernard Kuhn, John Rigby * Copyright (C) 2003 Mipsys - Benjamin Herrenschmidt * * This file is licensed under the terms of the GNU General Public License @@ -22,6 +23,8 @@ #include <asm/of_platform.h> #include <asm/mpc52xx.h> +#include <sysdev/bestcomm/bestcomm.h> +#include <sysdev/bestcomm/ata.h> #define DRV_NAME "mpc52xx_ata" #define DRV_VERSION "0.1.0ac2" @@ -31,6 +34,14 @@ struct mpc52xx_ata_timings { u32 pio1; u32 pio2; + u32 mdma1; + u32 mdma2; + u32 udma1; + u32 udma2; + u32 udma3; + u32 udma4; + u32 udma5; + int using_udma; }; struct mpc52xx_ata_priv { @@ -39,6 +50,12 @@ struct mpc52xx_ata_priv { int ata_irq; struct mpc52xx_ata_timings timings[2]; int csel; + + /* dma stuff follows */ + struct bcom_task * dmatsk; + const struct udmaspec * udmaspec; + const struct mdmaspec * mdmaspec; + int mpc52xx_ata_dma_last_write; }; @@ -53,6 +70,102 @@ static const int ataspec_ta[5] = { 35 #define CALC_CLKCYC(c,v) ((((v)+(c)-1)/(c))) +/* ATAPI-4 MDMA specs (in clocks) */ +struct mdmaspec { + u32 t0M[3]; + u32 td[3]; + u32 th[3]; + u32 tj[3]; + u32 tkw[3]; + u32 tm[3]; + u32 tn[3]; +}; + +// ----------------------------------------------------------------------------------------------- + +static const struct mdmaspec mdmaspec66 = { + {32, 10, 8}, + {15, 6, 5}, + {2, 1, 1}, + {2, 1, 1}, + {15, 4, 2}, + {4, 2, 2}, + {1, 1, 1} +}; + +static const struct mdmaspec mdmaspec132 = { + {64, 20, 16}, + {29, 11, 10}, + {3, 2, 2}, + {3, 1, 1}, + {29, 7, 4}, + {7, 4, 4}, + {2, 1, 1} +}; + + +/* ATAPI-4 UDMA specs (in clocks) */ +struct udmaspec { + u32 tcyc[6]; + u32 t2cyc[6]; + u32 tds[6]; + u32 tdh[6]; + u32 tdvs[6]; + u32 tdvh[6]; + u32 tfs_min[6]; + u32 tli_max[6]; + u32 tmli[6]; + u32 taz[6]; + u32 tzah[6]; + u32 tenv_min[6]; + u32 tsr[6]; + u32 trfs[6]; + u32 trp[6]; + u32 tack[6]; + u32 tss[6]; +}; + +static const struct udmaspec udmaspec66 = { + { 8, 5, 4, 3, 2, 2}, + {16, 11, 8, 6, 4 , 2}, + { 1, 1, 1, 1, 1, 1}, + { 1, 1, 1, 1, 1, 1}, + { 5, 4, 3, 2, 1, 1}, + { 1, 1, 1, 1, 1, 1}, + {16, 14, 12, 9, 8, 6}, + {10, 10, 10, 7, 8, 5}, + { 2, 2, 2, 2, 2, 2}, + { 1, 1, 1, 1, 1, 1}, + { 2, 2, 2, 2, 2, 2}, + { 2, 2, 2, 2, 2, 2}, + { 3, 2, 2, 2, 2, 2}, + { 5, 5, 4, 4, 4, 4}, + {11, 9, 7, 7, 7, 6}, + { 2, 2, 2, 2, 2, 2}, + { 4, 4, 4, 4, 4, 4} +}; + +static const struct udmaspec udmaspec132 = { + {15, 10, 6, 7, 2, 3}, + {31, 21, 12, 12, 5, 6}, + { 2, 2, 1, 1, 0, 1}, + { 1, 1, 1, 1, 0, 1}, + {10, 7, 5, 3, 1, 1}, + { 1, 1, 1, 1, 1, 1}, + {30, 27, 23, 15, 16, 12}, + {20, 20, 20, 13, 14, 10}, + { 3, 3, 3, 3, 2, 3}, + { 2, 2, 2, 2, 1, 2}, + { 3, 3, 3, 3, 2, 3}, + { 3, 3, 3, 3, 2, 3}, + { 7, 4, 3, 3, 2, 3}, + {10, 10, 8, 8, 7, 7}, + {22, 17, 14, 14, 13, 12}, + { 3, 3, 3, 3, 2, 3}, + { 7, 7, 7, 7, 6, 7}, +}; + +// ----------------------------------------------------------------------------------------------- /* Bit definitions inside the registers */ #define MPC52xx_ATA_HOSTCONF_SMR 0x80000000UL /* State machine reset */ @@ -76,6 +189,10 @@ static const int ataspec_ta[5] = { 35 #define MPC52xx_ATA_DMAMODE_HUT 0x40 /* Host UDMA burst terminate */ +#define MAX_DMA_BUFFERS 128 +#define MAX_DMA_BUFFER_SIZE 0x20000u + + /* Structure of the hardware registers */ struct mpc52xx_ata { @@ -141,6 +258,19 @@ struct mpc52xx_ata { /* MPC52xx low level hw control */ +static inline void +mpc52xx_ata_wait_tip_bit_clear(struct mpc52xx_ata __iomem *regs) +{ + int timeout = 1000; + + while (in_be32(®s->host_status) & MPC52xx_ATA_HOSTSTAT_TIP) + if (timeout-- == 0) { + printk(KERN_ERR "mpc52xx-ide: Timeout waiting for TIP clear\n"); + break; + } + udelay(10); /* FIXME: Necessary ??? */ +} + static int mpc52xx_ata_compute_pio_timings(struct mpc52xx_ata_priv *priv, int dev, int pio) { @@ -165,6 +295,96 @@ mpc52xx_ata_compute_pio_timings(struct m return 0; } +static int +mpc52xx_ata_compute_mdma_timings(struct mpc52xx_ata_priv *priv, int dev, int speed) +{ + struct mpc52xx_ata_timings *timing = &priv->timings[dev]; + u32 t0M, td, tkw, tm, th, tj, tn; + + if (speed < 0 || speed > 2) + return -EINVAL; + + t0M = priv->mdmaspec->t0M[speed]; + td = priv->mdmaspec->td[speed]; + tkw = priv->mdmaspec->tkw[speed]; + tm = priv->mdmaspec->tm[speed]; + th = priv->mdmaspec->th[speed]; + tj = priv->mdmaspec->tj[speed]; + tn = priv->mdmaspec->tn[speed]; + + /* + DPRINTK ("t0M = %d\n", t0M); + DPRINTK ("td = %d\n", td); + DPRINTK ("tkw = %d\n", tkw); + DPRINTK ("tm = %d\n", tm); + DPRINTK ("th = %d\n", th); + DPRINTK ("tj = %d\n", tj); + DPRINTK ("tn = %d\n", tn); + */ + timing->mdma1 = (t0M << 24) | (td << 16) | (tkw << 8) | (tm); + timing->mdma2 = (th << 24) | (tj << 16) | (tn << 8); + + timing->using_udma = 0; + + return 0; +} + +static int +mpc52xx_ata_compute_udma_timings(struct mpc52xx_ata_priv *priv, int dev, int speed) +{ + struct mpc52xx_ata_timings *timing = &priv->timings[dev]; + u32 t2cyc, tcyc, tds, tdh, tdvs, tdvh, tfs, tli, tmli, taz, tenv, tsr, tss, trfs, trp, tack, tzah; + + if (speed < 0 || speed > 2) + return -EINVAL; + + t2cyc = priv->udmaspec->t2cyc[speed]; + tcyc = priv->udmaspec->tcyc[speed]; + tds = priv->udmaspec->tds[speed]; + tdh = priv->udmaspec->tdh[speed]; + tdvs = priv->udmaspec->tdvs[speed]; + tdvh = priv->udmaspec->tdvh[speed]; + tfs = priv->udmaspec->tfs_min[speed]; + tmli = priv->udmaspec->tmli[speed]; + tenv = priv->udmaspec->tenv_min[speed]; + tss = priv->udmaspec->tss[speed]; + trp = priv->udmaspec->trp[speed]; + tack = priv->udmaspec->tack[speed]; + tzah = priv->udmaspec->tzah[speed]; + taz = priv->udmaspec->taz[speed]; + trfs = priv->udmaspec->trfs[speed]; + tsr = priv->udmaspec->tsr[speed]; + tli = priv->udmaspec->tli_max[speed]; +/* + DPRINTK ("UDMA t2cyc = %d\n", t2cyc); + DPRINTK ("UDMA tcyc = %d\n", tcyc); + DPRINTK ("UDMA tds = %d\n", tds); + DPRINTK ("UDMA tdh = %d\n", tdh); + DPRINTK ("UDMA tdvs = %d\n", tdvs); + DPRINTK ("UDMA tdvh = %d\n", tdvh); + DPRINTK ("UDMA tfs = %d\n", tfs); + DPRINTK ("UDMA tli = %d\n", tli); + DPRINTK ("UDMA tmli = %d\n", tmli); + DPRINTK ("UDMA taz = %d\n", taz); + DPRINTK ("UDMA tenv = %d\n", tenv); + DPRINTK ("UDMA tsr = %d\n", tsr); + DPRINTK ("UDMA tss = %d\n", tss); + DPRINTK ("UDMA trfs = %d\n", trfs); + DPRINTK ("UDMA trp = %d\n", trp); + DPRINTK ("UDMA tack = %d\n", tack); + DPRINTK ("UDMA tzah = %d\n", tzah); +*/ + timing->udma1 = (t2cyc << 24) | (tcyc << 16) | (tds << 8) | (tdh); + timing->udma2 = (tdvs << 24) | (tdvh << 16) | (tfs << 8) | (tli); + timing->udma3 = (tmli << 24) | (taz << 16) | (tenv << 8) | (tsr); + timing->udma4 = (tss << 24) | (trfs << 16) | (trp << 8) | (tack); + timing->udma5 = (tzah << 24); + + timing->using_udma = 1; + + return 0; +} + static void mpc52xx_ata_apply_timings(struct mpc52xx_ata_priv *priv, int device) { @@ -173,13 +393,13 @@ mpc52xx_ata_apply_timings(struct mpc52xx out_be32(®s->pio1, timing->pio1); out_be32(®s->pio2, timing->pio2); - out_be32(®s->mdma1, 0); - out_be32(®s->mdma2, 0); - out_be32(®s->udma1, 0); - out_be32(®s->udma2, 0); - out_be32(®s->udma3, 0); - out_be32(®s->udma4, 0); - out_be32(®s->udma5, 0); + out_be32(®s->mdma1, timing->mdma1); + out_be32(®s->mdma2, timing->mdma2); + out_be32(®s->udma1, timing->udma1); + out_be32(®s->udma2, timing->udma2); + out_be32(®s->udma3, timing->udma3); + out_be32(®s->udma4, timing->udma4); + out_be32(®s->udma5, timing->udma5); priv->csel = device; } @@ -234,6 +454,7 @@ mpc52xx_ata_set_piomode(struct ata_port pio = adev->pio_mode - XFER_PIO_0; + // FIXME, can be called for dma mode? rv = mpc52xx_ata_compute_pio_timings(priv, adev->devno, pio); if (rv) { @@ -245,6 +466,28 @@ mpc52xx_ata_set_piomode(struct ata_port mpc52xx_ata_apply_timings(priv, adev->devno); } static void +mpc52xx_ata_set_dmamode(struct ata_port *ap, struct ata_device *adev) +{ + struct mpc52xx_ata_priv *priv = ap->host->private_data; + int rv; + + if (adev->dma_mode >= XFER_UDMA_0) { + int dma = adev->dma_mode - XFER_UDMA_0; + rv = mpc52xx_ata_compute_udma_timings(priv, adev->devno, dma); + } else { + int dma = adev->dma_mode - XFER_MW_DMA_0; + rv = mpc52xx_ata_compute_mdma_timings(priv, adev->devno, dma); + } + + if (rv) { + printk(KERN_ERR DRV_NAME + ": Trying to select invalid DMA mode %d\n", adev->dma_mode); + return; + } + + mpc52xx_ata_apply_timings(priv, adev->devno); +} +static void mpc52xx_ata_dev_select(struct ata_port *ap, unsigned int device) { struct mpc52xx_ata_priv *priv = ap->host->private_data; @@ -258,10 +501,174 @@ mpc52xx_ata_dev_select(struct ata_port * static void mpc52xx_ata_error_handler(struct ata_port *ap) { + struct mpc52xx_ata_priv *priv = ap->host->private_data; + struct mpc52xx_ata __iomem *regs = priv->ata_regs; + + printk(KERN_ALERT "%s: status: %08x; fifo_status_frame: %08x; fifo_status: %08x\n", + __func__, in_be32(®s->host_status), + in_be32(®s->fifo_status_frame), + in_be32(®s->fifo_status)); + ata_bmdma_drive_eh(ap, ata_std_prereset, ata_std_softreset, NULL, ata_std_postreset); } +static void +mpc52xx_ata_exec_command(struct ata_port *ap, const struct ata_taskfile *tf) +{ + struct mpc52xx_ata_priv *priv = ap->host->private_data; + + mpc52xx_ata_wait_tip_bit_clear(priv->ata_regs); + + ata_exec_command(ap, tf); +} + +static int +mpc52xx_ata_build_dmatable(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + struct mpc52xx_ata_priv *priv = ap->host->private_data; + struct mpc52xx_ata __iomem *regs = priv->ata_regs; + struct scatterlist *sg; + unsigned int read = !(qc->tf.flags & ATA_TFLAG_WRITE); + int count = 0; + + if (read) + bcom_ata_rx_prepare(priv->dmatsk); + else + bcom_ata_tx_prepare(priv->dmatsk); + + + ata_for_each_sg(sg, qc) { + u32 cur_addr = sg_dma_address(sg); + u32 cur_len = sg_dma_len(sg); + + while (cur_len) { + unsigned int tc = min(cur_len, MAX_DMA_BUFFER_SIZE); + struct bcom_ata_bd *bd; + + bd = (struct bcom_ata_bd *) + bcom_prepare_next_buffer(priv->dmatsk); + + if (read) { + bd->status = tc; + bd->dst_pa = cur_addr; + bd->src_pa = (__force u32)®s->fifo_data; // virt_to_phys? + } else { + bd->status = tc; + bd->dst_pa = (__force u32)®s->fifo_data; // virt_to_phys? + bd->src_pa = cur_addr; + + /* and how does bestcomm know to increase one, and not other? + * XXX TODO */ + } + + bcom_submit_next_buffer(priv->dmatsk, phys_to_virt(cur_addr)); // qc is just a cookie + + cur_addr += tc; + cur_len -= tc; + count++; + + if (count == MAX_DMA_BUFFERS) { + printk(KERN_ALERT "%s: %i dma table too small\n", + __func__, __LINE__); + goto use_pio_instead; + } + } + } + return 1; + + use_pio_instead: + bcom_ata_reset_bd(priv->dmatsk); + //pci_unmap_sg ??? + + return 0; +} + +static void +mpc52xx_bmdma_setup(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + struct mpc52xx_ata_priv *priv = ap->host->private_data; + struct mpc52xx_ata __iomem *regs = priv->ata_regs; + unsigned int read = !(qc->tf.flags & ATA_TFLAG_WRITE); + u8 dma_mode; +printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); + + if (!mpc52xx_ata_build_dmatable(qc)) { + //ide_map_sg(drive, rq); + printk(KERN_ALERT "%s: %i, return 1?\n", __func__, __LINE__); + } + + if (read) { + dma_mode = MPC52xx_ATA_DMAMODE_IE | MPC52xx_ATA_DMAMODE_READ | + MPC52xx_ATA_DMAMODE_FE; + + /* Setup FIFO if direction changed */ + if (priv->mpc52xx_ata_dma_last_write) { + priv->mpc52xx_ata_dma_last_write = 0; + mpc52xx_ata_wait_tip_bit_clear(regs); + out_8(®s->dma_mode, MPC52xx_ATA_DMAMODE_FR); + /* Configure it with granularity to 7 like sample code */ + out_8(®s->fifo_control, 7); + out_be16(®s->fifo_alarm, 128); + } + } else { + dma_mode = MPC52xx_ATA_DMAMODE_IE | MPC52xx_ATA_DMAMODE_WRITE; + + /* Setup FIFO if direction changed */ + if (!priv->mpc52xx_ata_dma_last_write) { + priv->mpc52xx_ata_dma_last_write = 1; + mpc52xx_ata_wait_tip_bit_clear(regs); + /* Configure FIFO with granularity to 4 like sample code */ + out_8(®s->fifo_control, 4); + //out_be16(®s->fifo_alarm, 256); + out_be16(®s->fifo_alarm, 128); + } + } + + if (priv->timings[qc->dev->devno].using_udma) + dma_mode |= MPC52xx_ATA_DMAMODE_UDMA; + + mpc52xx_ata_wait_tip_bit_clear(regs); + out_8(®s->dma_mode, dma_mode); + + ap->ops->exec_command(ap, &qc->tf); // added, ata_bmdma_setup has it +} + +static void +mpc52xx_bmdma_start(struct ata_queued_cmd *qc) +{ + struct ata_port *ap = qc->ap; + struct mpc52xx_ata_priv *priv = ap->host->private_data; +printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); + + //xlb_clear(); // WTF + bcom_enable(priv->dmatsk); +} + +static void +mpc52xx_bmdma_stop(struct ata_queued_cmd *qc) +{ + // uh? looks like destructor for setup not start + struct ata_port *ap = qc->ap; + struct mpc52xx_ata_priv *priv = ap->host->private_data; +printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); + + bcom_disable(priv->dmatsk); + //bcom_clear_irq(priv->dmatsk); //!! + bcom_ata_reset_bd(priv->dmatsk); + + // mpc52xx_ata_destroy_dmatable(); +} +// waiting_for_dma eliminated + +static u8 +mpc52xx_bmdma_status(struct ata_port *ap) +{ +printk(KERN_ALERT "%s: %i TODO\n", __func__, __LINE__); + return 0; +} static struct scsi_host_template mpc52xx_ata_sht = { @@ -282,25 +689,47 @@ static struct scsi_host_template mpc52xx .bios_param = ata_std_bios_param, }; +static void mpc52xx_ata_bmdma_freeze(struct ata_port *ap) +{ + printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); + ata_bmdma_freeze(ap); +} +static void mpc52xx_ata_bmdma_thaw(struct ata_port *ap) +{ + printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); + ata_bmdma_thaw(ap); +} +static void ata_dummy_noret(struct ata_port *ap) +{ +printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); +} static struct ata_port_operations mpc52xx_ata_port_ops = { .port_disable = ata_port_disable, .set_piomode = mpc52xx_ata_set_piomode, + .set_dmamode = mpc52xx_ata_set_dmamode, .dev_select = mpc52xx_ata_dev_select, .tf_load = ata_tf_load, .tf_read = ata_tf_read, .check_status = ata_check_status, - .exec_command = ata_exec_command, - .freeze = ata_bmdma_freeze, - .thaw = ata_bmdma_thaw, + .exec_command = mpc52xx_ata_exec_command, + .freeze = mpc52xx_ata_bmdma_freeze, + .thaw = mpc52xx_ata_bmdma_thaw, .error_handler = mpc52xx_ata_error_handler, .cable_detect = ata_cable_40wire, .qc_prep = ata_qc_prep, .qc_issue = ata_qc_issue_prot, .data_xfer = ata_data_xfer, - .irq_clear = ata_bmdma_irq_clear, +// .irq_clear = ata_bmdma_irq_clear, + .irq_clear = ata_dummy_noret, .irq_on = ata_irq_on, .irq_ack = ata_irq_ack, .port_start = ata_port_start, + .bmdma_setup = mpc52xx_bmdma_setup, + .bmdma_start = mpc52xx_bmdma_start, + .bmdma_stop = mpc52xx_bmdma_stop, + .bmdma_status = mpc52xx_bmdma_status, + +// not ide_dma_check int (*check_atapi_dma) (struct ata_queued_cmd *qc); }; static int __devinit @@ -309,7 +738,6 @@ mpc52xx_ata_init_one(struct device *dev, struct ata_host *host; struct ata_port *ap; struct ata_ioports *aio; - int rc; host = ata_host_alloc(dev, 1); if (!host) @@ -317,9 +745,9 @@ mpc52xx_ata_init_one(struct device *dev, ap = host->ports[0]; ap->flags |= ATA_FLAG_SLAVE_POSS; - ap->pio_mask = 0x1f; /* Up to PIO4 */ - ap->mwdma_mask = 0x00; /* No MWDMA */ - ap->udma_mask = 0x00; /* No UDMA */ + ap->pio_mask = ATA_PIO4; /* Up to PIO4 */ + ap->mwdma_mask = 0x07; /* Up to MWDMA2 */ + ap->udma_mask = ATA_UDMA2; /* Up to UDMA2 */ ap->ops = &mpc52xx_ata_port_ops; host->private_data = priv; @@ -359,6 +787,15 @@ mpc52xx_ata_remove_one(struct device *de /* OF Platform driver */ /* ======================================================================== */ +static irqreturn_t +mpc52xx_ata_task_irq(int irq, void *vpriv) +{ + struct mpc52xx_ata_priv *priv = vpriv; +printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); + + return IRQ_HANDLED; +} + static int __devinit mpc52xx_ata_probe(struct of_device *op, const struct of_device_id *match) { @@ -426,6 +863,30 @@ mpc52xx_ata_probe(struct of_device *op, priv->ata_irq = ata_irq; priv->csel = -1; + if (ipb_freq/1000000 == 66) { + priv->mdmaspec = &mdmaspec66; + priv->udmaspec = &udmaspec66; + } else { + priv->mdmaspec = &mdmaspec132; + priv->udmaspec = &udmaspec132; + } + + priv->dmatsk = bcom_ata_init(MAX_DMA_BUFFERS, MAX_DMA_BUFFER_SIZE); + if (!priv->dmatsk) { + printk(KERN_ALERT "%s: %i\n", __func__, __LINE__); + rv = -ENOMEM; + goto err; + } + // XXX task irq? nope, it doesn't get any irq's + { + int ret; + int task_irq = bcom_get_task_irq(priv->dmatsk); + printk(KERN_ALERT "%s: ata task irq: %i\n", __func__, task_irq); + ret = request_irq(task_irq, &mpc52xx_ata_task_irq, IRQF_DISABLED, "ata task", priv); + if (ret) + printk(KERN_ALERT "%s: request_irq failed with: %i\n", __func__, ret); + } + /* Init the hw */ rv = mpc52xx_ata_hw_init(priv); if (rv) { Index: work-powerpc.git/include/linux/libata.h =================================================================== --- work-powerpc.git.orig/include/linux/libata.h +++ work-powerpc.git/include/linux/libata.h @@ -51,7 +51,7 @@ * compile-time options: to be removed as soon as all the drivers are * converted to the new debugging mechanism */ -#undef ATA_DEBUG /* debugging output */ +#define ATA_DEBUG /* debugging output */ #undef ATA_VERBOSE_DEBUG /* yet more debugging output */ #undef ATA_IRQ_TRAP /* define to ack screaming irqs */ #undef ATA_NDEBUG /* define to disable quick runtime checks */ _______________________________________________ Linuxppc-embedded mailing list Linuxppc-embedded@ozlabs.org https://ozlabs.org/mailman/listinfo/linuxppc-embedded