From 0d831770b154a057562236e8cf50905c8f1ae1b0 Mon Sep 17 00:00:00 2001 From: Paul Mundt Date: Mon, 16 Jan 2006 22:14:09 -0800 Subject: [PATCH] sh: DMA updates This extends the current SH DMA API somewhat to support a proper virtual channel abstraction, and also works to represent this through the driver model by giving each DMAC its own platform device. There's also a few other minor changes to support a few new CPU subtypes, and make TEI generation for the SH DMAC configurable. Signed-off-by: Paul Mundt Signed-off-by: Andrew Morton Signed-off-by: Linus Torvalds --- arch/sh/drivers/dma/dma-api.c | 51 ++++++++++----- arch/sh/drivers/dma/dma-g2.c | 3 +- arch/sh/drivers/dma/dma-isa.c | 20 +++--- arch/sh/drivers/dma/dma-pvr2.c | 3 +- arch/sh/drivers/dma/dma-sh.c | 134 ++++++++++++++++++++++++---------------- arch/sh/drivers/dma/dma-sh.h | 44 +++++++++---- arch/sh/drivers/dma/dma-sysfs.c | 31 ++++++++-- arch/sh/kernel/cpu/bus.c | 2 + 8 files changed, 194 insertions(+), 94 deletions(-) (limited to 'arch/sh') diff --git a/arch/sh/drivers/dma/dma-api.c b/arch/sh/drivers/dma/dma-api.c index 96e3036ec2b..47c3e837599 100644 --- a/arch/sh/drivers/dma/dma-api.c +++ b/arch/sh/drivers/dma/dma-api.c @@ -3,7 +3,7 @@ * * SuperH-specific DMA management API * - * Copyright (C) 2003, 2004 Paul Mundt + * Copyright (C) 2003, 2004, 2005 Paul Mundt * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive @@ -15,6 +15,7 @@ #include #include #include +#include #include DEFINE_SPINLOCK(dma_spin_lock); @@ -55,16 +56,14 @@ static LIST_HEAD(registered_dmac_list); struct dma_info *get_dma_info(unsigned int chan) { - struct list_head *pos, *tmp; + struct dma_info *info; unsigned int total = 0; /* * Look for each DMAC's range to determine who the owner of * the channel is. */ - list_for_each_safe(pos, tmp, ®istered_dmac_list) { - struct dma_info *info = list_entry(pos, struct dma_info, list); - + list_for_each_entry(info, ®istered_dmac_list, list) { total += info->nr_channels; if (chan > total) continue; @@ -75,6 +74,20 @@ struct dma_info *get_dma_info(unsigned int chan) return NULL; } +static unsigned int get_nr_channels(void) +{ + struct dma_info *info; + unsigned int nr = 0; + + if (unlikely(list_empty(®istered_dmac_list))) + return nr; + + list_for_each_entry(info, ®istered_dmac_list, list) + nr += info->nr_channels; + + return nr; +} + struct dma_channel *get_dma_channel(unsigned int chan) { struct dma_info *info = get_dma_info(chan); @@ -173,7 +186,7 @@ int dma_xfer(unsigned int chan, unsigned long from, static int dma_read_proc(char *buf, char **start, off_t off, int len, int *eof, void *data) { - struct list_head *pos, *tmp; + struct dma_info *info; char *p = buf; if (list_empty(®istered_dmac_list)) @@ -182,8 +195,7 @@ static int dma_read_proc(char *buf, char **start, off_t off, /* * Iterate over each registered DMAC */ - list_for_each_safe(pos, tmp, ®istered_dmac_list) { - struct dma_info *info = list_entry(pos, struct dma_info, list); + list_for_each_entry(info, ®istered_dmac_list, list) { int i; /* @@ -205,9 +217,9 @@ static int dma_read_proc(char *buf, char **start, off_t off, #endif -int __init register_dmac(struct dma_info *info) +int register_dmac(struct dma_info *info) { - int i; + unsigned int total_channels, i; INIT_LIST_HEAD(&info->list); @@ -217,6 +229,11 @@ int __init register_dmac(struct dma_info *info) BUG_ON((info->flags & DMAC_CHANNELS_CONFIGURED) && !info->channels); + info->pdev = platform_device_register_simple((char *)info->name, -1, + NULL, 0); + if (IS_ERR(info->pdev)) + return PTR_ERR(info->pdev); + /* * Don't touch pre-configured channels */ @@ -232,10 +249,12 @@ int __init register_dmac(struct dma_info *info) memset(info->channels, 0, size); } + total_channels = get_nr_channels(); for (i = 0; i < info->nr_channels; i++) { struct dma_channel *chan = info->channels + i; chan->chan = i; + chan->vchan = i + total_channels; memcpy(chan->dev_id, "Unused", 7); @@ -245,9 +264,7 @@ int __init register_dmac(struct dma_info *info) init_MUTEX(&chan->sem); init_waitqueue_head(&chan->wait_queue); -#ifdef CONFIG_SYSFS - dma_create_sysfs_files(chan); -#endif + dma_create_sysfs_files(chan, info); } list_add(&info->list, ®istered_dmac_list); @@ -255,12 +272,18 @@ int __init register_dmac(struct dma_info *info) return 0; } -void __exit unregister_dmac(struct dma_info *info) +void unregister_dmac(struct dma_info *info) { + unsigned int i; + + for (i = 0; i < info->nr_channels; i++) + dma_remove_sysfs_files(info->channels + i, info); + if (!(info->flags & DMAC_CHANNELS_CONFIGURED)) kfree(info->channels); list_del(&info->list); + platform_device_unregister(info->pdev); } static int __init dma_api_init(void) diff --git a/arch/sh/drivers/dma/dma-g2.c b/arch/sh/drivers/dma/dma-g2.c index 231e3f6fb28..5afab6f56ec 100644 --- a/arch/sh/drivers/dma/dma-g2.c +++ b/arch/sh/drivers/dma/dma-g2.c @@ -140,7 +140,7 @@ static struct dma_ops g2_dma_ops = { }; static struct dma_info g2_dma_info = { - .name = "G2 DMA", + .name = "g2_dmac", .nr_channels = 4, .ops = &g2_dma_ops, .flags = DMAC_CHANNELS_TEI_CAPABLE, @@ -160,6 +160,7 @@ static int __init g2_dma_init(void) static void __exit g2_dma_exit(void) { free_irq(HW_EVENT_G2_DMA, 0); + unregister_dmac(&g2_dma_info); } subsys_initcall(g2_dma_init); diff --git a/arch/sh/drivers/dma/dma-isa.c b/arch/sh/drivers/dma/dma-isa.c index 1c9bc45b8bc..05a74ffdb68 100644 --- a/arch/sh/drivers/dma/dma-isa.c +++ b/arch/sh/drivers/dma/dma-isa.c @@ -25,14 +25,14 @@ * such, this code is meant for only the simplest of tasks (and shouldn't be * used in any new drivers at all). * - * It should also be noted that various functions here are labelled as - * being deprecated. This is due to the fact that the ops->xfer() method is - * the preferred way of doing things (as well as just grabbing the spinlock - * directly). As such, any users of this interface will be warned rather - * loudly. + * NOTE: ops->xfer() is the preferred way of doing things. However, there + * are some users of the ISA DMA API that exist in common code that we + * don't necessarily want to go out of our way to break, so we still + * allow for some compatability at that level. Any new code is strongly + * advised to run far away from the ISA DMA API and use the SH DMA API + * directly. */ - -unsigned long __deprecated claim_dma_lock(void) +unsigned long claim_dma_lock(void) { unsigned long flags; @@ -42,19 +42,19 @@ unsigned long __deprecated claim_dma_lock(void) } EXPORT_SYMBOL(claim_dma_lock); -void __deprecated release_dma_lock(unsigned long flags) +void release_dma_lock(unsigned long flags) { spin_unlock_irqrestore(&dma_spin_lock, flags); } EXPORT_SYMBOL(release_dma_lock); -void __deprecated disable_dma(unsigned int chan) +void disable_dma(unsigned int chan) { /* Nothing */ } EXPORT_SYMBOL(disable_dma); -void __deprecated enable_dma(unsigned int chan) +void enable_dma(unsigned int chan) { struct dma_info *info = get_dma_info(chan); struct dma_channel *channel = &info->channels[chan]; diff --git a/arch/sh/drivers/dma/dma-pvr2.c b/arch/sh/drivers/dma/dma-pvr2.c index 2e1d58f2d1b..df604975ccc 100644 --- a/arch/sh/drivers/dma/dma-pvr2.c +++ b/arch/sh/drivers/dma/dma-pvr2.c @@ -80,7 +80,7 @@ static struct dma_ops pvr2_dma_ops = { }; static struct dma_info pvr2_dma_info = { - .name = "PowerVR 2 DMA", + .name = "pvr2_dmac", .nr_channels = 1, .ops = &pvr2_dma_ops, .flags = DMAC_CHANNELS_TEI_CAPABLE, @@ -98,6 +98,7 @@ static void __exit pvr2_dma_exit(void) { free_dma(PVR2_CASCADE_CHAN); free_irq(HW_EVENT_PVR2_DMA, 0); + unregister_dmac(&pvr2_dma_info); } subsys_initcall(pvr2_dma_init); diff --git a/arch/sh/drivers/dma/dma-sh.c b/arch/sh/drivers/dma/dma-sh.c index 31dacd4444b..cca26c4c9d1 100644 --- a/arch/sh/drivers/dma/dma-sh.c +++ b/arch/sh/drivers/dma/dma-sh.c @@ -5,6 +5,7 @@ * * Copyright (C) 2000 Takashi YOSHII * Copyright (C) 2003, 2004 Paul Mundt + * Copyright (C) 2005 Andriy Skulysh * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive @@ -16,51 +17,28 @@ #include #include #include +#include #include #include #include #include #include "dma-sh.h" -/* - * The SuperH DMAC supports a number of transmit sizes, we list them here, - * with their respective values as they appear in the CHCR registers. - * - * Defaults to a 64-bit transfer size. - */ -enum { - XMIT_SZ_64BIT, - XMIT_SZ_8BIT, - XMIT_SZ_16BIT, - XMIT_SZ_32BIT, - XMIT_SZ_256BIT, -}; - -/* - * The DMA count is defined as the number of bytes to transfer. - */ -static unsigned int ts_shift[] = { - [XMIT_SZ_64BIT] = 3, - [XMIT_SZ_8BIT] = 0, - [XMIT_SZ_16BIT] = 1, - [XMIT_SZ_32BIT] = 2, - [XMIT_SZ_256BIT] = 5, -}; - static inline unsigned int get_dmte_irq(unsigned int chan) { - unsigned int irq; + unsigned int irq = 0; /* * Normally we could just do DMTE0_IRQ + chan outright, though in the * case of the 7751R, the DMTE IRQs for channels > 4 start right above * the SCIF */ - if (chan < 4) { irq = DMTE0_IRQ + chan; } else { +#ifdef DMTE4_IRQ irq = DMTE4_IRQ + chan - 4; +#endif } return irq; @@ -78,9 +56,7 @@ static inline unsigned int calc_xmit_shift(struct dma_channel *chan) { u32 chcr = ctrl_inl(CHCR[chan->chan]); - chcr >>= 4; - - return ts_shift[chcr & 0x0007]; + return ts_shift[(chcr & CHCR_TS_MASK)>>CHCR_TS_SHIFT]; } /* @@ -109,8 +85,13 @@ static irqreturn_t dma_tei(int irq, void *dev_id, struct pt_regs *regs) static int sh_dmac_request_dma(struct dma_channel *chan) { + char name[32]; + + snprintf(name, sizeof(name), "DMAC Transfer End (Channel %d)", + chan->chan); + return request_irq(get_dmte_irq(chan->chan), dma_tei, - SA_INTERRUPT, "DMAC Transfer End", chan); + SA_INTERRUPT, name, chan); } static void sh_dmac_free_dma(struct dma_channel *chan) @@ -118,10 +99,18 @@ static void sh_dmac_free_dma(struct dma_channel *chan) free_irq(get_dmte_irq(chan->chan), chan); } -static void sh_dmac_configure_channel(struct dma_channel *chan, unsigned long chcr) +static void +sh_dmac_configure_channel(struct dma_channel *chan, unsigned long chcr) { if (!chcr) - chcr = RS_DUAL; + chcr = RS_DUAL | CHCR_IE; + + if (chcr & CHCR_IE) { + chcr &= ~CHCR_IE; + chan->flags |= DMA_TEI_CAPABLE; + } else { + chan->flags &= ~DMA_TEI_CAPABLE; + } ctrl_outl(chcr, CHCR[chan->chan]); @@ -130,22 +119,32 @@ static void sh_dmac_configure_channel(struct dma_channel *chan, unsigned long ch static void sh_dmac_enable_dma(struct dma_channel *chan) { - int irq = get_dmte_irq(chan->chan); + int irq; u32 chcr; chcr = ctrl_inl(CHCR[chan->chan]); - chcr |= CHCR_DE | CHCR_IE; + chcr |= CHCR_DE; + + if (chan->flags & DMA_TEI_CAPABLE) + chcr |= CHCR_IE; + ctrl_outl(chcr, CHCR[chan->chan]); - enable_irq(irq); + if (chan->flags & DMA_TEI_CAPABLE) { + irq = get_dmte_irq(chan->chan); + enable_irq(irq); + } } static void sh_dmac_disable_dma(struct dma_channel *chan) { - int irq = get_dmte_irq(chan->chan); + int irq; u32 chcr; - disable_irq(irq); + if (chan->flags & DMA_TEI_CAPABLE) { + irq = get_dmte_irq(chan->chan); + disable_irq(irq); + } chcr = ctrl_inl(CHCR[chan->chan]); chcr &= ~(CHCR_DE | CHCR_TE | CHCR_IE); @@ -158,7 +157,7 @@ static int sh_dmac_xfer_dma(struct dma_channel *chan) * If we haven't pre-configured the channel with special flags, use * the defaults. */ - if (!(chan->flags & DMA_CONFIGURED)) + if (unlikely(!(chan->flags & DMA_CONFIGURED))) sh_dmac_configure_channel(chan, 0); sh_dmac_disable_dma(chan); @@ -178,9 +177,11 @@ static int sh_dmac_xfer_dma(struct dma_channel *chan) * cascading to the PVR2 DMAC. In this case, we still need to write * SAR and DAR, regardless of value, in order for cascading to work. */ - if (chan->sar || (mach_is_dreamcast() && chan->chan == 2)) + if (chan->sar || (mach_is_dreamcast() && + chan->chan == PVR2_CASCADE_CHAN)) ctrl_outl(chan->sar, SAR[chan->chan]); - if (chan->dar || (mach_is_dreamcast() && chan->chan == 2)) + if (chan->dar || (mach_is_dreamcast() && + chan->chan == PVR2_CASCADE_CHAN)) ctrl_outl(chan->dar, DAR[chan->chan]); ctrl_outl(chan->count >> calc_xmit_shift(chan), DMATCR[chan->chan]); @@ -198,17 +199,38 @@ static int sh_dmac_get_dma_residue(struct dma_channel *chan) return ctrl_inl(DMATCR[chan->chan]) << calc_xmit_shift(chan); } -#if defined(CONFIG_CPU_SH4) -static irqreturn_t dma_err(int irq, void *dev_id, struct pt_regs *regs) +#ifdef CONFIG_CPU_SUBTYPE_SH7780 +#define dmaor_read_reg() ctrl_inw(DMAOR) +#define dmaor_write_reg(data) ctrl_outw(data, DMAOR) +#else +#define dmaor_read_reg() ctrl_inl(DMAOR) +#define dmaor_write_reg(data) ctrl_outl(data, DMAOR) +#endif + +static inline int dmaor_reset(void) { - unsigned long dmaor = ctrl_inl(DMAOR); + unsigned long dmaor = dmaor_read_reg(); + + /* Try to clear the error flags first, incase they are set */ + dmaor &= ~(DMAOR_NMIF | DMAOR_AE); + dmaor_write_reg(dmaor); - printk("DMAE: DMAOR=%lx\n", dmaor); + dmaor |= DMAOR_INIT; + dmaor_write_reg(dmaor); - ctrl_outl(ctrl_inl(DMAOR)&~DMAOR_NMIF, DMAOR); - ctrl_outl(ctrl_inl(DMAOR)&~DMAOR_AE, DMAOR); - ctrl_outl(ctrl_inl(DMAOR)|DMAOR_DME, DMAOR); + /* See if we got an error again */ + if ((dmaor_read_reg() & (DMAOR_AE | DMAOR_NMIF))) { + printk(KERN_ERR "dma-sh: Can't initialize DMAOR.\n"); + return -EINVAL; + } + return 0; +} + +#if defined(CONFIG_CPU_SH4) +static irqreturn_t dma_err(int irq, void *dev_id, struct pt_regs *regs) +{ + dmaor_reset(); disable_irq(irq); return IRQ_HANDLED; @@ -224,8 +246,8 @@ static struct dma_ops sh_dmac_ops = { }; static struct dma_info sh_dmac_info = { - .name = "SuperH DMAC", - .nr_channels = 4, + .name = "sh_dmac", + .nr_channels = CONFIG_NR_ONCHIP_DMA_CHANNELS, .ops = &sh_dmac_ops, .flags = DMAC_CHANNELS_TEI_CAPABLE, }; @@ -248,7 +270,13 @@ static int __init sh_dmac_init(void) make_ipr_irq(irq, DMA_IPR_ADDR, DMA_IPR_POS, DMA_PRIORITY); } - ctrl_outl(0x8000 | DMAOR_DME, DMAOR); + /* + * Initialize DMAOR, and clean up any error flags that may have + * been set. + */ + i = dmaor_reset(); + if (i < 0) + return i; return register_dmac(info); } @@ -258,10 +286,12 @@ static void __exit sh_dmac_exit(void) #ifdef CONFIG_CPU_SH4 free_irq(DMAE_IRQ, 0); #endif + unregister_dmac(&sh_dmac_info); } subsys_initcall(sh_dmac_init); module_exit(sh_dmac_exit); +MODULE_AUTHOR("Takashi YOSHII, Paul Mundt, Andriy Skulysh"); +MODULE_DESCRIPTION("SuperH On-Chip DMAC Support"); MODULE_LICENSE("GPL"); - diff --git a/arch/sh/drivers/dma/dma-sh.h b/arch/sh/drivers/dma/dma-sh.h index dd9d547539a..0f591fbc922 100644 --- a/arch/sh/drivers/dma/dma-sh.h +++ b/arch/sh/drivers/dma/dma-sh.h @@ -11,6 +11,8 @@ #ifndef __DMA_SH_H #define __DMA_SH_H +#include + /* Definitions for the SuperH DMAC */ #define REQ_L 0x00000000 #define REQ_E 0x00080000 @@ -26,27 +28,47 @@ #define SM_DEC 0x00002000 #define RS_IN 0x00000200 #define RS_OUT 0x00000300 -#define TM_BURST 0x0000080 -#define TS_8 0x00000010 -#define TS_16 0x00000020 -#define TS_32 0x00000030 -#define TS_64 0x00000000 #define TS_BLK 0x00000040 #define CHCR_DE 0x00000001 #define CHCR_TE 0x00000002 #define CHCR_IE 0x00000004 -/* Define the default configuration for dual address memory-memory transfer. - * The 0x400 value represents auto-request, external->external. - */ -#define RS_DUAL (DM_INC | SM_INC | 0x400 | TS_32) - -#define DMAOR_COD 0x00000008 +/* DMAOR definitions */ #define DMAOR_AE 0x00000004 #define DMAOR_NMIF 0x00000002 #define DMAOR_DME 0x00000001 +/* + * Define the default configuration for dual address memory-memory transfer. + * The 0x400 value represents auto-request, external->external. + */ +#define RS_DUAL (DM_INC | SM_INC | 0x400 | TS_32) + #define MAX_DMAC_CHANNELS (CONFIG_NR_ONCHIP_DMA_CHANNELS) +/* + * Subtypes that have fewer channels than this simply need to change + * CONFIG_NR_ONCHIP_DMA_CHANNELS. Likewise, subtypes with a larger number + * of channels should expand on this. + * + * For most subtypes we can easily figure these values out with some + * basic calculation, unfortunately on other subtypes these are more + * scattered, so we just leave it unrolled for simplicity. + */ +#define SAR ((unsigned long[]){SH_DMAC_BASE + 0x00, SH_DMAC_BASE + 0x10, \ + SH_DMAC_BASE + 0x20, SH_DMAC_BASE + 0x30, \ + SH_DMAC_BASE + 0x50, SH_DMAC_BASE + 0x60}) +#define DAR ((unsigned long[]){SH_DMAC_BASE + 0x04, SH_DMAC_BASE + 0x14, \ + SH_DMAC_BASE + 0x24, SH_DMAC_BASE + 0x34, \ + SH_DMAC_BASE + 0x54, SH_DMAC_BASE + 0x64}) +#define DMATCR ((unsigned long[]){SH_DMAC_BASE + 0x08, SH_DMAC_BASE + 0x18, \ + SH_DMAC_BASE + 0x28, SH_DMAC_BASE + 0x38, \ + SH_DMAC_BASE + 0x58, SH_DMAC_BASE + 0x68}) +#define CHCR ((unsigned long[]){SH_DMAC_BASE + 0x0c, SH_DMAC_BASE + 0x1c, \ + SH_DMAC_BASE + 0x2c, SH_DMAC_BASE + 0x3c, \ + SH_DMAC_BASE + 0x5c, SH_DMAC_BASE + 0x6c}) + +#define DMAOR (SH_DMAC_BASE + 0x40) + #endif /* __DMA_SH_H */ diff --git a/arch/sh/drivers/dma/dma-sysfs.c b/arch/sh/drivers/dma/dma-sysfs.c index 6e3b58bd879..70a5d82eb2f 100644 --- a/arch/sh/drivers/dma/dma-sysfs.c +++ b/arch/sh/drivers/dma/dma-sysfs.c @@ -3,7 +3,7 @@ * * sysfs interface for SH DMA API * - * Copyright (C) 2004 Paul Mundt + * Copyright (C) 2004, 2005 Paul Mundt * * This file is subject to the terms and conditions of the GNU General Public * License. See the file "COPYING" in the main directory of this archive @@ -12,7 +12,9 @@ #include #include #include +#include #include +#include #include #include @@ -77,7 +79,7 @@ static ssize_t dma_store_config(struct sys_device *dev, unsigned long config; config = simple_strtoul(buf, NULL, 0); - dma_configure_channel(channel->chan, config); + dma_configure_channel(channel->vchan, config); return count; } @@ -111,12 +113,13 @@ static SYSDEV_ATTR(field, S_IRUGO, dma_show_##field, NULL); dma_ro_attr(count, "0x%08x\n"); dma_ro_attr(flags, "0x%08lx\n"); -int __init dma_create_sysfs_files(struct dma_channel *chan) +int dma_create_sysfs_files(struct dma_channel *chan, struct dma_info *info) { struct sys_device *dev = &chan->dev; + char name[16]; int ret; - dev->id = chan->chan; + dev->id = chan->vchan; dev->cls = &dma_sysclass; ret = sysdev_register(dev); @@ -129,6 +132,24 @@ int __init dma_create_sysfs_files(struct dma_channel *chan) sysdev_create_file(dev, &attr_flags); sysdev_create_file(dev, &attr_config); - return 0; + snprintf(name, sizeof(name), "dma%d", chan->chan); + return sysfs_create_link(&info->pdev->dev.kobj, &dev->kobj, name); +} + +void dma_remove_sysfs_files(struct dma_channel *chan, struct dma_info *info) +{ + struct sys_device *dev = &chan->dev; + char name[16]; + + sysdev_remove_file(dev, &attr_dev_id); + sysdev_remove_file(dev, &attr_count); + sysdev_remove_file(dev, &attr_mode); + sysdev_remove_file(dev, &attr_flags); + sysdev_remove_file(dev, &attr_config); + + snprintf(name, sizeof(name), "dma%d", chan->chan); + sysfs_remove_link(&info->pdev->dev.kobj, name); + + sysdev_unregister(dev); } diff --git a/arch/sh/kernel/cpu/bus.c b/arch/sh/kernel/cpu/bus.c index 3278d234bb1..fc6c4bd40c6 100644 --- a/arch/sh/kernel/cpu/bus.c +++ b/arch/sh/kernel/cpu/bus.c @@ -109,6 +109,8 @@ int sh_device_register(struct sh_dev *dev) /* This is needed for USB OHCI to work */ if (dev->dma_mask) dev->dev.dma_mask = dev->dma_mask; + if (dev->coherent_dma_mask) + dev->dev.coherent_dma_mask = dev->coherent_dma_mask; snprintf(dev->dev.bus_id, BUS_ID_SIZE, "%s%u", dev->name, dev->dev_id); -- cgit v1.2.3-70-g09d2