Received: by 2002:ac0:a581:0:0:0:0:0 with SMTP id m1-v6csp3846720imm; Mon, 25 Jun 2018 05:42:06 -0700 (PDT) X-Google-Smtp-Source: ADUXVKIu+3shR04jlkhY8+uulqNO9xrg90is7OFD35q7B3SPeQQeXWYhOjvufQ0PFvTTjTYjCoAU X-Received: by 2002:a63:b109:: with SMTP id r9-v6mr10695976pgf.102.1529930526118; Mon, 25 Jun 2018 05:42:06 -0700 (PDT) ARC-Seal: i=1; a=rsa-sha256; t=1529930526; cv=none; d=google.com; s=arc-20160816; b=YIsB68gyLcr0ZlwUcKOU9i7WWR0n3TItXZQGOC4vQXwdVKC+EdbL9fzCTtb/ngztRt px1814z8BBu7dw5HaypM5uTl3bq+mDdEp8pFCSnYvbnP5XEncwBRZ4aBAsbsEnZVJNpR REIpvFX26fsqNG0KgPhm68VTBu06dTjxVSWyOyKJI8Jk0drf9xh6C05ZDVLaU6zV8Un5 RTbbK7mu6OOy/efb+N5i9wmwGsN7t4VG3cA8UMtmTgcvmr9uX/JXSE4jR0oRjsJkPglH JvSJhPuq5HHyKlCPCU8zD0DDL5CYl9RfNohPzX49CMpW5dvGW1f1gkpNsZhzDhnivn5j 5iUQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=google.com; s=arc-20160816; h=list-id:precedence:sender:references:in-reply-to:message-id:date :subject:cc:to:from:arc-authentication-results; bh=W35n39vmeTI22453tafWMQ+w/68wHL4U8wGiWcSTtIc=; b=t6Jh6lTZrEYNs9uT8pIb6XMcqIa0k19A7KdpOqV8tATgLlm70KtXJ9xc7/Qe3qk0Fq fCjBiD/wtAMhNZ8RkGVeIY/wzGWtwi/iajn8Xl/94w13QGzMAiEMzgnK0+MjsQE6iv7K JjdnhDsNv1PqzohZ9KaFmfAgpUGJ6ht8aVAjWV1QwqJCokGDDi20ZNO3R0CuHVGtzcuE 4MYMaOAqt/9xX36fJHGN//owg8GkT3MGwfeqOZo7lVO/Y6KiAXVLorgtzLvFRs4WDSA6 NJYmMBFD8cz1fXhio4zJUidTxxpl+DUzKq1CHlnn2Nihj+VQtwu6tS02cAwmVq/gJf8h uxgQ== ARC-Authentication-Results: i=1; mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=nxp.com Return-Path: Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id n1-v6si2819279pgv.641.2018.06.25.05.41.51; Mon, 25 Jun 2018 05:42:06 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) smtp.mailfrom=linux-kernel-owner@vger.kernel.org; dmarc=fail (p=NONE sp=NONE dis=NONE) header.from=nxp.com Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933865AbeFYMjU (ORCPT + 99 others); Mon, 25 Jun 2018 08:39:20 -0400 Received: from inva021.nxp.com ([92.121.34.21]:42040 "EHLO inva021.nxp.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S933787AbeFYMjR (ORCPT ); Mon, 25 Jun 2018 08:39:17 -0400 Received: from inva021.nxp.com (localhost [127.0.0.1]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 884C420008B; Mon, 25 Jun 2018 14:39:15 +0200 (CEST) Received: from smtp.na-rdc02.nxp.com (inv1260.us-phx01.nxp.com [134.27.49.11]) by inva021.eu-rdc02.nxp.com (Postfix) with ESMTP id 116D0200078; Mon, 25 Jun 2018 14:39:15 +0200 (CEST) Received: from az84smr01.freescale.net (az84smr01.freescale.net [10.64.34.197]) by inv1260.na-rdc02.nxp.com (Postfix) with ESMTP id 6E69340A70; Mon, 25 Jun 2018 05:39:14 -0700 (MST) Received: from titan.ap.freescale.net ([10.192.208.233]) by az84smr01.freescale.net (8.14.3/8.14.0) with ESMTP id w5PCd34N025070; Mon, 25 Jun 2018 05:39:11 -0700 From: Yangbo Lu To: netdev@vger.kernel.org, madalin.bucur@nxp.com, Richard Cochran , Rob Herring , Shawn Guo , "David S . Miller" Cc: devicetree@vger.kernel.org, linuxppc-dev@lists.ozlabs.org, linux-arm-kernel@lists.infradead.org, linux-kernel@vger.kernel.org, Yangbo Lu Subject: [v4, 02/10] ptp: support DPAA FMan 1588 timer in ptp_qoriq Date: Mon, 25 Jun 2018 20:37:08 +0800 Message-Id: <20180625123716.28993-3-yangbo.lu@nxp.com> X-Mailer: git-send-email 2.14.1 In-Reply-To: <20180625123716.28993-1-yangbo.lu@nxp.com> References: <20180625123716.28993-1-yangbo.lu@nxp.com> X-Virus-Scanned: ClamAV using ClamSMTP Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org This patch is to support DPAA (Data Path Acceleration Architecture) 1588 timer by adding "fsl,fman-ptp-timer" compatible, sharing interrupt with FMan, adding FSL_DPAA_ETH dependency, and fixing up register offset. Signed-off-by: Yangbo Lu Acked-by: Richard Cochran Acked-by: Madalin Bucur --- Changes for v2: - None. Changes for v3: - None. Changes for v4: - Added ACKs. --- drivers/ptp/Kconfig | 2 +- drivers/ptp/ptp_qoriq.c | 104 ++++++++++++++++++++++++++--------------- include/linux/fsl/ptp_qoriq.h | 38 ++++++++++++--- 3 files changed, 98 insertions(+), 46 deletions(-) diff --git a/drivers/ptp/Kconfig b/drivers/ptp/Kconfig index 474c988..d137c48 100644 --- a/drivers/ptp/Kconfig +++ b/drivers/ptp/Kconfig @@ -43,7 +43,7 @@ config PTP_1588_CLOCK_DTE config PTP_1588_CLOCK_QORIQ tristate "Freescale QorIQ 1588 timer as PTP clock" - depends on GIANFAR + depends on GIANFAR || FSL_DPAA_ETH depends on PTP_1588_CLOCK default y help diff --git a/drivers/ptp/ptp_qoriq.c b/drivers/ptp/ptp_qoriq.c index e8652c1..a14c317 100644 --- a/drivers/ptp/ptp_qoriq.c +++ b/drivers/ptp/ptp_qoriq.c @@ -39,11 +39,12 @@ /* Caller must hold qoriq_ptp->lock. */ static u64 tmr_cnt_read(struct qoriq_ptp *qoriq_ptp) { + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; u64 ns; u32 lo, hi; - lo = qoriq_read(&qoriq_ptp->regs->tmr_cnt_l); - hi = qoriq_read(&qoriq_ptp->regs->tmr_cnt_h); + lo = qoriq_read(®s->ctrl_regs->tmr_cnt_l); + hi = qoriq_read(®s->ctrl_regs->tmr_cnt_h); ns = ((u64) hi) << 32; ns |= lo; return ns; @@ -52,16 +53,18 @@ static u64 tmr_cnt_read(struct qoriq_ptp *qoriq_ptp) /* Caller must hold qoriq_ptp->lock. */ static void tmr_cnt_write(struct qoriq_ptp *qoriq_ptp, u64 ns) { + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; u32 hi = ns >> 32; u32 lo = ns & 0xffffffff; - qoriq_write(&qoriq_ptp->regs->tmr_cnt_l, lo); - qoriq_write(&qoriq_ptp->regs->tmr_cnt_h, hi); + qoriq_write(®s->ctrl_regs->tmr_cnt_l, lo); + qoriq_write(®s->ctrl_regs->tmr_cnt_h, hi); } /* Caller must hold qoriq_ptp->lock. */ static void set_alarm(struct qoriq_ptp *qoriq_ptp) { + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; u64 ns; u32 lo, hi; @@ -70,16 +73,18 @@ static void set_alarm(struct qoriq_ptp *qoriq_ptp) ns -= qoriq_ptp->tclk_period; hi = ns >> 32; lo = ns & 0xffffffff; - qoriq_write(&qoriq_ptp->regs->tmr_alarm1_l, lo); - qoriq_write(&qoriq_ptp->regs->tmr_alarm1_h, hi); + qoriq_write(®s->alarm_regs->tmr_alarm1_l, lo); + qoriq_write(®s->alarm_regs->tmr_alarm1_h, hi); } /* Caller must hold qoriq_ptp->lock. */ static void set_fipers(struct qoriq_ptp *qoriq_ptp) { + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; + set_alarm(qoriq_ptp); - qoriq_write(&qoriq_ptp->regs->tmr_fiper1, qoriq_ptp->tmr_fiper1); - qoriq_write(&qoriq_ptp->regs->tmr_fiper2, qoriq_ptp->tmr_fiper2); + qoriq_write(®s->fiper_regs->tmr_fiper1, qoriq_ptp->tmr_fiper1); + qoriq_write(®s->fiper_regs->tmr_fiper2, qoriq_ptp->tmr_fiper2); } /* @@ -89,16 +94,17 @@ static void set_fipers(struct qoriq_ptp *qoriq_ptp) static irqreturn_t isr(int irq, void *priv) { struct qoriq_ptp *qoriq_ptp = priv; + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; struct ptp_clock_event event; u64 ns; u32 ack = 0, lo, hi, mask, val; - val = qoriq_read(&qoriq_ptp->regs->tmr_tevent); + val = qoriq_read(®s->ctrl_regs->tmr_tevent); if (val & ETS1) { ack |= ETS1; - hi = qoriq_read(&qoriq_ptp->regs->tmr_etts1_h); - lo = qoriq_read(&qoriq_ptp->regs->tmr_etts1_l); + hi = qoriq_read(®s->etts_regs->tmr_etts1_h); + lo = qoriq_read(®s->etts_regs->tmr_etts1_l); event.type = PTP_CLOCK_EXTTS; event.index = 0; event.timestamp = ((u64) hi) << 32; @@ -108,8 +114,8 @@ static irqreturn_t isr(int irq, void *priv) if (val & ETS2) { ack |= ETS2; - hi = qoriq_read(&qoriq_ptp->regs->tmr_etts2_h); - lo = qoriq_read(&qoriq_ptp->regs->tmr_etts2_l); + hi = qoriq_read(®s->etts_regs->tmr_etts2_h); + lo = qoriq_read(®s->etts_regs->tmr_etts2_l); event.type = PTP_CLOCK_EXTTS; event.index = 1; event.timestamp = ((u64) hi) << 32; @@ -130,16 +136,16 @@ static irqreturn_t isr(int irq, void *priv) hi = ns >> 32; lo = ns & 0xffffffff; spin_lock(&qoriq_ptp->lock); - qoriq_write(&qoriq_ptp->regs->tmr_alarm2_l, lo); - qoriq_write(&qoriq_ptp->regs->tmr_alarm2_h, hi); + qoriq_write(®s->alarm_regs->tmr_alarm2_l, lo); + qoriq_write(®s->alarm_regs->tmr_alarm2_h, hi); spin_unlock(&qoriq_ptp->lock); qoriq_ptp->alarm_value = ns; } else { - qoriq_write(&qoriq_ptp->regs->tmr_tevent, ALM2); + qoriq_write(®s->ctrl_regs->tmr_tevent, ALM2); spin_lock(&qoriq_ptp->lock); - mask = qoriq_read(&qoriq_ptp->regs->tmr_temask); + mask = qoriq_read(®s->ctrl_regs->tmr_temask); mask &= ~ALM2EN; - qoriq_write(&qoriq_ptp->regs->tmr_temask, mask); + qoriq_write(®s->ctrl_regs->tmr_temask, mask); spin_unlock(&qoriq_ptp->lock); qoriq_ptp->alarm_value = 0; qoriq_ptp->alarm_interval = 0; @@ -153,7 +159,7 @@ static irqreturn_t isr(int irq, void *priv) } if (ack) { - qoriq_write(&qoriq_ptp->regs->tmr_tevent, ack); + qoriq_write(®s->ctrl_regs->tmr_tevent, ack); return IRQ_HANDLED; } else return IRQ_NONE; @@ -169,6 +175,7 @@ static int ptp_qoriq_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) u32 tmr_add; int neg_adj = 0; struct qoriq_ptp *qoriq_ptp = container_of(ptp, struct qoriq_ptp, caps); + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; if (scaled_ppm < 0) { neg_adj = 1; @@ -186,7 +193,7 @@ static int ptp_qoriq_adjfine(struct ptp_clock_info *ptp, long scaled_ppm) tmr_add = neg_adj ? tmr_add - diff : tmr_add + diff; - qoriq_write(&qoriq_ptp->regs->tmr_add, tmr_add); + qoriq_write(®s->ctrl_regs->tmr_add, tmr_add); return 0; } @@ -250,6 +257,7 @@ static int ptp_qoriq_enable(struct ptp_clock_info *ptp, struct ptp_clock_request *rq, int on) { struct qoriq_ptp *qoriq_ptp = container_of(ptp, struct qoriq_ptp, caps); + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; unsigned long flags; u32 bit, mask; @@ -266,23 +274,23 @@ static int ptp_qoriq_enable(struct ptp_clock_info *ptp, return -EINVAL; } spin_lock_irqsave(&qoriq_ptp->lock, flags); - mask = qoriq_read(&qoriq_ptp->regs->tmr_temask); + mask = qoriq_read(®s->ctrl_regs->tmr_temask); if (on) mask |= bit; else mask &= ~bit; - qoriq_write(&qoriq_ptp->regs->tmr_temask, mask); + qoriq_write(®s->ctrl_regs->tmr_temask, mask); spin_unlock_irqrestore(&qoriq_ptp->lock, flags); return 0; case PTP_CLK_REQ_PPS: spin_lock_irqsave(&qoriq_ptp->lock, flags); - mask = qoriq_read(&qoriq_ptp->regs->tmr_temask); + mask = qoriq_read(®s->ctrl_regs->tmr_temask); if (on) mask |= PP1EN; else mask &= ~PP1EN; - qoriq_write(&qoriq_ptp->regs->tmr_temask, mask); + qoriq_write(®s->ctrl_regs->tmr_temask, mask); spin_unlock_irqrestore(&qoriq_ptp->lock, flags); return 0; @@ -313,10 +321,12 @@ static int qoriq_ptp_probe(struct platform_device *dev) { struct device_node *node = dev->dev.of_node; struct qoriq_ptp *qoriq_ptp; + struct qoriq_ptp_registers *regs; struct timespec64 now; int err = -ENOMEM; u32 tmr_ctrl; unsigned long flags; + void __iomem *base; qoriq_ptp = kzalloc(sizeof(*qoriq_ptp), GFP_KERNEL); if (!qoriq_ptp) @@ -351,7 +361,7 @@ static int qoriq_ptp_probe(struct platform_device *dev) pr_err("irq not in device tree\n"); goto no_node; } - if (request_irq(qoriq_ptp->irq, isr, 0, DRIVER, qoriq_ptp)) { + if (request_irq(qoriq_ptp->irq, isr, IRQF_SHARED, DRIVER, qoriq_ptp)) { pr_err("request_irq failed\n"); goto no_node; } @@ -368,12 +378,27 @@ static int qoriq_ptp_probe(struct platform_device *dev) spin_lock_init(&qoriq_ptp->lock); - qoriq_ptp->regs = ioremap(qoriq_ptp->rsrc->start, - resource_size(qoriq_ptp->rsrc)); - if (!qoriq_ptp->regs) { + base = ioremap(qoriq_ptp->rsrc->start, + resource_size(qoriq_ptp->rsrc)); + if (!base) { pr_err("ioremap ptp registers failed\n"); goto no_ioremap; } + + qoriq_ptp->base = base; + + if (of_device_is_compatible(node, "fsl,fman-ptp-timer")) { + qoriq_ptp->regs.ctrl_regs = base + FMAN_CTRL_REGS_OFFSET; + qoriq_ptp->regs.alarm_regs = base + FMAN_ALARM_REGS_OFFSET; + qoriq_ptp->regs.fiper_regs = base + FMAN_FIPER_REGS_OFFSET; + qoriq_ptp->regs.etts_regs = base + FMAN_ETTS_REGS_OFFSET; + } else { + qoriq_ptp->regs.ctrl_regs = base + CTRL_REGS_OFFSET; + qoriq_ptp->regs.alarm_regs = base + ALARM_REGS_OFFSET; + qoriq_ptp->regs.fiper_regs = base + FIPER_REGS_OFFSET; + qoriq_ptp->regs.etts_regs = base + ETTS_REGS_OFFSET; + } + ktime_get_real_ts64(&now); ptp_qoriq_settime(&qoriq_ptp->caps, &now); @@ -383,13 +408,14 @@ static int qoriq_ptp_probe(struct platform_device *dev) spin_lock_irqsave(&qoriq_ptp->lock, flags); - qoriq_write(&qoriq_ptp->regs->tmr_ctrl, tmr_ctrl); - qoriq_write(&qoriq_ptp->regs->tmr_add, qoriq_ptp->tmr_add); - qoriq_write(&qoriq_ptp->regs->tmr_prsc, qoriq_ptp->tmr_prsc); - qoriq_write(&qoriq_ptp->regs->tmr_fiper1, qoriq_ptp->tmr_fiper1); - qoriq_write(&qoriq_ptp->regs->tmr_fiper2, qoriq_ptp->tmr_fiper2); + regs = &qoriq_ptp->regs; + qoriq_write(®s->ctrl_regs->tmr_ctrl, tmr_ctrl); + qoriq_write(®s->ctrl_regs->tmr_add, qoriq_ptp->tmr_add); + qoriq_write(®s->ctrl_regs->tmr_prsc, qoriq_ptp->tmr_prsc); + qoriq_write(®s->fiper_regs->tmr_fiper1, qoriq_ptp->tmr_fiper1); + qoriq_write(®s->fiper_regs->tmr_fiper2, qoriq_ptp->tmr_fiper2); set_alarm(qoriq_ptp); - qoriq_write(&qoriq_ptp->regs->tmr_ctrl, tmr_ctrl|FIPERST|RTPE|TE|FRD); + qoriq_write(®s->ctrl_regs->tmr_ctrl, tmr_ctrl|FIPERST|RTPE|TE|FRD); spin_unlock_irqrestore(&qoriq_ptp->lock, flags); @@ -405,7 +431,7 @@ static int qoriq_ptp_probe(struct platform_device *dev) return 0; no_clock: - iounmap(qoriq_ptp->regs); + iounmap(qoriq_ptp->base); no_ioremap: release_resource(qoriq_ptp->rsrc); no_resource: @@ -419,12 +445,13 @@ static int qoriq_ptp_probe(struct platform_device *dev) static int qoriq_ptp_remove(struct platform_device *dev) { struct qoriq_ptp *qoriq_ptp = platform_get_drvdata(dev); + struct qoriq_ptp_registers *regs = &qoriq_ptp->regs; - qoriq_write(&qoriq_ptp->regs->tmr_temask, 0); - qoriq_write(&qoriq_ptp->regs->tmr_ctrl, 0); + qoriq_write(®s->ctrl_regs->tmr_temask, 0); + qoriq_write(®s->ctrl_regs->tmr_ctrl, 0); ptp_clock_unregister(qoriq_ptp->clock); - iounmap(qoriq_ptp->regs); + iounmap(qoriq_ptp->base); release_resource(qoriq_ptp->rsrc); free_irq(qoriq_ptp->irq, qoriq_ptp); kfree(qoriq_ptp); @@ -434,6 +461,7 @@ static int qoriq_ptp_remove(struct platform_device *dev) static const struct of_device_id match_table[] = { { .compatible = "fsl,etsec-ptp" }, + { .compatible = "fsl,fman-ptp-timer" }, {}, }; MODULE_DEVICE_TABLE(of, match_table); diff --git a/include/linux/fsl/ptp_qoriq.h b/include/linux/fsl/ptp_qoriq.h index b462d9e..dc3dac4 100644 --- a/include/linux/fsl/ptp_qoriq.h +++ b/include/linux/fsl/ptp_qoriq.h @@ -11,9 +11,8 @@ /* * qoriq ptp registers - * Generated by regen.tcl on Thu May 13 01:38:57 PM CEST 2010 */ -struct qoriq_ptp_registers { +struct ctrl_regs { u32 tmr_ctrl; /* Timer control register */ u32 tmr_tevent; /* Timestamp event register */ u32 tmr_temask; /* Timer event mask register */ @@ -28,22 +27,47 @@ struct qoriq_ptp_registers { u8 res1[4]; u32 tmroff_h; /* Timer offset high */ u32 tmroff_l; /* Timer offset low */ - u8 res2[8]; +}; + +struct alarm_regs { u32 tmr_alarm1_h; /* Timer alarm 1 high register */ u32 tmr_alarm1_l; /* Timer alarm 1 high register */ u32 tmr_alarm2_h; /* Timer alarm 2 high register */ u32 tmr_alarm2_l; /* Timer alarm 2 high register */ - u8 res3[48]; +}; + +struct fiper_regs { u32 tmr_fiper1; /* Timer fixed period interval */ u32 tmr_fiper2; /* Timer fixed period interval */ u32 tmr_fiper3; /* Timer fixed period interval */ - u8 res4[20]; +}; + +struct etts_regs { u32 tmr_etts1_h; /* Timestamp of general purpose external trigger */ u32 tmr_etts1_l; /* Timestamp of general purpose external trigger */ u32 tmr_etts2_h; /* Timestamp of general purpose external trigger */ u32 tmr_etts2_l; /* Timestamp of general purpose external trigger */ }; +struct qoriq_ptp_registers { + struct ctrl_regs __iomem *ctrl_regs; + struct alarm_regs __iomem *alarm_regs; + struct fiper_regs __iomem *fiper_regs; + struct etts_regs __iomem *etts_regs; +}; + +/* Offset definitions for the four register groups */ +#define CTRL_REGS_OFFSET 0x0 +#define ALARM_REGS_OFFSET 0x40 +#define FIPER_REGS_OFFSET 0x80 +#define ETTS_REGS_OFFSET 0xa0 + +#define FMAN_CTRL_REGS_OFFSET 0x80 +#define FMAN_ALARM_REGS_OFFSET 0xb8 +#define FMAN_FIPER_REGS_OFFSET 0xd0 +#define FMAN_ETTS_REGS_OFFSET 0xe0 + + /* Bit definitions for the TMR_CTRL register */ #define ALM1P (1<<31) /* Alarm1 output polarity */ #define ALM2P (1<<30) /* Alarm2 output polarity */ @@ -105,10 +129,10 @@ struct qoriq_ptp_registers { #define DRIVER "ptp_qoriq" #define DEFAULT_CKSEL 1 #define N_EXT_TS 2 -#define REG_SIZE sizeof(struct qoriq_ptp_registers) struct qoriq_ptp { - struct qoriq_ptp_registers __iomem *regs; + void __iomem *base; + struct qoriq_ptp_registers regs; spinlock_t lock; /* protects regs */ struct ptp_clock *clock; struct ptp_clock_info caps; -- 1.7.1