* [PATCH v11 2/2] mtd: rawnand: Add NAND controller support on Intel LGM SoC
From: Ramuthevar,Vadivel MuruganX @ 2020-05-30 0:51 UTC (permalink / raw)
To: linux-kernel, linux-mtd, devicetree, miquel.raynal
Cc: richard, vigneshr, arnd, brendanhiggins, tglx, boris.brezillon,
anders.roxell, masonccyang, robh+dt, linux-mips, hauke.mehrtens,
andriy.shevchenko, qi-ming.wu, cheol.yong.kim,
Ramuthevar Vadivel Murugan
In-Reply-To: <20200530005117.10986-1-vadivel.muruganx.ramuthevar@linux.intel.com>
From: Ramuthevar Vadivel Murugan <vadivel.muruganx.ramuthevar@linux.intel.com>
This patch adds the new IP of Nand Flash Controller(NFC) support
on Intel's Lightning Mountain(LGM) SoC.
DMA is used for burst data transfer operation, also DMA HW supports
aligned 32bit memory address and aligned data access by default.
DMA burst of 8 supported. Data register used to support the read/write
operation from/to device.
NAND controller driver implements ->exec_op() to replace legacy hooks,
these specific call-back method to execute NAND operations.
Signed-off-by: Ramuthevar Vadivel Murugan <vadivel.muruganx.ramuthevar@linux.intel.com>
---
drivers/mtd/nand/raw/Kconfig | 8 +
drivers/mtd/nand/raw/Makefile | 1 +
drivers/mtd/nand/raw/intel-nand-controller.c | 747 +++++++++++++++++++++++++++
3 files changed, 756 insertions(+)
create mode 100644 drivers/mtd/nand/raw/intel-nand-controller.c
diff --git a/drivers/mtd/nand/raw/Kconfig b/drivers/mtd/nand/raw/Kconfig
index a80a46bb5b8b..75ab2afb78cf 100644
--- a/drivers/mtd/nand/raw/Kconfig
+++ b/drivers/mtd/nand/raw/Kconfig
@@ -457,6 +457,14 @@ config MTD_NAND_CADENCE
Enable the driver for NAND flash on platforms using a Cadence NAND
controller.
+config MTD_NAND_INTEL_LGM
+ tristate "Support for NAND controller on Intel LGM SoC"
+ depends on OF || COMPILE_TEST
+ depends on HAS_IOMEM
+ help
+ Enables support for NAND Flash chips on Intel's LGM SoC.
+ NAND flash controller interfaced through the External Bus Unit.
+
comment "Misc"
config MTD_SM_COMMON
diff --git a/drivers/mtd/nand/raw/Makefile b/drivers/mtd/nand/raw/Makefile
index 2d136b158fb7..bfc8fe4d2cb0 100644
--- a/drivers/mtd/nand/raw/Makefile
+++ b/drivers/mtd/nand/raw/Makefile
@@ -58,6 +58,7 @@ obj-$(CONFIG_MTD_NAND_TEGRA) += tegra_nand.o
obj-$(CONFIG_MTD_NAND_STM32_FMC2) += stm32_fmc2_nand.o
obj-$(CONFIG_MTD_NAND_MESON) += meson_nand.o
obj-$(CONFIG_MTD_NAND_CADENCE) += cadence-nand-controller.o
+obj-$(CONFIG_MTD_NAND_INTEL_LGM) += intel-nand-controller.o
nand-objs := nand_base.o nand_legacy.o nand_bbt.o nand_timings.o nand_ids.o
nand-objs += nand_onfi.o
diff --git a/drivers/mtd/nand/raw/intel-nand-controller.c b/drivers/mtd/nand/raw/intel-nand-controller.c
new file mode 100644
index 000000000000..564d28978943
--- /dev/null
+++ b/drivers/mtd/nand/raw/intel-nand-controller.c
@@ -0,0 +1,747 @@
+// SPDX-License-Identifier: GPL-2.0+
+/* Copyright (c) 2020 Intel Corporation. */
+
+#include <linux/clk.h>
+#include <linux/completion.h>
+#include <linux/dmaengine.h>
+#include <linux/dma-direction.h>
+#include <linux/dma-mapping.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/kernel.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/mtd/mtd.h>
+#include <linux/mtd/rawnand.h>
+#include <linux/mtd/nand_ecc.h>
+#include <linux/mtd/nand.h>
+#include <linux/resource.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/platform_device.h>
+
+#define EBU_CLC 0x000
+#define EBU_CLC_RST 0x00000000u
+
+#define EBU_ADDR_SEL(n) (0x20 + (n) * 4)
+/* 5 bits 26:22 included for comparison in the ADDR_SELx */
+#define EBU_ADDR_MASK(x) ((x) << 4)
+#define EBU_ADDR_SEL_REGEN 0x1
+
+#define EBU_BUSCON(n) (0x60 + (n) * 4)
+#define EBU_BUSCON_CMULT_V4 0x1
+#define EBU_BUSCON_RECOVC(n) ((n) << 2)
+#define EBU_BUSCON_HOLDC(n) ((n) << 4)
+#define EBU_BUSCON_WAITRDC(n) ((n) << 6)
+#define EBU_BUSCON_WAITWRC(n) ((n) << 8)
+#define EBU_BUSCON_BCGEN_CS 0x0
+#define EBU_BUSCON_SETUP_EN BIT(22)
+#define EBU_BUSCON_ALEC 0xC000
+
+#define EBU_CON 0x0B0
+#define EBU_CON_NANDM_EN BIT(0)
+#define EBU_CON_NANDM_DIS 0x0
+#define EBU_CON_CSMUX_E_EN BIT(1)
+#define EBU_CON_ALE_P_LOW BIT(2)
+#define EBU_CON_CLE_P_LOW BIT(3)
+#define EBU_CON_CS_P_LOW BIT(4)
+#define EBU_CON_SE_P_LOW BIT(5)
+#define EBU_CON_WP_P_LOW BIT(6)
+#define EBU_CON_PRE_P_LOW BIT(7)
+#define EBU_CON_IN_CS_S(n) ((n) << 8)
+#define EBU_CON_OUT_CS_S(n) ((n) << 10)
+#define EBU_CON_LAT_EN_CS_P ((0x3D) << 18)
+
+#define EBU_WAIT 0x0B4
+#define EBU_WAIT_RDBY BIT(0)
+#define EBU_WAIT_WR_C BIT(3)
+
+#define HSNAND_CTL1 0x110
+#define HSNAND_CTL1_ADDR_SHIFT 24
+
+#define HSNAND_CTL2 0x114
+#define HSNAND_CTL2_ADDR_SHIFT 8
+#define HSNAND_CTL2_CYC_N_V5 (0x2 << 16)
+
+#define HSNAND_INT_MSK_CTL 0x124
+#define HSNAND_INT_MSK_CTL_WR_C BIT(4)
+
+#define HSNAND_INT_STA 0x128
+#define HSNAND_INT_STA_WR_C BIT(4)
+
+#define HSNAND_CTL 0x130
+#define HSNAND_CTL_ENABLE_ECC BIT(0)
+#define HSNAND_CTL_GO BIT(2)
+#define HSNAND_CTL_CE_SEL_CS(n) BIT(3 + (n))
+#define HSNAND_CTL_RW_READ 0x0
+#define HSNAND_CTL_RW_WRITE BIT(10)
+#define HSNAND_CTL_ECC_OFF_V8TH BIT(11)
+#define HSNAND_CTL_CKFF_EN 0x0
+#define HSNAND_CTL_MSG_EN BIT(17)
+
+#define HSNAND_PARA0 0x13c
+#define HSNAND_PARA0_PAGE_V8192 0x3
+#define HSNAND_PARA0_PIB_V256 (0x3 << 4)
+#define HSNAND_PARA0_BYP_EN_NP 0x0
+#define HSNAND_PARA0_BYP_DEC_NP 0x0
+#define HSNAND_PARA0_TYPE_ONFI BIT(18)
+#define HSNAND_PARA0_ADEP_EN BIT(21)
+
+#define HSNAND_CMSG_0 0x150
+#define HSNAND_CMSG_1 0x154
+
+#define HSNAND_ALE_OFFS BIT(2)
+#define HSNAND_CLE_OFFS BIT(3)
+#define HSNAND_CS_OFFS BIT(4)
+
+#define HSNAND_ECC_OFFSET 0x008
+
+#define NAND_DATA_IFACE_CHECK_ONLY -1
+
+#define MAX_CS 2
+
+struct ebu_nand_cs {
+ void __iomem *chipaddr;
+ dma_addr_t nand_pa;
+ u32 addr_sel;
+};
+
+struct ebu_nand_controller {
+ struct nand_controller controller;
+ struct nand_chip chip;
+ struct device *dev;
+ void __iomem *ebu;
+ void __iomem *hsnand;
+ struct dma_chan *dma_tx;
+ struct dma_chan *dma_rx;
+ struct completion dma_access_complete;
+ unsigned long clk_rate;
+ struct clk *clk;
+ u32 nd_para0;
+ u8 cs_num;
+ struct ebu_nand_cs cs[MAX_CS];
+};
+
+static inline struct ebu_nand_controller *nand_to_ebu(struct nand_chip *chip)
+{
+ return container_of(chip, struct ebu_nand_controller, chip);
+}
+
+static u8 ebu_nand_readb(struct nand_chip *chip)
+{
+ struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip);
+ void __iomem *nand_wait = ebu_host->ebu + EBU_WAIT;
+ u8 cs_num = ebu_host->cs_num;
+ u32 stat;
+ int ret;
+ u8 val;
+
+ val = readb(ebu_host->cs[cs_num].chipaddr + HSNAND_CS_OFFS);
+
+ ret = readl_poll_timeout(nand_wait, stat, stat & EBU_WAIT_WR_C,
+ 20, 1000);
+ if (ret)
+ dev_warn(ebu_host->dev,
+ "ebu nand write timeout. nand_wait(0x%p)=0x%x\n",
+ nand_wait, readl(nand_wait));
+
+ return val;
+}
+
+static void ebu_nand_writeb(struct nand_chip *chip, u32 offset, u8 value)
+{
+ struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip);
+ void __iomem *nand_wait = ebu_host->ebu + EBU_WAIT;
+ u8 cs_num = ebu_host->cs_num;
+ u32 stat;
+ int ret;
+
+ writeb(value, ebu_host->cs[cs_num].chipaddr + offset);
+
+ ret = readl_poll_timeout(nand_wait, stat, stat & EBU_WAIT_WR_C,
+ 20, 1000);
+ if (ret)
+ dev_warn(ebu_host->dev,
+ "ebu nand write timeout. nand_wait(0x%p)=0x%x\n",
+ nand_wait, readl(nand_wait));
+}
+
+static void ebu_read_buf(struct nand_chip *chip, u_char *buf, unsigned int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ buf[i] = ebu_nand_readb(chip);
+}
+
+static void ebu_write_buf(struct nand_chip *chip, const u_char *buf, int len)
+{
+ int i;
+
+ for (i = 0; i < len; i++)
+ ebu_nand_writeb(chip, HSNAND_CS_OFFS, buf[i]);
+}
+
+static void ebu_nand_disable(struct nand_chip *chip)
+{
+ struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip);
+
+ writel(0, ebu_host->ebu + EBU_CON);
+}
+
+static void ebu_select_chip(struct nand_chip *chip)
+{
+ struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip);
+ void __iomem *nand_con = ebu_host->ebu + EBU_CON;
+ u32 cs = ebu_host->cs_num;
+
+ writel(EBU_CON_NANDM_EN | EBU_CON_CSMUX_E_EN | EBU_CON_CS_P_LOW |
+ EBU_CON_SE_P_LOW | EBU_CON_WP_P_LOW | EBU_CON_PRE_P_LOW |
+ EBU_CON_IN_CS_S(cs) | EBU_CON_OUT_CS_S(cs) |
+ EBU_CON_LAT_EN_CS_P, nand_con);
+}
+
+static void ebu_nand_setup_timing(struct ebu_nand_controller *ctrl,
+ const struct nand_sdr_timings *timings)
+{
+ unsigned int rate = clk_get_rate(ctrl->clk) / 1000000;
+ unsigned int period = DIV_ROUND_UP(1000000, rate);
+ u32 trecov, thold, twrwait, trdwait;
+ u32 reg = 0;
+
+ trecov = DIV_ROUND_UP(max(timings->tREA_max, timings->tREH_min),
+ period);
+ reg |= EBU_BUSCON_RECOVC(trecov);
+
+ thold = DIV_ROUND_UP(max(timings->tDH_min, timings->tDS_min), period);
+ reg |= EBU_BUSCON_HOLDC(thold);
+
+ trdwait = DIV_ROUND_UP(max(timings->tRC_min, timings->tREH_min),
+ period);
+ reg |= EBU_BUSCON_WAITRDC(trdwait);
+
+ twrwait = DIV_ROUND_UP(max(timings->tWC_min, timings->tWH_min), period);
+ reg |= EBU_BUSCON_WAITWRC(twrwait);
+
+ reg |= EBU_BUSCON_CMULT_V4 | EBU_BUSCON_BCGEN_CS | EBU_BUSCON_ALEC |
+ EBU_BUSCON_SETUP_EN;
+
+ writel(reg, ctrl->ebu + EBU_BUSCON(ctrl->cs_num));
+}
+
+static int ebu_nand_setup_data_interface(struct nand_chip *chip, int csline,
+ const struct nand_data_interface *conf)
+{
+ struct ebu_nand_controller *ctrl = nand_to_ebu(chip);
+ const struct nand_sdr_timings *timings;
+
+ timings = nand_get_sdr_timings(conf);
+ if (IS_ERR(timings))
+ return PTR_ERR(timings);
+
+ if (csline == NAND_DATA_IFACE_CHECK_ONLY)
+ return 0;
+
+ ebu_nand_setup_timing(ctrl, timings);
+
+ return 0;
+}
+
+static int ebu_nand_ooblayout_ecc(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+
+ if (section)
+ return -ERANGE;
+
+ oobregion->offset = HSNAND_ECC_OFFSET;
+ oobregion->length = chip->ecc.total;
+
+ return 0;
+}
+
+static int ebu_nand_ooblayout_free(struct mtd_info *mtd, int section,
+ struct mtd_oob_region *oobregion)
+{
+ struct nand_chip *chip = mtd_to_nand(mtd);
+
+ if (section)
+ return -ERANGE;
+
+ oobregion->offset = chip->ecc.total + HSNAND_ECC_OFFSET;
+ oobregion->length = mtd->oobsize - oobregion->offset;
+
+ return 0;
+}
+
+static const struct mtd_ooblayout_ops ebu_nand_ooblayout_ops = {
+ .ecc = ebu_nand_ooblayout_ecc,
+ .free = ebu_nand_ooblayout_free,
+};
+
+static void ebu_dma_rx_callback(void *cookie)
+{
+ struct ebu_nand_controller *ebu_host = cookie;
+
+ dmaengine_terminate_async(ebu_host->dma_rx);
+
+ complete(&ebu_host->dma_access_complete);
+}
+
+static void ebu_dma_tx_callback(void *cookie)
+{
+ struct ebu_nand_controller *ebu_host = cookie;
+
+ dmaengine_terminate_async(ebu_host->dma_tx);
+
+ complete(&ebu_host->dma_access_complete);
+}
+
+static int ebu_dma_start(struct ebu_nand_controller *ebu_host, u32 dir,
+ const u8 *buf, u32 len)
+{
+ struct dma_async_tx_descriptor *tx;
+ struct completion *dma_completion;
+ dma_async_tx_callback callback;
+ struct dma_chan *chan;
+ dma_cookie_t cookie;
+ unsigned long flags = DMA_CTRL_ACK | DMA_PREP_INTERRUPT;
+ dma_addr_t buf_dma;
+ int ret;
+ u32 timeout;
+
+ if (dir == DMA_DEV_TO_MEM) {
+ chan = ebu_host->dma_rx;
+ dma_completion = &ebu_host->dma_access_complete;
+ callback = ebu_dma_rx_callback;
+ } else {
+ chan = ebu_host->dma_tx;
+ dma_completion = &ebu_host->dma_access_complete;
+ callback = ebu_dma_tx_callback;
+ }
+
+ buf_dma = dma_map_single(chan->device->dev, (void *)buf, len, dir);
+ if (dma_mapping_error(chan->device->dev, buf_dma)) {
+ dev_err(ebu_host->dev, "Failed to map DMA buffer\n");
+ ret = -EIO;
+ goto err_unmap;
+ }
+
+ tx = dmaengine_prep_slave_single(chan, buf_dma, len, dir, flags);
+ if (!tx)
+ return -ENXIO;
+
+ tx->callback = callback;
+ tx->callback_param = ebu_host;
+ cookie = tx->tx_submit(tx);
+
+ ret = dma_submit_error(cookie);
+ if (ret) {
+ dev_err(ebu_host->dev, "dma_submit_error %d\n", cookie);
+ ret = -EIO;
+ goto err_unmap;
+ }
+
+ init_completion(dma_completion);
+ dma_async_issue_pending(chan);
+
+ /* Wait DMA to finish the data transfer.*/
+ timeout =
+ wait_for_completion_timeout(dma_completion, msecs_to_jiffies(1000));
+ if (!timeout) {
+ dev_err(ebu_host->dev, "I/O Error in DMA RX (status %d)\n",
+ dmaengine_tx_status(chan, cookie, NULL));
+ dmaengine_terminate_sync(chan);
+ ret = -ETIMEDOUT;
+ goto err_unmap;
+ }
+
+ return 0;
+
+err_unmap:
+ dma_unmap_single(ebu_host->dev, buf_dma, len, dir);
+
+ return ret;
+}
+
+static void ebu_nand_trigger(struct ebu_nand_controller *ebu_host,
+ int page, u32 cmd)
+{
+ unsigned int val;
+
+ val = cmd | (page & 0xFF) << HSNAND_CTL1_ADDR_SHIFT;
+ writel(val, ebu_host->hsnand + HSNAND_CTL1);
+ val = (page & 0xFFFF00) >> 8 | HSNAND_CTL2_CYC_N_V5;
+ writel(val, ebu_host->hsnand + HSNAND_CTL2);
+
+ writel(ebu_host->nd_para0, ebu_host->hsnand + HSNAND_PARA0);
+
+ /* clear first, will update later */
+ writel(0xFFFFFFFF, ebu_host->hsnand + HSNAND_CMSG_0);
+ writel(0xFFFFFFFF, ebu_host->hsnand + HSNAND_CMSG_1);
+
+ writel(HSNAND_INT_MSK_CTL_WR_C,
+ ebu_host->hsnand + HSNAND_INT_MSK_CTL);
+
+ val = cmd == NAND_CMD_READ0 ? HSNAND_CTL_RW_READ : HSNAND_CTL_RW_WRITE;
+
+ writel(HSNAND_CTL_MSG_EN | HSNAND_CTL_CKFF_EN |
+ HSNAND_CTL_ECC_OFF_V8TH | HSNAND_CTL_CE_SEL_CS(ebu_host->cs_num) |
+ HSNAND_CTL_ENABLE_ECC | HSNAND_CTL_GO | val,
+ ebu_host->hsnand + HSNAND_CTL);
+}
+
+static int ebu_nand_read_page_hwecc(struct nand_chip *chip, u8 *buf,
+ int oob_required, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip);
+ int ret, x;
+
+ ebu_nand_trigger(ebu_host, page, NAND_CMD_READ0);
+
+ ret = ebu_dma_start(ebu_host, DMA_DEV_TO_MEM, buf, mtd->writesize);
+ if (ret)
+ return ret;
+
+ if (oob_required)
+ chip->ecc.read_oob(chip, page);
+
+ x = readl(ebu_host->hsnand + HSNAND_CTL);
+ x &= ~HSNAND_CTL_GO;
+ writel(x, ebu_host->hsnand + HSNAND_CTL);
+
+ return 0;
+}
+
+static int ebu_nand_write_page_hwecc(struct nand_chip *chip, const u8 *buf,
+ int oob_required, int page)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip);
+ void __iomem *int_sta = ebu_host->hsnand + HSNAND_INT_STA;
+ int ret, val, x;
+ u32 reg;
+
+ ebu_nand_trigger(ebu_host, page, NAND_CMD_SEQIN);
+
+ ret = ebu_dma_start(ebu_host, DMA_MEM_TO_DEV, buf, mtd->writesize);
+ if (ret)
+ return ret;
+
+ if (oob_required) {
+ reg = (chip->oob_poi[3] << 24) | (chip->oob_poi[2] << 16) |
+ (chip->oob_poi[1] << 8) | chip->oob_poi[0];
+
+ writel(reg, ebu_host->hsnand + HSNAND_CMSG_0);
+
+ reg = (chip->oob_poi[7] << 24) | (chip->oob_poi[6] << 16) |
+ (chip->oob_poi[5] << 8) | chip->oob_poi[4];
+
+ writel(reg, ebu_host->hsnand + HSNAND_CMSG_1);
+ }
+
+ ret = readl_poll_timeout_atomic(int_sta, val,
+ !(val & HSNAND_INT_STA_WR_C), 10, 1000);
+ if (ret)
+ return -EIO;
+
+ x = readl(ebu_host->hsnand + HSNAND_CTL);
+ x &= ~HSNAND_CTL_GO;
+ writel(x, ebu_host->hsnand + HSNAND_CTL);
+
+ return 0;
+}
+
+static const u8 ecc_strength[] = { 1, 1, 4, 8, 24, 32, 40, 60, };
+
+static int ebu_nand_attach_chip(struct nand_chip *chip)
+{
+ struct mtd_info *mtd = nand_to_mtd(chip);
+ struct ebu_nand_controller *ebu_host = nand_get_controller_data(chip);
+ u32 eccsize, eccsteps, eccbytes, ecctotal, pagesize, pg_per_blk;
+ u32 eccstrength = chip->ecc.strength;
+ u32 writesize = mtd->writesize;
+ u32 blocksize = mtd->erasesize;
+ int start, val, i;
+
+ if (chip->ecc.mode != NAND_ECC_HW)
+ return 0;
+
+ /* Check whether eccsize is 0x0 or wrong. assign eccsize = 512 if YES */
+ if (!chip->ecc.size)
+ chip->ecc.size = 512;
+ eccsize = chip->ecc.size;
+
+ switch (eccsize) {
+ case 512:
+ start = 1;
+ if (!eccstrength)
+ eccstrength = 4;
+ break;
+ case 1024:
+ start = 4;
+ if (!eccstrength)
+ eccstrength = 32;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ i = round_up(start + 1, 4);
+ for (val = start; val < i; val++) {
+ if (eccstrength == ecc_strength[val])
+ break;
+ }
+ if (val == i)
+ return -EINVAL;
+
+ if (eccstrength == 8)
+ eccbytes = 14;
+ else
+ eccbytes = DIV_ROUND_UP(eccstrength * fls(8 * eccsize), 8);
+
+ eccsteps = writesize / eccsize;
+ ecctotal = eccsteps * eccbytes;
+ if ((ecctotal + 8) > mtd->oobsize)
+ return -ERANGE;
+
+ chip->ecc.total = ecctotal;
+ pagesize = fls(writesize >> 11);
+ if (pagesize > HSNAND_PARA0_PAGE_V8192)
+ return -ERANGE;
+
+ pg_per_blk = fls((blocksize / writesize) >> 6) << 4;
+ if (pg_per_blk > HSNAND_PARA0_PIB_V256)
+ return -ERANGE;
+
+ ebu_host->nd_para0 = pagesize | pg_per_blk | HSNAND_PARA0_BYP_EN_NP |
+ HSNAND_PARA0_BYP_DEC_NP | HSNAND_PARA0_ADEP_EN |
+ HSNAND_PARA0_TYPE_ONFI | (val << 29);
+
+ mtd_set_ooblayout(mtd, &ebu_nand_ooblayout_ops);
+ chip->ecc.read_page = ebu_nand_read_page_hwecc;
+ chip->ecc.write_page = ebu_nand_write_page_hwecc;
+
+ return 0;
+}
+
+static int ebu_nand_exec_op(struct nand_chip *chip,
+ const struct nand_operation *op, bool check_only)
+{
+ struct ebu_nand_controller *ctrl = nand_to_ebu(chip);
+ const struct nand_op_instr *instr = NULL;
+ unsigned int op_id;
+ int i, time_out, ret = 0;
+ u32 stat;
+
+ ebu_select_chip(chip);
+
+ for (op_id = 0; op_id < op->ninstrs; op_id++) {
+ instr = &op->instrs[op_id];
+
+ switch (instr->type) {
+ case NAND_OP_CMD_INSTR:
+ ebu_nand_writeb(chip, HSNAND_CLE_OFFS | HSNAND_CS_OFFS,
+ instr->ctx.cmd.opcode);
+ break;
+
+ case NAND_OP_ADDR_INSTR:
+ for (i = 0; i < instr->ctx.addr.naddrs; i++)
+ ebu_nand_writeb(chip,
+ HSNAND_ALE_OFFS | HSNAND_CS_OFFS,
+ instr->ctx.addr.addrs[i]);
+ break;
+
+ case NAND_OP_DATA_IN_INSTR:
+ ebu_read_buf(chip, instr->ctx.data.buf.in,
+ instr->ctx.data.len);
+ break;
+
+ case NAND_OP_DATA_OUT_INSTR:
+ ebu_write_buf(chip, instr->ctx.data.buf.out,
+ instr->ctx.data.len);
+ break;
+
+ case NAND_OP_WAITRDY_INSTR:
+ time_out = instr->ctx.waitrdy.timeout_ms * 1000;
+ ret = readl_poll_timeout(ctrl->ebu + EBU_WAIT,
+ stat, stat & EBU_WAIT_RDBY,
+ 20, time_out);
+ break;
+ }
+ }
+
+ return ret;
+}
+
+static const struct nand_controller_ops ebu_nand_controller_ops = {
+ .attach_chip = ebu_nand_attach_chip,
+ .exec_op = ebu_nand_exec_op,
+ .setup_data_interface = ebu_nand_setup_data_interface,
+};
+
+static void ebu_dma_cleanup(struct ebu_nand_controller *ebu_host)
+{
+ if (ebu_host->dma_rx)
+ dma_release_channel(ebu_host->dma_rx);
+
+ if (ebu_host->dma_tx)
+ dma_release_channel(ebu_host->dma_tx);
+}
+
+static int ebu_nand_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct ebu_nand_controller *ebu_host;
+ struct nand_chip *nand;
+ struct mtd_info *mtd;
+ struct resource *res;
+ char *resname;
+ int ret, i;
+ u32 reg;
+
+ ebu_host = devm_kzalloc(dev, sizeof(*ebu_host), GFP_KERNEL);
+ if (!ebu_host)
+ return -ENOMEM;
+
+ ebu_host->dev = dev;
+ nand_controller_init(&ebu_host->controller);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "ebunand");
+ ebu_host->ebu = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ebu_host->ebu))
+ return PTR_ERR(ebu_host->ebu);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "hsnand");
+ ebu_host->hsnand = devm_ioremap_resource(&pdev->dev, res);
+ if (IS_ERR(ebu_host->hsnand))
+ return PTR_ERR(ebu_host->hsnand);
+
+ ret = device_property_read_u32(dev, "nand,cs", ®);
+ if (ret) {
+ dev_err(dev, "failed to get chip select: %d\n", ret);
+ return ret;
+ }
+ ebu_host->cs_num = reg;
+
+ for (i = 0; i < MAX_CS; i++) {
+ resname = devm_kasprintf(dev, GFP_KERNEL, "nand_cs%d", i);
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ resname);
+ if (!res)
+ return -EINVAL;
+ ebu_host->cs[i].chipaddr = devm_ioremap_resource(dev, res);
+ ebu_host->cs[i].nand_pa = res->start;
+ if (IS_ERR(ebu_host->cs[i].chipaddr))
+ return PTR_ERR(ebu_host->cs[i].chipaddr);
+ }
+
+ ebu_host->clk = devm_clk_get(dev, NULL);
+ if (IS_ERR(ebu_host->clk)) {
+ ret = PTR_ERR(ebu_host->clk);
+ dev_err(dev, "failed to get clock: %d\n", ret);
+ return ret;
+ }
+
+ ret = clk_prepare_enable(ebu_host->clk);
+ if (ret) {
+ dev_err(dev, "failed to enable clock: %d\n", ret);
+ return ret;
+ }
+ ebu_host->clk_rate = clk_get_rate(ebu_host->clk);
+
+ ebu_host->dma_tx = dma_request_chan(dev, "tx");
+ if (IS_ERR(ebu_host->dma_tx)) {
+ ret = PTR_ERR(ebu_host->dma_tx);
+ dev_err(dev, "DMA tx channel request fail!.\n");
+ goto err_cleanup_dma;
+ }
+
+ ebu_host->dma_rx = dma_request_chan(dev, "rx");
+ if (IS_ERR(ebu_host->dma_rx)) {
+ ret = PTR_ERR(ebu_host->dma_rx);
+ dev_err(dev, "DMA rx channel request fail!.\n");
+ goto err_cleanup_dma;
+ }
+
+ for (i = 0; i < MAX_CS; i++) {
+ resname = devm_kasprintf(dev, GFP_KERNEL, "addr_sel%d", i);
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ resname);
+ if (!res)
+ return -EINVAL;
+ ebu_host->cs[i].addr_sel = res->start;
+ writel(ebu_host->cs[i].addr_sel | EBU_ADDR_MASK(5) |
+ EBU_ADDR_SEL_REGEN, ebu_host->ebu + EBU_ADDR_SEL(i));
+ }
+
+ nand_set_flash_node(&ebu_host->chip, dev->of_node);
+ mtd = nand_to_mtd(&ebu_host->chip);
+ mtd->dev.parent = dev;
+ ebu_host->dev = dev;
+
+ platform_set_drvdata(pdev, ebu_host);
+ nand_set_controller_data(&ebu_host->chip, ebu_host);
+
+ nand = &ebu_host->chip;
+ nand->controller = &ebu_host->controller;
+ nand->controller->ops = &ebu_nand_controller_ops;
+
+ /* Scan to find existence of the device */
+ ret = nand_scan(&ebu_host->chip, 1);
+ if (ret)
+ goto err_cleanup_dma;
+
+ ret = mtd_device_register(mtd, NULL, 0);
+ if (ret)
+ goto err_clean_nand;
+
+ return 0;
+
+err_clean_nand:
+ nand_cleanup(&ebu_host->chip);
+err_cleanup_dma:
+ ebu_dma_cleanup(ebu_host);
+ clk_disable_unprepare(ebu_host->clk);
+
+ return ret;
+}
+
+static int ebu_nand_remove(struct platform_device *pdev)
+{
+ struct ebu_nand_controller *ebu_host = platform_get_drvdata(pdev);
+
+ mtd_device_unregister(nand_to_mtd(&ebu_host->chip));
+ nand_cleanup(&ebu_host->chip);
+ ebu_nand_disable(&ebu_host->chip);
+ ebu_dma_cleanup(ebu_host);
+ clk_disable_unprepare(ebu_host->clk);
+
+ return 0;
+}
+
+static const struct of_device_id ebu_nand_match[] = {
+ { .compatible = "intel,nand-controller", },
+ {}
+};
+MODULE_DEVICE_TABLE(of, ebu_nand_match);
+
+static struct platform_driver ebu_nand_driver = {
+ .probe = ebu_nand_probe,
+ .remove = ebu_nand_remove,
+ .driver = {
+ .name = "intel-nand-controller",
+ .of_match_table = ebu_nand_match,
+ },
+
+};
+module_platform_driver(ebu_nand_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_AUTHOR("Vadivel Murugan R <vadivel.muruganx.ramuthevar@intel.com>");
+MODULE_DESCRIPTION("Intel's LGM External Bus NAND Controller driver");
--
2.11.0
^ permalink raw reply related
* [PATCH v11 1/2] dt-bindings: mtd: Add Nand Flash Controller support for Intel LGM SoC
From: Ramuthevar,Vadivel MuruganX @ 2020-05-30 0:51 UTC (permalink / raw)
To: linux-kernel, linux-mtd, devicetree, miquel.raynal
Cc: richard, vigneshr, arnd, brendanhiggins, tglx, boris.brezillon,
anders.roxell, masonccyang, robh+dt, linux-mips, hauke.mehrtens,
andriy.shevchenko, qi-ming.wu, cheol.yong.kim,
Ramuthevar Vadivel Murugan
In-Reply-To: <20200530005117.10986-1-vadivel.muruganx.ramuthevar@linux.intel.com>
From: Ramuthevar Vadivel Murugan <vadivel.muruganx.ramuthevar@linux.intel.com>
Add YAML file for dt-bindings to support NAND Flash Controller
on Intel's Lightning Mountain SoC.
Signed-off-by: Ramuthevar Vadivel Murugan <vadivel.muruganx.ramuthevar@linux.intel.com>
---
.../devicetree/bindings/mtd/intel,lgm-nand.yaml | 99 ++++++++++++++++++++++
1 file changed, 99 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml
diff --git a/Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml b/Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml
new file mode 100644
index 000000000000..313daec4d783
--- /dev/null
+++ b/Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml
@@ -0,0 +1,99 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/mtd/intel,lgm-nand.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Intel LGM SoC NAND Controller Device Tree Bindings
+
+allOf:
+ - $ref: "nand-controller.yaml"
+
+maintainers:
+ - Ramuthevar Vadivel Murugan <vadivel.muruganx.ramuthevar@linux.intel.com>
+
+properties:
+ compatible:
+ const: intel,lgm-nand
+
+ reg:
+ maxItems: 6
+
+ reg-names:
+ items:
+ - const: ebunand
+ - const: hsnand
+ - const: nand_cs0
+ - const: nand_cs1
+ - const: addr_sel0
+ - const: addr_sel1
+
+ clocks:
+ maxItems: 1
+
+ dmas:
+ maxItems: 2
+
+ dma-names:
+ items:
+ - const: tx
+ - const: rx
+
+ "#address-cells":
+ const: 1
+
+ "#size-cells":
+ const: 0
+
+patternProperties:
+ "^nand@[a-f0-9]+$":
+ type: object
+ properties:
+ reg:
+ minimum: 0
+ maximum: 7
+
+ nand-ecc-mode: true
+
+ nand-ecc-algo:
+ const: hw
+
+ additionalProperties: false
+
+required:
+ - compatible
+ - reg
+ - reg-names
+ - clocks
+ - dmas
+ - dma-names
+ - "#address-cells"
+ - "#size-cells"
+
+additionalProperties: false
+
+examples:
+ - |
+ nand-controller@e0f00000 {
+ compatible = "intel,lgm-nand";
+ reg = <0xe0f00000 0x100>,
+ <0xe1000000 0x300>,
+ <0xe1400000 0x8000>,
+ <0xe1c00000 0x1000>,
+ <0x17400000 0x4>,
+ <0x17c00000 0x4>;
+ reg-names = "ebunand", "hsnand", "nand_cs0", "nand_cs1",
+ "addr_sel0", "addr_sel1";
+ clocks = <&cgu0 125>;
+ dmas = <&dma0 8>, <&dma0 9>;
+ dma-names = "tx", "rx";
+ #address-cells = <1>;
+ #size-cells = <0>;
+
+ nand@0 {
+ reg = <0>;
+ nand-ecc-mode = "hw";
+ };
+ };
+
+...
--
2.11.0
^ permalink raw reply related
* [PATCH v11 0/2] mtd: rawnand: Add NAND controller support on Intel LGM SoC
From: Ramuthevar,Vadivel MuruganX @ 2020-05-30 0:51 UTC (permalink / raw)
To: linux-kernel, linux-mtd, devicetree, miquel.raynal
Cc: richard, vigneshr, arnd, brendanhiggins, tglx, boris.brezillon,
anders.roxell, masonccyang, robh+dt, linux-mips, hauke.mehrtens,
andriy.shevchenko, qi-ming.wu, cheol.yong.kim,
Ramuthevar,Vadivel MuruganX
This patch adds the new IP of Nand Flash Controller(NFC) support
on Intel's Lightning Mountain(LGM) SoC.
DMA is used for burst data transfer operation, also DMA HW supports
aligned 32bit memory address and aligned data access by default.
DMA burst of 8 supported. Data register used to support the read/write
operation from/to device.
NAND controller also supports in-built HW ECC engine.
NAND controller driver implements ->exec_op() to replace legacy hooks,
these specific call-back method to execute NAND operations.
Thanks Boris, Andy, Arnd and Rob for the review comments and suggestions.
---
v11:
- No Change
v10:
- No Change
v9:
- No change
v8:
- fix the kbuild bot warnings
- correct the typo's
v7:
- indentation issue is fixed
- add error check for retrieve the resource from dt
v6:
- update EBU_ADDR_SELx register base value build it from DT
- Add tabs in in Kconfig
v5:
- replace by 'HSNAND_CLE_OFFS | HSNAND_CS_OFFS' to NAND_WRITE_CMD and NAND_WRITE_ADDR
- remove the unused macros
- update EBU_ADDR_MASK(x) macro
- update the EBU_ADDR_SELx register values to be written
v4:
- add ebu_nand_cs structure for multiple-CS support
- mask/offset encoding for 0x51 value
- update macro HSNAND_CTL_ENABLE_ECC
- drop the op argument and un-used macros.
- updated the datatype and macros
- add function disable nand module
- remove ebu_host->dma_rx = NULL;
- rename MMIO address range variables to ebu and hsnand
- implement ->setup_data_interface()
- update label err_cleanup_nand and err_cleanup_dma
- add return value check in the nand_remove function
- add/remove tabs and spaces as per coding standard
- encoded CS ids by reg property
v3:
- Add depends on MACRO in Kconfig
- file name update in Makefile
- file name update to intel-nand-controller
- modification of MACRO divided like EBU, HSNAND and NAND
- add NAND_ALE_OFFS, NAND_CLE_OFFS and NAND_CS_OFFS
- rename lgm_ to ebu_ and _va suffix is removed in the whole file
- rename structure and varaibles as per review comments.
- remove lgm_read_byte(), lgm_dev_ready() and cmd_ctrl() un-used function
- update in exec_op() as per review comments
- rename function lgm_dma_exit() by lgm_dma_cleanup()
- hardcoded magic value for base and offset replaced by MACRO defined
- mtd_device_unregister() + nand_cleanup() instead of nand_release()
v2:
- implement the ->exec_op() to replaces the legacy hook-up.
- update the commit message
- add MIPS maintainers and xway_nand driver author in CC
v1:
- initial version
dt-bindings: mtd: Add Nand Flash Controller support for Intel LGM SoC
---
v11:
- Fixed the compatible issue with example
10:
- fix bot errors
v9:
- Rob's review comments address
- dual licensed
- compatible change
- add reg-names
- drop clock-names and clock-cells
- correct typo's
v8:
No change
v7:
- Rob's review comments addressed
- dt-schema build issue fixed with upgraded dt-schema
v6:
- Rob's review comments addressed in YAML file
- add addr_sel0 and addr_sel1 reg-names in YAML example
v5:
- add the example in YAML file
v4:
- No change
v3:
- No change
v2:
YAML compatible string update to intel, lgm-nand-controller
v1:
- initial version
Ramuthevar Vadivel Murugan (2):
dt-bindings: mtd: Add Nand Flash Controller support for Intel LGM SoC
mtd: rawnand: Add NAND controller support on Intel LGM SoC
.../devicetree/bindings/mtd/intel,lgm-nand.yaml | 99 +++
drivers/mtd/nand/raw/Kconfig | 8 +
drivers/mtd/nand/raw/Makefile | 1 +
drivers/mtd/nand/raw/intel-nand-controller.c | 747 +++++++++++++++++++++
4 files changed, 855 insertions(+)
create mode 100644 Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml
create mode 100644 drivers/mtd/nand/raw/intel-nand-controller.c
--
2.11.0
^ permalink raw reply
* Re: [PATCH v10 1/2] dt-bindings: mtd: Add Nand Flash Controller support for Intel LGM SoC
From: Ramuthevar, Vadivel MuruganX @ 2020-05-30 0:18 UTC (permalink / raw)
To: Rob Herring
Cc: linux-kernel, linux-mtd, devicetree, miquel.raynal, richard,
vigneshr, arnd, brendanhiggins, tglx, boris.brezillon,
anders.roxell, masonccyang, linux-mips, hauke.mehrtens,
andriy.shevchenko, qi-ming.wu, cheol.yong.kim
In-Reply-To: <20200529193130.GA2805164@bogus>
Hi Rob,
On 30/5/2020 3:31 am, Rob Herring wrote:
> On Thu, May 28, 2020 at 11:39:28PM +0800, Ramuthevar,Vadivel MuruganX wrote:
>> From: Ramuthevar Vadivel Murugan<vadivel.muruganx.ramuthevar@linux.intel.com>
>>
>> Add YAML file for dt-bindings to support NAND Flash Controller
>> on Intel's Lightning Mountain SoC.
>>
>> Signed-off-by: Ramuthevar Vadivel Murugan<vadivel.muruganx.ramuthevar@linux.intel.com>
>> ---
>> .../devicetree/bindings/mtd/intel,lgm-nand.yaml | 93 ++++++++++++++++++++++
>> 1 file changed, 93 insertions(+)
>> create mode 100644 Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml
>>
>> diff --git a/Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml b/Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml
>> new file mode 100644
>> index 000000000000..afecc9920e04
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/mtd/intel,lgm-nand.yaml
>> @@ -0,0 +1,93 @@
>> +# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
>> +%YAML 1.2
>> +---
>> +$id:http://devicetree.org/schemas/mtd/intel,lgm-nand.yaml#
>> +$schema:http://devicetree.org/meta-schemas/core.yaml#
>> +
>> +title: Intel LGM SoC NAND Controller Device Tree Bindings
>> +
>> +allOf:
>> + - $ref: "nand-controller.yaml"
>> +
>> +maintainers:
>> + - Ramuthevar Vadivel Murugan<vadivel.muruganx.ramuthevar@linux.intel.com>
>> +
>> +properties:
>> + compatible:
>> + const: intel,lgm-nand-controller
> Doesn't match the example.
Thank you for the review comments...
if we add the compatible = intel,lgm-nand-controller it throws an error
like below..
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/mtd/intel,lgm-nand.example.dt.yaml:
nand-controller@e0f00000: '#address-cells', '#size-cells' do not match
any of the regexes: '^nand@[a-f0-9]+$', 'pinctrl-[0-9]+'
/builds/robherring/linux-dt-review/Documentation/devicetree/bindings/mtd/intel,lgm-nand.example.dt.yaml:
nand-controller@e0f00000: nand@0: '#address-cells', '#size-cells',
'nand-on-flash-bbt' do not match any of the regexes: 'pinctrl-[0-9]+'
referred from this file
:Documentation/devicetree/bindings/mtd/nand-controller.yaml
fixed the compatible and example doesn't match issue.
Regards
Vadivel
>
^ permalink raw reply
* Re: [PATCH net-next v2] dt-bindings: net: rename the bindings document for MediaTek STAR EMAC
From: David Miller @ 2020-05-30 0:07 UTC (permalink / raw)
To: brgl
Cc: kuba, robh+dt, matthias.bgg, netdev, devicetree, linux-kernel,
linux-arm-kernel, linux-mediatek, fparent, stephane.leprovost,
pedro.tsai, andrew.perepech, bgolaszewski
In-Reply-To: <20200528135902.14041-1-brgl@bgdev.pl>
From: Bartosz Golaszewski <brgl@bgdev.pl>
Date: Thu, 28 May 2020 15:59:02 +0200
> From: Bartosz Golaszewski <bgolaszewski@baylibre.com>
>
> The driver itself was renamed before getting merged into mainline, but
> the binding document kept the old name. This makes both names consistent.
>
> Signed-off-by: Bartosz Golaszewski <bgolaszewski@baylibre.com>
> ---
> v1 -> v2:
> - update the id field as well
Applied, thank you.
^ permalink raw reply
* Re: [PATCH 1/2] dt-bindings: chrome: Add cros-ec-typec mux props
From: Prashant Malani @ 2020-05-29 23:30 UTC (permalink / raw)
To: Rob Herring
Cc: Heikki Krogerus, linux-kernel@vger.kernel.org, Tim Wawrzynczak,
Benson Leung,
open list:OPEN FIRMWARE AND FLATTENED DEVICE TREE BINDINGS,
Enric Balletbo i Serra, Guenter Roeck
In-Reply-To: <CAL_JsqJ2pbh5BbjGd9eEiD6-sV94=omk6o+mLXjCYiVnUOtO=g@mail.gmail.com>
Hi Rob,
Thanks for reviewing the patch! Kindly see inline:
On Fri, May 29, 2020 at 2:55 PM Rob Herring <robh@kernel.org> wrote:
>
> > > " Reference to a DT node for the USB Type C Multiplexer controlling the
> > > data lines routing for this connector. This switch is assumed registered
> > > with the Type C connector class framework, which requires it to be named
> > > this way."
> > > >
> > > > > + mode-switch:
> > > > > + description: Reference to a DT node for the USB Type C Multiplexer
> > > > > + controlling the data lines routing for this connector.
> > > >
> > > > This is for alternate mode muxing I presume.
> > >
> > > Yes, that's right.
> > > >
> > > > We already have a mux-control binding. Why not use that here?
> > >
> > > Heikki might be able to offer more insight into why this is the case,
> > > since the connector class framework seems to expect a phandle and for
> > > the device driver to implement a "set" command. Heikki, would you happen to know?
> >
> > The mode-switch here would actually represent the "consumer" part in
> > the mux-control bindings. So the mux-controls would describe the
> > relationship between the "mode-switch" and the mux controller(s),
> > while the mode-switch property describes the relationship between
> > something like USB Type-C Port Manager (or this cros_ec function) and
> > the "mux consumer".
>
> The "USB Type-C Port Manager" is not just the parent node in your case?
>
> Can you point me to what you expect your DT to look like showing the
> mode switch node, the connector, the USB host(s), and the DP/HDMI
> bridge/output?
Caveat: I'm not a DT expert and not well-versed with the mux-control
bindings, so Heikki may be able to describe these better.
That said, here is my attempt to show the nodes you requested, cobbled
together from the Rockchip rk3399 DTSI[1] and
swboyd's connector binding example [2].
Nodes truncated and unrelated fields omitted in the interest of brevity:
// Chrome OS EC Type C Port Manager.
typec {
compatible = "google,cros-ec-typec";
#address-cells = <1>;
#size-cells = <0>;
connector@0 {
compatible = "usb-c-connector";
reg = <0>;
power-role = "dual";
data-role = "dual";
try-power-role = "source";
mode-switch = <&foo_mux>;
// Other switches can point to the same mux.
....
};
};
// Mux switch
// TODO: Can possibly embed this in the PHY controller node itself?
foo_mux {
compatible = "vendor,typec-mux";
mux-gpios = <&gpio_controller 23 GPIO_ACTIVE_HIGH>;
ports {
#address-cells = <1>;
#size-cells = <0>;
port@0 {
reg = <0>;
mux_dp_in: endpoint {
remote-endpoint = <&dp_phy_out>;
};
};
port@1 {
reg = <1>;
mux_usb_in: endpoint1 {
remote-endpoint = <&usb3_phy_out>;
};
};
};
};
// Type C PHY Controller.
tcphy0: phy@ff7c0000 {
compatible = "rockchip,rk3399-typec-phy";
reg = <0x0 0xff7c0000 0x0 0x40000>;
...
tcphy0_dp: phy@dc00000 {
compatible = "soc,dp-phy";
reg = <0xdc00000 0x1000>;
ports {
port@0 {
reg = <0>;
dp_phy_out: endpoint {
remote-endpoint = <&mux_dp_in>;
};
};
};
};
tcphy0_usb3: phy@db00000 {
compatible = "soc,usb3-phy";
reg = <0xdb00000 0x1000>;
ports {
port@0 {
reg = <0>;
usb3_phy_out: endpoint {
remote-endpoint = <&mux_usb3_in>;
};
};
};
};
};
// USB3 Host controller
usbdrd3_0: usb@fe800000 {
compatible = "rockchip,rk3399-dwc3";
#address-cells = <2>;
#size-cells = <2>;
clocks = ...;
clock-names = ...;
status = "disabled";
usbdrd_dwc3_0: usb@fe800000 {
compatible = "snps,dwc3";
reg = <0x0 0xfe800000 0x0 0x100000>;
interrupts = <GIC_SPI 105 IRQ_TYPE_LEVEL_HIGH 0>;
clocks = ...;
clock-names = ...;
dr_mode = "otg";
phys = <&tcphy0_usb3>;
phy-names = "usb3-phy";
phy_type = "utmi_wide";
power-domains = <&power RK3399_PD_USB3>;
status = "disabled";
};
};
// DP controller
cdn_dp: dp@fec00000 {
compatible = "rockchip,rk3399-cdn-dp";
reg = <0x0 0xfec00000 0x0 0x100000>;
interrupts = ...;
clocks = ...;
clock-names = ...;
phys = <&tcphy0_dp>;
...
ports {
dp_in: port {
#address-cells = <1>;
#size-cells = <0>;
dp_in_vopb: endpoint@0 {
reg = <0>;
remote-endpoint = <&vopb_out_dp>;
};
dp_in_vopl: endpoint@1 {
reg = <1>;
remote-endpoint = <&vopl_out_dp>;
};
};
};
};
[1] : https://chromium.googlesource.com/chromiumos/third_party/kernel/+/refs/heads/chromeos-5.4/arch/arm64/boot/dts/rockchip/rk3399.dtsi
[2]: https://lkml.org/lkml/2020/2/28/1081
Hope this helps, and my apologies in advance for any errors.
Best regards,
-Prashant
>
> Rob
^ permalink raw reply
* [PATCH v4 3/9] ARM: dts: at91: sama5d2: add TCB GCLK
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
The sama5d2 tcbs take an extra input clock, their gclk.
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
arch/arm/boot/dts/sama5d2.dtsi | 12 ++++++------
1 file changed, 6 insertions(+), 6 deletions(-)
diff --git a/arch/arm/boot/dts/sama5d2.dtsi b/arch/arm/boot/dts/sama5d2.dtsi
index ab550d69db91..996143e966d8 100644
--- a/arch/arm/boot/dts/sama5d2.dtsi
+++ b/arch/arm/boot/dts/sama5d2.dtsi
@@ -499,23 +499,23 @@ macb0: ethernet@f8008000 {
};
tcb0: timer@f800c000 {
- compatible = "atmel,at91sam9x5-tcb", "simple-mfd", "syscon";
+ compatible = "atmel,sama5d2-tcb", "simple-mfd", "syscon";
#address-cells = <1>;
#size-cells = <0>;
reg = <0xf800c000 0x100>;
interrupts = <35 IRQ_TYPE_LEVEL_HIGH 0>;
- clocks = <&pmc PMC_TYPE_PERIPHERAL 35>, <&clk32k>;
- clock-names = "t0_clk", "slow_clk";
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 35>, <&pmc PMC_TYPE_GCK 35>, <&clk32k>;
+ clock-names = "t0_clk", "gclk", "slow_clk";
};
tcb1: timer@f8010000 {
- compatible = "atmel,at91sam9x5-tcb", "simple-mfd", "syscon";
+ compatible = "atmel,sama5d2-tcb", "simple-mfd", "syscon";
#address-cells = <1>;
#size-cells = <0>;
reg = <0xf8010000 0x100>;
interrupts = <36 IRQ_TYPE_LEVEL_HIGH 0>;
- clocks = <&pmc PMC_TYPE_PERIPHERAL 36>, <&clk32k>;
- clock-names = "t0_clk", "slow_clk";
+ clocks = <&pmc PMC_TYPE_PERIPHERAL 36>, <&pmc PMC_TYPE_GCK 36>, <&clk32k>;
+ clock-names = "t0_clk", "gclk", "slow_clk";
};
hsmc: hsmc@f8014000 {
--
2.26.2
^ permalink raw reply related
* [PATCH v4 1/9] dt-bindings: atmel-tcb: convert bindings to json-schema
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni, Rob Herring
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
Convert Atmel Timer Counter Blocks bindings to DT schema format using
json-schema.
Also move it out of mfd as it is not and has never been related to mfd.
Cc: Rob Herring <robh+dt@kernel.org>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
Changes in v4:
- use oneOf to describe possible clock-names list
.../devicetree/bindings/mfd/atmel-tcb.txt | 56 --------
.../soc/microchip/atmel,at91rm9200-tcb.yaml | 131 ++++++++++++++++++
2 files changed, 131 insertions(+), 56 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/mfd/atmel-tcb.txt
create mode 100644 Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml
diff --git a/Documentation/devicetree/bindings/mfd/atmel-tcb.txt b/Documentation/devicetree/bindings/mfd/atmel-tcb.txt
deleted file mode 100644
index c4a83e364cb6..000000000000
--- a/Documentation/devicetree/bindings/mfd/atmel-tcb.txt
+++ /dev/null
@@ -1,56 +0,0 @@
-* Device tree bindings for Atmel Timer Counter Blocks
-- compatible: Should be "atmel,<chip>-tcb", "simple-mfd", "syscon".
- <chip> can be "at91rm9200" or "at91sam9x5"
-- reg: Should contain registers location and length
-- #address-cells: has to be 1
-- #size-cells: has to be 0
-- interrupts: Should contain all interrupts for the TC block
- Note that you can specify several interrupt cells if the TC
- block has one interrupt per channel.
-- clock-names: tuple listing input clock names.
- Required elements: "t0_clk", "slow_clk"
- Optional elements: "t1_clk", "t2_clk"
-- clocks: phandles to input clocks.
-
-The TCB can expose multiple subdevices:
- * a timer
- - compatible: Should be "atmel,tcb-timer"
- - reg: Should contain the TCB channels to be used. If the
- counter width is 16 bits (at91rm9200-tcb), two consecutive
- channels are needed. Else, only one channel will be used.
-
-Examples:
-
-One interrupt per TC block:
- tcb0: timer@fff7c000 {
- compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
- #address-cells = <1>;
- #size-cells = <0>;
- reg = <0xfff7c000 0x100>;
- interrupts = <18 4>;
- clocks = <&tcb0_clk>, <&clk32k>;
- clock-names = "t0_clk", "slow_clk";
-
- timer@0 {
- compatible = "atmel,tcb-timer";
- reg = <0>, <1>;
- };
-
- timer@2 {
- compatible = "atmel,tcb-timer";
- reg = <2>;
- };
- };
-
-One interrupt per TC channel in a TC block:
- tcb1: timer@fffdc000 {
- compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
- #address-cells = <1>;
- #size-cells = <0>;
- reg = <0xfffdc000 0x100>;
- interrupts = <26 4>, <27 4>, <28 4>;
- clocks = <&tcb1_clk>, <&clk32k>;
- clock-names = "t0_clk", "slow_clk";
- };
-
-
diff --git a/Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml b/Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml
new file mode 100644
index 000000000000..9d680e0b9109
--- /dev/null
+++ b/Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml
@@ -0,0 +1,131 @@
+# SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: "http://devicetree.org/schemas/soc/microchip/atmel,at91rm9200-tcb.yaml#"
+$schema: "http://devicetree.org/meta-schemas/core.yaml#"
+
+title: Atmel Timer Counter Block
+
+maintainers:
+ - Alexandre Belloni <alexandre.belloni@bootlin.com>
+
+description: |
+ The Atmel (now Microchip) SoCs have timers named Timer Counter Block. Each
+ timer has three channels with two counters each.
+
+properties:
+ compatible:
+ items:
+ - enum:
+ - atmel,at91rm9200-tcb
+ - atmel,at91sam9x5-tcb
+ - const: simple-mfd
+ - const: syscon
+
+ reg:
+ maxItems: 1
+
+ interrupts:
+ description:
+ List of interrupts. One interrupt per TCB channel if available or one
+ interrupt for the TC block
+ minItems: 1
+ maxItems: 3
+
+ clock-names:
+ description:
+ List of clock names. Always includes t0_clk and slow clk. Also includes
+ t1_clk and t2_clk if a clock per channel is available.
+ oneOf:
+ - items:
+ - const: t0_clk
+ - const: slow_clk
+ - items:
+ - const: t0_clk
+ - const: t1_clk
+ - const: t2_clk
+ - const: slow_clk
+ minItems: 2
+ maxItems: 4
+
+ clocks:
+ minItems: 2
+ maxItems: 4
+
+ '#address-cells':
+ const: 1
+
+ '#size-cells':
+ const: 0
+
+patternProperties:
+ "^timer@[0-2]$":
+ description: The timer block channels that are used as timers.
+ type: object
+ properties:
+ compatible:
+ const: atmel,tcb-timer
+ reg:
+ description:
+ List of channels to use for this particular timer.
+ minItems: 1
+ maxItems: 3
+
+ required:
+ - compatible
+ - reg
+
+required:
+ - compatible
+ - reg
+ - interrupts
+ - clocks
+ - clock-names
+ - '#address-cells'
+ - '#size-cells'
+
+additionalProperties: false
+
+examples:
+ - |
+ /* One interrupt per TC block: */
+ tcb0: timer@fff7c000 {
+ compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0xfff7c000 0x100>;
+ interrupts = <18 4>;
+ clocks = <&tcb0_clk>, <&clk32k>;
+ clock-names = "t0_clk", "slow_clk";
+
+ timer@0 {
+ compatible = "atmel,tcb-timer";
+ reg = <0>, <1>;
+ };
+
+ timer@2 {
+ compatible = "atmel,tcb-timer";
+ reg = <2>;
+ };
+ };
+
+ /* One interrupt per TC channel in a TC block: */
+ tcb1: timer@fffdc000 {
+ compatible = "atmel,at91rm9200-tcb", "simple-mfd", "syscon";
+ #address-cells = <1>;
+ #size-cells = <0>;
+ reg = <0xfffdc000 0x100>;
+ interrupts = <26 4>, <27 4>, <28 4>;
+ clocks = <&tcb1_clk>, <&clk32k>;
+ clock-names = "t0_clk", "slow_clk";
+
+ timer@0 {
+ compatible = "atmel,tcb-timer";
+ reg = <0>;
+ };
+
+ timer@1 {
+ compatible = "atmel,tcb-timer";
+ reg = <1>;
+ };
+ };
--
2.26.2
^ permalink raw reply related
* [PATCH v4 4/9] ARM: at91: add atmel tcb capabilities
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
From: Kamel Bouhara <kamel.bouhara@bootlin.com>
Some atmel socs have extra tcb capabilities that allow using a generic
clock source or enabling a quadrature decoder.
Signed-off-by: Kamel Bouhara <kamel.bouhara@bootlin.com>
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
include/soc/at91/atmel_tcb.h | 5 +++++
1 file changed, 5 insertions(+)
diff --git a/include/soc/at91/atmel_tcb.h b/include/soc/at91/atmel_tcb.h
index c3c7200ce151..1d7071dc0bca 100644
--- a/include/soc/at91/atmel_tcb.h
+++ b/include/soc/at91/atmel_tcb.h
@@ -36,9 +36,14 @@ struct clk;
/**
* struct atmel_tcb_config - SoC data for a Timer/Counter Block
* @counter_width: size in bits of a timer counter register
+ * @has_gclk: boolean indicating if a timer counter has a generic clock
+ * @has_qdec: boolean indicating if a timer counter has a quadrature
+ * decoder.
*/
struct atmel_tcb_config {
size_t counter_width;
+ bool has_gclk;
+ bool has_qdec;
};
/**
--
2.26.2
^ permalink raw reply related
* [PATCH v4 9/9] clocksource/drivers/timer-atmel-tcb: add sama5d2 support
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
The first divisor for the sama5d2 is actually the gclk selector. Because
the currently remaining divisors are fitting the use case, currently ensure
it is skipped.
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
drivers/clocksource/timer-atmel-tcb.c | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
index ccb77b9cb489..e373b02d509a 100644
--- a/drivers/clocksource/timer-atmel-tcb.c
+++ b/drivers/clocksource/timer-atmel-tcb.c
@@ -359,9 +359,15 @@ static struct atmel_tcb_config tcb_sam9x5_config = {
.counter_width = 32,
};
+static struct atmel_tcb_config tcb_sama5d2_config = {
+ .counter_width = 32,
+ .has_gclk = 1,
+};
+
static const struct of_device_id atmel_tcb_of_match[] = {
{ .compatible = "atmel,at91rm9200-tcb", .data = &tcb_rm9200_config, },
{ .compatible = "atmel,at91sam9x5-tcb", .data = &tcb_sam9x5_config, },
+ { .compatible = "atmel,sama5d2-tcb", .data = &tcb_sama5d2_config, },
{ /* sentinel */ }
};
@@ -426,7 +432,10 @@ static int __init tcb_clksrc_init(struct device_node *node)
/* How fast will we be counting? Pick something over 5 MHz. */
rate = (u32) clk_get_rate(t0_clk);
- for (i = 0; i < ARRAY_SIZE(atmel_tcb_divisors); i++) {
+ i = 0;
+ if (tc.tcb_config->has_gclk)
+ i = 1;
+ for (; i < ARRAY_SIZE(atmel_tcb_divisors); i++) {
unsigned divisor = atmel_tcb_divisors[i];
unsigned tmp;
--
2.26.2
^ permalink raw reply related
* [PATCH v4 8/9] clocksource/drivers/timer-atmel-tcb: allow selecting first divider
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
The divider selection algorithm never allowed to get index 0. It was also
continuing to look for dividers, trying to find the slow clock selection.
This is not necessary anymore.
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
drivers/clocksource/timer-atmel-tcb.c | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
index 8fcd4d74c54b..ccb77b9cb489 100644
--- a/drivers/clocksource/timer-atmel-tcb.c
+++ b/drivers/clocksource/timer-atmel-tcb.c
@@ -432,10 +432,8 @@ static int __init tcb_clksrc_init(struct device_node *node)
tmp = rate / divisor;
pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp);
- if (best_divisor_idx > 0) {
- if (tmp < 5 * 1000 * 1000)
- continue;
- }
+ if ((best_divisor_idx >= 0) && (tmp < 5 * 1000 * 1000))
+ break;
divided_rate = tmp;
best_divisor_idx = i;
}
--
2.26.2
^ permalink raw reply related
* [PATCH v4 7/9] clocksource/drivers/timer-atmel-tcb: stop using the 32kHz for clockevents
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
Stop using the slow clock as the clock source for 32 bit counters because
even at 10MHz, they are able to handle delays up to two minutes. This
provides a way better resolution.
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
drivers/clocksource/timer-atmel-tcb.c | 61 ++++++++++++++-------------
1 file changed, 32 insertions(+), 29 deletions(-)
diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
index 423af2f9835f..8fcd4d74c54b 100644
--- a/drivers/clocksource/timer-atmel-tcb.c
+++ b/drivers/clocksource/timer-atmel-tcb.c
@@ -27,9 +27,10 @@
* - Some chips support 32 bit counter. A single channel is used for
* this 32 bit free-running counter. the second channel is not used.
*
- * - The third channel may be used to provide a 16-bit clockevent
- * source, used in either periodic or oneshot mode. This runs
- * at 32 KiHZ, and can handle delays of up to two seconds.
+ * - The third channel may be used to provide a clockevent source, used in
+ * either periodic or oneshot mode. For 16-bit counter its runs at 32 KiHZ,
+ * and can handle delays of up to two seconds. For 32-bit counters, it runs at
+ * the same rate as the clocksource
*
* REVISIT behavior during system suspend states... we should disable
* all clocks and save the power. Easily done for clockevent devices,
@@ -47,6 +48,8 @@ static struct
} tcb_cache[3];
static u32 bmr_cache;
+static const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128 };
+
static u64 tc_get_cycles(struct clocksource *cs)
{
unsigned long flags;
@@ -151,13 +154,6 @@ static struct tc_clkevt_device *to_tc_clkevt(struct clock_event_device *clkevt)
return container_of(clkevt, struct tc_clkevt_device, clkevt);
}
-/* For now, we always use the 32K clock ... this optimizes for NO_HZ,
- * because using one of the divided clocks would usually mean the
- * tick rate can never be less than several dozen Hz (vs 0.5 Hz).
- *
- * A divided clock could be good for high resolution timers, since
- * 30.5 usec resolution can seem "low".
- */
static u32 timer_clock;
static int tc_shutdown(struct clock_event_device *d)
@@ -183,7 +179,7 @@ static int tc_set_oneshot(struct clock_event_device *d)
clk_enable(tcd->clk);
- /* slow clock, count up to RC, then irq and stop */
+ /* count up to RC, then irq and stop */
writel(timer_clock | ATMEL_TC_CPCSTOP | ATMEL_TC_WAVE |
ATMEL_TC_WAVESEL_UP_AUTO, regs + ATMEL_TC_REG(2, CMR));
writel(ATMEL_TC_CPCS, regs + ATMEL_TC_REG(2, IER));
@@ -205,7 +201,7 @@ static int tc_set_periodic(struct clock_event_device *d)
*/
clk_enable(tcd->clk);
- /* slow clock, count up to RC, then irq and restart */
+ /* count up to RC, then irq and restart */
writel(timer_clock | ATMEL_TC_WAVE | ATMEL_TC_WAVESEL_UP_AUTO,
regs + ATMEL_TC_REG(2, CMR));
writel((32768 + HZ / 2) / HZ, tcaddr + ATMEL_TC_REG(2, RC));
@@ -256,47 +252,56 @@ static irqreturn_t ch2_irq(int irq, void *handle)
return IRQ_NONE;
}
-static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx)
+static int __init setup_clkevents(struct atmel_tc *tc, int divisor_idx)
{
+ u32 rate;
int ret;
struct clk *t2_clk = tc->clk[2];
int irq = tc->irq[2];
-
- ret = clk_prepare_enable(tc->slow_clk);
- if (ret)
- return ret;
+ int bits = tc->tcb_config->counter_width;
/* try to enable t2 clk to avoid future errors in mode change */
ret = clk_prepare_enable(t2_clk);
- if (ret) {
- clk_disable_unprepare(tc->slow_clk);
+ if (ret)
return ret;
- }
-
- clk_disable(t2_clk);
clkevt.regs = tc->regs;
clkevt.clk = t2_clk;
- timer_clock = clk32k_divisor_idx;
+ if (bits == 32) {
+ timer_clock = divisor_idx;
+ rate = clk_get_rate(t2_clk) / atmel_tcb_divisors[divisor_idx];
+ } else {
+ ret = clk_prepare_enable(tc->slow_clk);
+ if (ret) {
+ clk_disable_unprepare(t2_clk);
+ return ret;
+ }
+
+ rate = clk_get_rate(tc->slow_clk);
+ timer_clock = ATMEL_TC_TIMER_CLOCK5;
+ }
+
+ clk_disable(t2_clk);
clkevt.clkevt.cpumask = cpumask_of(0);
ret = request_irq(irq, ch2_irq, IRQF_TIMER, "tc_clkevt", &clkevt);
if (ret) {
clk_unprepare(t2_clk);
- clk_disable_unprepare(tc->slow_clk);
+ if (bits != 32)
+ clk_disable_unprepare(tc->slow_clk);
return ret;
}
- clockevents_config_and_register(&clkevt.clkevt, 32768, 1, 0xffff);
+ clockevents_config_and_register(&clkevt.clkevt, rate, 1, BIT(bits) - 1);
return ret;
}
#else /* !CONFIG_GENERIC_CLOCKEVENTS */
-static int __init setup_clkevents(struct atmel_tc *tc, int clk32k_divisor_idx)
+static int __init setup_clkevents(struct atmel_tc *tc, int divisor_idx)
{
/* NOTHING */
return 0;
@@ -346,8 +351,6 @@ static void __init tcb_setup_single_chan(struct atmel_tc *tc, int mck_divisor_id
writel(ATMEL_TC_SYNC, tcaddr + ATMEL_TC_BCR);
}
-static const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128 };
-
static struct atmel_tcb_config tcb_rm9200_config = {
.counter_width = 16,
};
@@ -472,7 +475,7 @@ static int __init tcb_clksrc_init(struct device_node *node)
goto err_disable_t1;
/* channel 2: periodic and oneshot timer support */
- ret = setup_clkevents(&tc, ATMEL_TC_TIMER_CLOCK5);
+ ret = setup_clkevents(&tc, best_divisor_idx);
if (ret)
goto err_unregister_clksrc;
--
2.26.2
^ permalink raw reply related
* [PATCH v4 5/9] clocksource/drivers/timer-atmel-tcb: rework 32khz clock selection
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
On all the supported SoCs, the slow clock is always ATMEL_TC_TIMER_CLOCK5,
avoid looking it up and pass it directly to setup_clkevents.
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
drivers/clocksource/timer-atmel-tcb.c | 11 ++---------
1 file changed, 2 insertions(+), 9 deletions(-)
diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
index 7427b07495a8..b255a4a1a36b 100644
--- a/drivers/clocksource/timer-atmel-tcb.c
+++ b/drivers/clocksource/timer-atmel-tcb.c
@@ -346,7 +346,7 @@ static void __init tcb_setup_single_chan(struct atmel_tc *tc, int mck_divisor_id
writel(ATMEL_TC_SYNC, tcaddr + ATMEL_TC_BCR);
}
-static const u8 atmel_tcb_divisors[5] = { 2, 8, 32, 128, 0, };
+static const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128 };
static const struct of_device_id atmel_tcb_of_match[] = {
{ .compatible = "atmel,at91rm9200-tcb", .data = (void *)16, },
@@ -362,7 +362,6 @@ static int __init tcb_clksrc_init(struct device_node *node)
u64 (*tc_sched_clock)(void);
u32 rate, divided_rate = 0;
int best_divisor_idx = -1;
- int clk32k_divisor_idx = -1;
int bits;
int i;
int ret;
@@ -416,12 +415,6 @@ static int __init tcb_clksrc_init(struct device_node *node)
unsigned divisor = atmel_tcb_divisors[i];
unsigned tmp;
- /* remember 32 KiHz clock for later */
- if (!divisor) {
- clk32k_divisor_idx = i;
- continue;
- }
-
tmp = rate / divisor;
pr_debug("TC: %u / %-3u [%d] --> %u\n", rate, divisor, i, tmp);
if (best_divisor_idx > 0) {
@@ -467,7 +460,7 @@ static int __init tcb_clksrc_init(struct device_node *node)
goto err_disable_t1;
/* channel 2: periodic and oneshot timer support */
- ret = setup_clkevents(&tc, clk32k_divisor_idx);
+ ret = setup_clkevents(&tc, ATMEL_TC_TIMER_CLOCK5);
if (ret)
goto err_unregister_clksrc;
--
2.26.2
^ permalink raw reply related
* [PATCH v4 6/9] clocksource/drivers/timer-atmel-tcb: fill tcb_config
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
Use the tcb_config and struct atmel_tcb_config to get the timer counter
width. This is necessary because atmel_tcb_config will be extended later
on.
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
---
drivers/clocksource/timer-atmel-tcb.c | 18 +++++++++++++++---
1 file changed, 15 insertions(+), 3 deletions(-)
diff --git a/drivers/clocksource/timer-atmel-tcb.c b/drivers/clocksource/timer-atmel-tcb.c
index b255a4a1a36b..423af2f9835f 100644
--- a/drivers/clocksource/timer-atmel-tcb.c
+++ b/drivers/clocksource/timer-atmel-tcb.c
@@ -348,9 +348,17 @@ static void __init tcb_setup_single_chan(struct atmel_tc *tc, int mck_divisor_id
static const u8 atmel_tcb_divisors[] = { 2, 8, 32, 128 };
+static struct atmel_tcb_config tcb_rm9200_config = {
+ .counter_width = 16,
+};
+
+static struct atmel_tcb_config tcb_sam9x5_config = {
+ .counter_width = 32,
+};
+
static const struct of_device_id atmel_tcb_of_match[] = {
- { .compatible = "atmel,at91rm9200-tcb", .data = (void *)16, },
- { .compatible = "atmel,at91sam9x5-tcb", .data = (void *)32, },
+ { .compatible = "atmel,at91rm9200-tcb", .data = &tcb_rm9200_config, },
+ { .compatible = "atmel,at91sam9x5-tcb", .data = &tcb_sam9x5_config, },
{ /* sentinel */ }
};
@@ -398,7 +406,11 @@ static int __init tcb_clksrc_init(struct device_node *node)
}
match = of_match_node(atmel_tcb_of_match, node->parent);
- bits = (uintptr_t)match->data;
+ if (!match)
+ return -ENODEV;
+
+ tc.tcb_config = match->data;
+ bits = tc.tcb_config->counter_width;
for (i = 0; i < ARRAY_SIZE(tc.irq); i++)
writel(ATMEL_TC_ALL_IRQ, tc.regs + ATMEL_TC_REG(i, IDR));
--
2.26.2
^ permalink raw reply related
* [PATCH v4 2/9] dt-bindings: microchip: atmel,at91rm9200-tcb: add sama5d2 compatible
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni, Rob Herring
In-Reply-To: <20200529232749.299627-1-alexandre.belloni@bootlin.com>
The sama5d2 TC block TIMER_CLOCK1 is different from the at91sam9x5 one.
Instead of being MCK / 2, it is the TCB GCLK.
Signed-off-by: Alexandre Belloni <alexandre.belloni@bootlin.com>
Reviewed-by: Rob Herring <robh+dt@kernel.org>
---
Reviewed by tag taken from:
https://lore.kernel.org/linux-arm-kernel/20200526225046.GA534667@bogus/
.../soc/microchip/atmel,at91rm9200-tcb.yaml | 42 +++++++++++++++----
1 file changed, 33 insertions(+), 9 deletions(-)
diff --git a/Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml b/Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml
index 9d680e0b9109..d226fd7d5258 100644
--- a/Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml
+++ b/Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml
@@ -19,6 +19,7 @@ properties:
- enum:
- atmel,at91rm9200-tcb
- atmel,at91sam9x5-tcb
+ - atmel,sama5d2-tcb
- const: simple-mfd
- const: syscon
@@ -36,15 +37,6 @@ properties:
description:
List of clock names. Always includes t0_clk and slow clk. Also includes
t1_clk and t2_clk if a clock per channel is available.
- oneOf:
- - items:
- - const: t0_clk
- - const: slow_clk
- - items:
- - const: t0_clk
- - const: t1_clk
- - const: t2_clk
- - const: slow_clk
minItems: 2
maxItems: 4
@@ -75,6 +67,38 @@ patternProperties:
- compatible
- reg
+allOf:
+ - if:
+ properties:
+ compatible:
+ contains:
+ const: atmel,sama5d2-tcb
+ then:
+ properties:
+ clocks:
+ minItems: 3
+ maxItems: 3
+ clock-names:
+ items:
+ - const: t0_clk
+ - const: gclk
+ - const: slow_clk
+ else:
+ properties:
+ clocks:
+ minItems: 2
+ maxItems: 4
+ clock-names:
+ oneOf:
+ - items:
+ - const: t0_clk
+ - const: slow_clk
+ - items:
+ - const: t0_clk
+ - const: t1_clk
+ - const: t2_clk
+ - const: slow_clk
+
required:
- compatible
- reg
--
2.26.2
^ permalink raw reply related
* [PATCH v4 0/9] clocksource/drivers/timer-atmel-tcb: add sama5d2 support
From: Alexandre Belloni @ 2020-05-29 23:27 UTC (permalink / raw)
To: Daniel Lezcano
Cc: Thomas Gleixner, Nicolas Ferre, Sebastian Andrzej Siewior,
kamel.bouhara, linux-arm-kernel, linux-kernel, devicetree,
Alexandre Belloni
Hello,
This series mainly adds sama5d2 support where we need to avoid using
clock index 0 because that clock is never enabled by the driver.
There is also a rework of the 32khz clock handling so it is not used for
clockevents on 32 bit counter because the increased rate improves the
resolution and doesn't have any drawback with that counter width. This
replaces a patch that has been carried in the linux-rt tree for a while.
Changes in v4:
- Rework binding documentation
Changes in v3:
- Moved the child node documentation to the parent documentation
Changes in v2:
- Rebased on v5.7-rc1
- Moved the binding documentation to its proper place
- Added back the atmel,tcb-timer child node documentation
Alexandre Belloni (8):
dt-bindings: atmel-tcb: convert bindings to json-schema
dt-bindings: microchip: atmel,at91rm9200-tcb: add sama5d2 compatible
ARM: dts: at91: sama5d2: add TCB GCLK
clocksource/drivers/timer-atmel-tcb: rework 32khz clock selection
clocksource/drivers/timer-atmel-tcb: fill tcb_config
clocksource/drivers/timer-atmel-tcb: stop using the 32kHz for
clockevents
clocksource/drivers/timer-atmel-tcb: allow selecting first divider
clocksource/drivers/timer-atmel-tcb: add sama5d2 support
Kamel Bouhara (1):
ARM: at91: add atmel tcb capabilities
.../devicetree/bindings/mfd/atmel-tcb.txt | 56 -------
.../soc/microchip/atmel,at91rm9200-tcb.yaml | 155 ++++++++++++++++++
arch/arm/boot/dts/sama5d2.dtsi | 12 +-
drivers/clocksource/timer-atmel-tcb.c | 101 +++++++-----
include/soc/at91/atmel_tcb.h | 5 +
5 files changed, 224 insertions(+), 105 deletions(-)
delete mode 100644 Documentation/devicetree/bindings/mfd/atmel-tcb.txt
create mode 100644 Documentation/devicetree/bindings/soc/microchip/atmel,at91rm9200-tcb.yaml
--
2.26.2
^ permalink raw reply
* Re: [PATCH v3 7/7] watchdog: dw_wdt: Add DebugFS files
From: Guenter Roeck @ 2020-05-29 23:03 UTC (permalink / raw)
To: Serge Semin
Cc: Wim Van Sebroeck, Serge Semin, Alexey Malahov,
Thomas Bogendoerfer, Arnd Bergmann, Rob Herring, linux-mips,
devicetree, linux-watchdog, linux-kernel
In-Reply-To: <20200526154123.24402-8-Sergey.Semin@baikalelectronics.ru>
On Tue, May 26, 2020 at 06:41:23PM +0300, Serge Semin wrote:
> For the sake of the easier device-driver debug procedure, we added a
> DebugFS file with the controller registers state. It's available only if
> kernel is configured with DebugFS support.
>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: linux-mips@vger.kernel.org
> Cc: devicetree@vger.kernel.org
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> ---
>
> Changelog v2:
> - Rearrange SoBs.
> - Discard timeout/pretimeout/ping/enable DebugFS nodes. Registers state
> dump node is only left.
> ---
> drivers/watchdog/dw_wdt.c | 68 +++++++++++++++++++++++++++++++++++++++
> 1 file changed, 68 insertions(+)
>
> diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
> index 3cd7c485cd70..012681baaa6d 100644
> --- a/drivers/watchdog/dw_wdt.c
> +++ b/drivers/watchdog/dw_wdt.c
> @@ -28,6 +28,7 @@
> #include <linux/platform_device.h>
> #include <linux/reset.h>
> #include <linux/watchdog.h>
> +#include <linux/debugfs.h>
>
> #define WDOG_CONTROL_REG_OFFSET 0x00
> #define WDOG_CONTROL_REG_WDT_EN_MASK 0x01
> @@ -39,8 +40,14 @@
> #define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
> #define WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10
> #define WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14
> +#define WDOG_COMP_PARAMS_5_REG_OFFSET 0xe4
> +#define WDOG_COMP_PARAMS_4_REG_OFFSET 0xe8
> +#define WDOG_COMP_PARAMS_3_REG_OFFSET 0xec
> +#define WDOG_COMP_PARAMS_2_REG_OFFSET 0xf0
> #define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4
> #define WDOG_COMP_PARAMS_1_USE_FIX_TOP BIT(6)
> +#define WDOG_COMP_VERSION_REG_OFFSET 0xf8
> +#define WDOG_COMP_TYPE_REG_OFFSET 0xfc
>
> /* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */
> #define DW_WDT_NUM_TOPS 16
> @@ -85,6 +92,10 @@ struct dw_wdt {
> /* Save/restore */
> u32 control;
> u32 timeout;
> +
> +#ifdef CONFIG_DEBUG_FS
> + struct dentry *dbgfs_dir;
> +#endif
> };
>
> #define to_dw_wdt(wdd) container_of(wdd, struct dw_wdt, wdd)
> @@ -484,6 +495,59 @@ static int dw_wdt_init_timeouts(struct dw_wdt *dw_wdt, struct device *dev)
> return 0;
> }
>
> +#ifdef CONFIG_DEBUG_FS
> +
> +#define DW_WDT_DBGFS_REG(_name, _off) \
> +{ \
> + .name = _name, \
> + .offset = _off \
> +}
> +
> +static const struct debugfs_reg32 dw_wdt_dbgfs_regs[] = {
> + DW_WDT_DBGFS_REG("cr", WDOG_CONTROL_REG_OFFSET),
> + DW_WDT_DBGFS_REG("torr", WDOG_TIMEOUT_RANGE_REG_OFFSET),
> + DW_WDT_DBGFS_REG("ccvr", WDOG_CURRENT_COUNT_REG_OFFSET),
> + DW_WDT_DBGFS_REG("crr", WDOG_COUNTER_RESTART_REG_OFFSET),
> + DW_WDT_DBGFS_REG("stat", WDOG_INTERRUPT_STATUS_REG_OFFSET),
> + DW_WDT_DBGFS_REG("param5", WDOG_COMP_PARAMS_5_REG_OFFSET),
> + DW_WDT_DBGFS_REG("param4", WDOG_COMP_PARAMS_4_REG_OFFSET),
> + DW_WDT_DBGFS_REG("param3", WDOG_COMP_PARAMS_3_REG_OFFSET),
> + DW_WDT_DBGFS_REG("param2", WDOG_COMP_PARAMS_2_REG_OFFSET),
> + DW_WDT_DBGFS_REG("param1", WDOG_COMP_PARAMS_1_REG_OFFSET),
> + DW_WDT_DBGFS_REG("version", WDOG_COMP_VERSION_REG_OFFSET),
> + DW_WDT_DBGFS_REG("type", WDOG_COMP_TYPE_REG_OFFSET)
> +};
> +
> +static void dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt)
> +{
> + struct device *dev = dw_wdt->wdd.parent;
> + struct debugfs_regset32 *regset;
> +
> + regset = devm_kzalloc(dev, sizeof(*regset), GFP_KERNEL);
> + if (!regset)
> + return;
> +
> + regset->regs = dw_wdt_dbgfs_regs;
> + regset->nregs = ARRAY_SIZE(dw_wdt_dbgfs_regs);
> + regset->base = dw_wdt->regs;
> +
> + dw_wdt->dbgfs_dir = debugfs_create_dir(dev_name(dev), NULL);
> +
> + debugfs_create_regset32("registers", 0444, dw_wdt->dbgfs_dir, regset);
> +}
> +
> +static void dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt)
> +{
> + debugfs_remove_recursive(dw_wdt->dbgfs_dir);
> +}
> +
> +#else /* !CONFIG_DEBUG_FS */
> +
> +static void dw_wdt_dbgfs_init(struct dw_wdt *dw_wdt) {}
> +static void dw_wdt_dbgfs_clear(struct dw_wdt *dw_wdt) {}
> +
> +#endif /* !CONFIG_DEBUG_FS */
> +
> static int dw_wdt_drv_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> @@ -607,6 +671,8 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
> if (ret)
> goto out_disable_pclk;
>
> + dw_wdt_dbgfs_init(dw_wdt);
> +
> return 0;
>
> out_disable_pclk:
> @@ -621,6 +687,8 @@ static int dw_wdt_drv_remove(struct platform_device *pdev)
> {
> struct dw_wdt *dw_wdt = platform_get_drvdata(pdev);
>
> + dw_wdt_dbgfs_clear(dw_wdt);
> +
> watchdog_unregister_device(&dw_wdt->wdd);
> reset_control_assert(dw_wdt->rst);
> clk_disable_unprepare(dw_wdt->pclk);
^ permalink raw reply
* Re: [PATCH] i2c: add 'single-master' property to generic bindings
From: Wolfram Sang @ 2020-05-29 23:02 UTC (permalink / raw)
To: Rob Herring; +Cc: linux-i2c, devicetree, Laine Jaakko EXT
In-Reply-To: <20200529220228.GA3052199@bogus>
[-- Attachment #1: Type: text/plain, Size: 790 bytes --]
Hi Rob,
thanks for the review!
> Could you just have different timeouts for clearing stalled bus. You
> know quickly if 'single-master' is set, but have to wait longer if not?
Timeouts are a difficult topic with I2C; there is no timeout defined.
However, if you want to start communictaing and don't have a 'bus idle'
condition, then the new property makes a difference. With
"single-master", we know the bus is stalled. With "multi-master" it
could be another master communicating.
> Note that we need to add a bunch of these properties to dt-schema
> i2c-controller.yaml. I hadn't done that because I want to dual license
> in the process, but lots of folks have touched i2c.txt IIRC.
What is your motivation for dual licensing?
All the best,
Wolfram
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 833 bytes --]
^ permalink raw reply
* Re: [PATCH v3 6/7] watchdog: dw_wdt: Add pre-timeouts support
From: Guenter Roeck @ 2020-05-29 23:02 UTC (permalink / raw)
To: Serge Semin
Cc: Wim Van Sebroeck, Serge Semin, Alexey Malahov,
Thomas Bogendoerfer, Arnd Bergmann, Rob Herring, linux-mips,
devicetree, linux-watchdog, linux-kernel
In-Reply-To: <20200526154123.24402-7-Sergey.Semin@baikalelectronics.ru>
On Tue, May 26, 2020 at 06:41:22PM +0300, Serge Semin wrote:
> DW Watchdog can rise an interrupt in case if IRQ request mode is enabled
> and timer reaches the zero value. In this case the IRQ lane is left
> pending until either the next watchdog kick event (watchdog restart) or
> until the WDT_EOI register is read or the device/system reset. This
> interface can be used to implement the pre-timeout functionality
> optionally provided by the Linux kernel watchdog devices.
>
> IRQ mode provides a two stages timeout interface. It means the IRQ is
> raised when the counter reaches zero, while the system reset occurs only
> after subsequent timeout if the timer restart is not performed. Due to
> this peculiarity the pre-timeout value is actually set to the achieved
> hardware timeout, while the real watchdog timeout is considered to be
> twice as much of it. This applies a significant limitation on the
> pre-timeout values, so current implementation supports either zero value,
> which disables the pre-timeout events, or non-zero values, which imply
> the pre-timeout to be at least half of the current watchdog timeout.
>
> Note that we ask the interrupt controller to detect the rising-edge
> pre-timeout interrupts to prevent the high-level-IRQs flood, since
> if the pre-timeout happens, the IRQ lane will be left pending until
> it's cleared by the timer restart.
>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: linux-mips@vger.kernel.org
> Cc: devicetree@vger.kernel.org
Nitpick below, but I don't really know what to do about it, so
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> ---
>
> Changelog v2:
> - Rearrange SoBs.
> - Make the Pre-timeout IRQ being optionally supported.
> ---
> drivers/watchdog/dw_wdt.c | 138 +++++++++++++++++++++++++++++++++++---
> 1 file changed, 130 insertions(+), 8 deletions(-)
>
> diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
> index efbc36872670..3cd7c485cd70 100644
> --- a/drivers/watchdog/dw_wdt.c
> +++ b/drivers/watchdog/dw_wdt.c
> @@ -22,6 +22,7 @@
> #include <linux/kernel.h>
> #include <linux/module.h>
> #include <linux/moduleparam.h>
> +#include <linux/interrupt.h>
> #include <linux/of.h>
> #include <linux/pm.h>
> #include <linux/platform_device.h>
> @@ -36,6 +37,8 @@
> #define WDOG_CURRENT_COUNT_REG_OFFSET 0x08
> #define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c
> #define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
> +#define WDOG_INTERRUPT_STATUS_REG_OFFSET 0x10
> +#define WDOG_INTERRUPT_CLEAR_REG_OFFSET 0x14
> #define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4
> #define WDOG_COMP_PARAMS_1_USE_FIX_TOP BIT(6)
>
> @@ -59,6 +62,11 @@ module_param(nowayout, bool, 0);
> MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
> "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
>
> +enum dw_wdt_rmod {
> + DW_WDT_RMOD_RESET = 1,
> + DW_WDT_RMOD_IRQ = 2
> +};
> +
> struct dw_wdt_timeout {
> u32 top_val;
> unsigned int sec;
> @@ -70,6 +78,7 @@ struct dw_wdt {
> struct clk *clk;
> struct clk *pclk;
> unsigned long rate;
> + enum dw_wdt_rmod rmod;
> struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS];
> struct watchdog_device wdd;
> struct reset_control *rst;
> @@ -86,6 +95,20 @@ static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt)
> WDOG_CONTROL_REG_WDT_EN_MASK;
> }
>
> +static void dw_wdt_update_mode(struct dw_wdt *dw_wdt, enum dw_wdt_rmod rmod)
> +{
> + u32 val;
> +
> + val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
> + if (rmod == DW_WDT_RMOD_IRQ)
> + val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
> + else
> + val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
> + writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
> +
> + dw_wdt->rmod = rmod;
> +}
> +
> static unsigned int dw_wdt_find_best_top(struct dw_wdt *dw_wdt,
> unsigned int timeout, u32 *top_val)
> {
> @@ -145,7 +168,11 @@ static unsigned int dw_wdt_get_timeout(struct dw_wdt *dw_wdt)
> break;
> }
>
> - return dw_wdt->timeouts[idx].sec;
> + /*
> + * In IRQ mode due to the two stages counter, the actual timeout is
> + * twice greater than the TOP setting.
> + */
> + return dw_wdt->timeouts[idx].sec * dw_wdt->rmod;
> }
>
> static int dw_wdt_ping(struct watchdog_device *wdd)
> @@ -164,7 +191,20 @@ static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
> unsigned int timeout;
> u32 top_val;
>
> - timeout = dw_wdt_find_best_top(dw_wdt, top_s, &top_val);
> + /*
> + * Note IRQ mode being enabled means having a non-zero pre-timeout
> + * setup. In this case we try to find a TOP as close to the half of the
> + * requested timeout as possible since DW Watchdog IRQ mode is designed
> + * in two stages way - first timeout rises the pre-timeout interrupt,
> + * second timeout performs the system reset. So basically the effective
> + * watchdog-caused reset happens after two watchdog TOPs elapsed.
> + */
> + timeout = dw_wdt_find_best_top(dw_wdt, DIV_ROUND_UP(top_s, dw_wdt->rmod),
> + &top_val);
> + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
> + wdd->pretimeout = timeout;
> + else
> + wdd->pretimeout = 0;
>
> /*
> * Set the new value in the watchdog. Some versions of dw_wdt
> @@ -175,25 +215,47 @@ static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
> writel(top_val | top_val << WDOG_TIMEOUT_RANGE_TOPINIT_SHIFT,
> dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
>
> + /* Kick new TOP value into the watchdog counter if activated. */
> + if (watchdog_active(wdd))
> + dw_wdt_ping(wdd);
> +
> /*
> * In case users set bigger timeout value than HW can support,
> * kernel(watchdog_dev.c) helps to feed watchdog before
> * wdd->max_hw_heartbeat_ms
> */
> if (top_s * 1000 <= wdd->max_hw_heartbeat_ms)
> - wdd->timeout = timeout;
> + wdd->timeout = timeout * dw_wdt->rmod;
> else
> wdd->timeout = top_s;
>
> return 0;
> }
>
> +static int dw_wdt_set_pretimeout(struct watchdog_device *wdd, unsigned int req)
> +{
> + struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
> +
> + /*
> + * We ignore actual value of the timeout passed from user-space
> + * using it as a flag whether the pretimeout functionality is intended
> + * to be activated.
> + */
> + dw_wdt_update_mode(dw_wdt, req ? DW_WDT_RMOD_IRQ : DW_WDT_RMOD_RESET);
> + dw_wdt_set_timeout(wdd, wdd->timeout);
> +
> + return 0;
> +}
> +
> static void dw_wdt_arm_system_reset(struct dw_wdt *dw_wdt)
> {
> u32 val = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
>
> - /* Disable interrupt mode; always perform system reset. */
> - val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
> + /* Disable/enable interrupt mode depending on the RMOD flag. */
> + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ)
> + val |= WDOG_CONTROL_REG_RESP_MODE_MASK;
> + else
> + val &= ~WDOG_CONTROL_REG_RESP_MODE_MASK;
> /* Enable watchdog. */
> val |= WDOG_CONTROL_REG_WDT_EN_MASK;
> writel(val, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
> @@ -231,6 +293,7 @@ static int dw_wdt_restart(struct watchdog_device *wdd,
> struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
>
> writel(0, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
> + dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET);
> if (dw_wdt_is_enabled(dw_wdt))
> writel(WDOG_COUNTER_RESTART_KICK_VALUE,
> dw_wdt->regs + WDOG_COUNTER_RESTART_REG_OFFSET);
> @@ -246,9 +309,19 @@ static int dw_wdt_restart(struct watchdog_device *wdd,
> static unsigned int dw_wdt_get_timeleft(struct watchdog_device *wdd)
> {
> struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
> + unsigned int sec;
> + u32 val;
> +
> + val = readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET);
> + sec = val / dw_wdt->rate;
>
> - return readl(dw_wdt->regs + WDOG_CURRENT_COUNT_REG_OFFSET) /
> - dw_wdt->rate;
> + if (dw_wdt->rmod == DW_WDT_RMOD_IRQ) {
> + val = readl(dw_wdt->regs + WDOG_INTERRUPT_STATUS_REG_OFFSET);
> + if (!val)
> + sec += wdd->pretimeout;
> + }
> +
> + return sec;
> }
>
> static const struct watchdog_info dw_wdt_ident = {
> @@ -257,16 +330,41 @@ static const struct watchdog_info dw_wdt_ident = {
> .identity = "Synopsys DesignWare Watchdog",
> };
>
> +static const struct watchdog_info dw_wdt_pt_ident = {
> + .options = WDIOF_KEEPALIVEPING | WDIOF_SETTIMEOUT |
> + WDIOF_PRETIMEOUT | WDIOF_MAGICCLOSE,
> + .identity = "Synopsys DesignWare Watchdog",
> +};
> +
> static const struct watchdog_ops dw_wdt_ops = {
> .owner = THIS_MODULE,
> .start = dw_wdt_start,
> .stop = dw_wdt_stop,
> .ping = dw_wdt_ping,
> .set_timeout = dw_wdt_set_timeout,
> + .set_pretimeout = dw_wdt_set_pretimeout,
> .get_timeleft = dw_wdt_get_timeleft,
> .restart = dw_wdt_restart,
> };
>
> +static irqreturn_t dw_wdt_irq(int irq, void *devid)
> +{
> + struct dw_wdt *dw_wdt = devid;
> + u32 val;
> +
> + /*
> + * We don't clear the IRQ status. It's supposed to be done by the
> + * following ping operations.
> + */
> + val = readl(dw_wdt->regs + WDOG_INTERRUPT_STATUS_REG_OFFSET);
> + if (!val)
> + return IRQ_NONE;
> +
> + watchdog_notify_pretimeout(&dw_wdt->wdd);
> +
> + return IRQ_HANDLED;
> +}
> +
> #ifdef CONFIG_PM_SLEEP
> static int dw_wdt_suspend(struct device *dev)
> {
> @@ -447,6 +545,31 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
> goto out_disable_pclk;
> }
>
> + /* Enable normal reset without pre-timeout by default. */
> + dw_wdt_update_mode(dw_wdt, DW_WDT_RMOD_RESET);
> +
> + /*
> + * Pre-timeout IRQ is optional, since some hardware may lack support
> + * of it. Note we must request rising-edge IRQ, since the lane is left
> + * pending either until the next watchdog kick event or up to the
> + * system reset.
> + */
> + ret = platform_get_irq_optional(pdev, 0);
> + if (ret >= 0) {
I keep seeing notes that an interrupt value of 0 is invalid.
> + ret = devm_request_irq(dev, ret, dw_wdt_irq,
> + IRQF_SHARED | IRQF_TRIGGER_RISING,
> + pdev->name, dw_wdt);
> + if (ret)
> + goto out_disable_pclk;
> +
> + dw_wdt->wdd.info = &dw_wdt_pt_ident;
> + } else {
> + if (ret == -EPROBE_DEFER)
> + goto out_disable_pclk;
> +
> + dw_wdt->wdd.info = &dw_wdt_ident;
> + }
> +
> reset_control_deassert(dw_wdt->rst);
>
> ret = dw_wdt_init_timeouts(dw_wdt, dev);
> @@ -454,7 +577,6 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
> goto out_disable_clk;
>
> wdd = &dw_wdt->wdd;
> - wdd->info = &dw_wdt_ident;
> wdd->ops = &dw_wdt_ops;
> wdd->min_timeout = dw_wdt_get_min_timeout(dw_wdt);
> wdd->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt);
^ permalink raw reply
* Re: [PATCH v3 3/7] dt-bindings: watchdog: dw-wdt: Add watchdog TOPs array property
From: Guenter Roeck @ 2020-05-29 22:54 UTC (permalink / raw)
To: Serge Semin
Cc: Wim Van Sebroeck, Rob Herring, Serge Semin, Rob Herring,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, linux-mips,
linux-watchdog, devicetree, linux-kernel
In-Reply-To: <20200526154123.24402-4-Sergey.Semin@baikalelectronics.ru>
On Tue, May 26, 2020 at 06:41:19PM +0300, Serge Semin wrote:
> In case if DW Watchdog IP core is built with WDT_USE_FIX_TOP == false,
> a custom timeout periods are used to preset the timer counter. In
> this case that periods should be specified in a new "snps,watchdog-tops"
> property of the DW watchdog dts node.
>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: linux-mips@vger.kernel.org
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> ---
>
> Changelog v2:
> - Rearrange SoBs.
> - Move $ref to the root level of the "snps,watchdog-tops" property
> so does the constraints.
> - Add default TOP values array.
> - Discard the label definition from the new bindings example.
>
> Changelog v3:
> - Remove items property and move the minItems and maxItems constraints to
> the root level of the snps,watchdog-tops property.
> ---
> .../bindings/watchdog/snps,dw-wdt.yaml | 32 +++++++++++++++++++
> 1 file changed, 32 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> index 5bf6dc6377f3..d9fc7bb851b1 100644
> --- a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> +++ b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> @@ -39,6 +39,23 @@ properties:
> description: Phandle to the DW Watchdog reset lane
> maxItems: 1
>
> + snps,watchdog-tops:
> + $ref: /schemas/types.yaml#/definitions/uint32-array
> + description: |
> + DW APB Watchdog custom timer intervals - Timeout Period ranges (TOPs).
> + Each TOP is a number loaded into the watchdog counter at the moment of
> + the timer restart. The counter decrementing happens each tick of the
> + reference clock. Therefore the TOPs array is equivalent to an array of
> + the timer expiration intervals supported by the DW APB Watchdog. Note
> + DW APB Watchdog IP-core might be synthesized with fixed TOP values,
> + in which case this property is unnecessary with default TOPs utilized.
> + default: [0x0001000 0x0002000 0x0004000 0x0008000
> + 0x0010000 0x0020000 0x0040000 0x0080000
> + 0x0100000 0x0200000 0x0400000 0x0800000
> + 0x1000000 0x2000000 0x4000000 0x8000000]
> + minItems: 16
> + maxItems: 16
> +
> unevaluatedProperties: false
>
> required:
> @@ -55,4 +72,19 @@ examples:
> clocks = <&per_base_clk>;
> resets = <&wdt_rst>;
> };
> +
> + - |
> + watchdog@ffd02000 {
> + compatible = "snps,dw-wdt";
> + reg = <0xffd02000 0x1000>;
> + interrupts = <0 171 4>;
> + clocks = <&per_base_clk>;
> + clock-names = "tclk";
> + snps,watchdog-tops = <0x000000FF 0x000001FF 0x000003FF
> + 0x000007FF 0x0000FFFF 0x0001FFFF
> + 0x0003FFFF 0x0007FFFF 0x000FFFFF
> + 0x001FFFFF 0x003FFFFF 0x007FFFFF
> + 0x00FFFFFF 0x01FFFFFF 0x03FFFFFF
> + 0x07FFFFFF>;
> + };
> ...
^ permalink raw reply
* Re: [PATCH v3 2/7] dt-bindings: watchdog: dw-wdt: Support devices with asynch clocks
From: Guenter Roeck @ 2020-05-29 22:53 UTC (permalink / raw)
To: Serge Semin
Cc: Wim Van Sebroeck, Rob Herring, Serge Semin, Rob Herring,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, linux-mips,
linux-watchdog, devicetree, linux-kernel
In-Reply-To: <20200526154123.24402-3-Sergey.Semin@baikalelectronics.ru>
On Tue, May 26, 2020 at 06:41:18PM +0300, Serge Semin wrote:
> DW Watchdog IP core can be synthesised with asynchronous timer/APB
> clocks support (WDT_ASYNC_CLK_MODE_ENABLE == 1). In this case
> separate clock signals are supposed to be used to feed watchdog timer
> and APB interface of the device. Let's update the DW Watchdog DT node
> schema so it would support the optional APB3 bus clock specified along
> with the mandatory watchdog timer reference clock.
>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: linux-mips@vger.kernel.org
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> ---
>
> Changelog v2:
> - It's a new patch unpinned from the previous one.
> ---
> .../devicetree/bindings/watchdog/snps,dw-wdt.yaml | 8 ++++++++
> 1 file changed, 8 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> index 4f6944756ab4..5bf6dc6377f3 100644
> --- a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> +++ b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> @@ -24,8 +24,16 @@ properties:
> maxItems: 1
>
> clocks:
> + minItems: 1
> items:
> - description: Watchdog timer reference clock
> + - description: APB3 interface clock
> +
> + clock-names:
> + minItems: 1
> + items:
> + - const: tclk
> + - const: pclk
>
> resets:
> description: Phandle to the DW Watchdog reset lane
^ permalink raw reply
* Re: [PATCH v3 1/7] dt-bindings: watchdog: Convert DW WDT binding to DT schema
From: Guenter Roeck @ 2020-05-29 22:53 UTC (permalink / raw)
To: Serge Semin
Cc: Wim Van Sebroeck, Rob Herring, Serge Semin, Rob Herring,
Alexey Malahov, Thomas Bogendoerfer, Arnd Bergmann, linux-mips,
linux-watchdog, devicetree, linux-kernel
In-Reply-To: <20200526154123.24402-2-Sergey.Semin@baikalelectronics.ru>
On Tue, May 26, 2020 at 06:41:17PM +0300, Serge Semin wrote:
> Modern device tree bindings are supposed to be created as YAML-files
> in accordance with dt-schema. This commit replaces the DW Watchdog
> legacy bare text bindings with YAML file. As before the binding states
> that the corresponding dts node is supposed to have a registers
> range, a watchdog timer references clock source, optional reset line and
> pre-timeout interrupt.
>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Reviewed-by: Rob Herring <robh@kernel.org>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: linux-mips@vger.kernel.org
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> ---
>
> Changelog v2:
> - Rearrange SoBs.
> - Discard BE copyright header.
> - Replace "additionalProperties: false" with "unevaluatedProperties: false"
> property.
> - Discard interrupts property from the required properties list.
> - Remove a label definition from the binding example.
> - Move the asynchronous APB3 clock support into a dedicated patch.
> ---
> .../devicetree/bindings/watchdog/dw_wdt.txt | 24 ---------
> .../bindings/watchdog/snps,dw-wdt.yaml | 50 +++++++++++++++++++
> 2 files changed, 50 insertions(+), 24 deletions(-)
> delete mode 100644 Documentation/devicetree/bindings/watchdog/dw_wdt.txt
> create mode 100644 Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
>
> diff --git a/Documentation/devicetree/bindings/watchdog/dw_wdt.txt b/Documentation/devicetree/bindings/watchdog/dw_wdt.txt
> deleted file mode 100644
> index eb0914420c7c..000000000000
> --- a/Documentation/devicetree/bindings/watchdog/dw_wdt.txt
> +++ /dev/null
> @@ -1,24 +0,0 @@
> -Synopsys Designware Watchdog Timer
> -
> -Required Properties:
> -
> -- compatible : Should contain "snps,dw-wdt"
> -- reg : Base address and size of the watchdog timer registers.
> -- clocks : phandle + clock-specifier for the clock that drives the
> - watchdog timer.
> -
> -Optional Properties:
> -
> -- interrupts : The interrupt used for the watchdog timeout warning.
> -- resets : phandle pointing to the system reset controller with
> - line index for the watchdog.
> -
> -Example:
> -
> - watchdog0: wd@ffd02000 {
> - compatible = "snps,dw-wdt";
> - reg = <0xffd02000 0x1000>;
> - interrupts = <0 171 4>;
> - clocks = <&per_base_clk>;
> - resets = <&rst WDT0_RESET>;
> - };
> diff --git a/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> new file mode 100644
> index 000000000000..4f6944756ab4
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/watchdog/snps,dw-wdt.yaml
> @@ -0,0 +1,50 @@
> +# SPDX-License-Identifier: GPL-2.0-only
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/watchdog/snps,dw-wdt.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Synopsys Designware Watchdog Timer
> +
> +allOf:
> + - $ref: "watchdog.yaml#"
> +
> +maintainers:
> + - Jamie Iles <jamie@jamieiles.com>
> +
> +properties:
> + compatible:
> + const: snps,dw-wdt
> +
> + reg:
> + maxItems: 1
> +
> + interrupts:
> + description: DW Watchdog pre-timeout interrupt
> + maxItems: 1
> +
> + clocks:
> + items:
> + - description: Watchdog timer reference clock
> +
> + resets:
> + description: Phandle to the DW Watchdog reset lane
> + maxItems: 1
> +
> +unevaluatedProperties: false
> +
> +required:
> + - compatible
> + - reg
> + - clocks
> +
> +examples:
> + - |
> + watchdog@ffd02000 {
> + compatible = "snps,dw-wdt";
> + reg = <0xffd02000 0x1000>;
> + interrupts = <0 171 4>;
> + clocks = <&per_base_clk>;
> + resets = <&wdt_rst>;
> + };
> +...
^ permalink raw reply
* Re: [PATCH v3 5/7] watchdog: dw_wdt: Support devices with asynch clocks
From: Guenter Roeck @ 2020-05-29 22:52 UTC (permalink / raw)
To: Serge Semin
Cc: Wim Van Sebroeck, Serge Semin, Alexey Malahov,
Thomas Bogendoerfer, Arnd Bergmann, Rob Herring, linux-mips,
devicetree, linux-watchdog, linux-kernel
In-Reply-To: <20200526154123.24402-6-Sergey.Semin@baikalelectronics.ru>
On Tue, May 26, 2020 at 06:41:21PM +0300, Serge Semin wrote:
> DW Watchdog IP core can be synthesised with asynchronous timer/APB
> clocks support (WDT_ASYNC_CLK_MODE_ENABLE == 1). In this case
> separate clock signals are supposed to be used to feed watchdog timer
> and APB interface of the device. Currently the driver supports
> the synchronous mode only. Since there is no way to determine which
> mode was actually activated for device from its registers, we have to
> rely on the platform device configuration data. If optional "pclk"
> clock source is supplied, we consider the device working in asynchronous
> mode, otherwise the driver falls back to the synchronous configuration.
>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: linux-mips@vger.kernel.org
> Cc: devicetree@vger.kernel.org
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> ---
>
> Changelog v2:
> - Rearrange SoBs.
> ---
> drivers/watchdog/dw_wdt.c | 48 +++++++++++++++++++++++++++++++++++----
> 1 file changed, 43 insertions(+), 5 deletions(-)
>
> diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
> index 693c0d1fd796..efbc36872670 100644
> --- a/drivers/watchdog/dw_wdt.c
> +++ b/drivers/watchdog/dw_wdt.c
> @@ -68,6 +68,7 @@ struct dw_wdt_timeout {
> struct dw_wdt {
> void __iomem *regs;
> struct clk *clk;
> + struct clk *pclk;
> unsigned long rate;
> struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS];
> struct watchdog_device wdd;
> @@ -274,6 +275,7 @@ static int dw_wdt_suspend(struct device *dev)
> dw_wdt->control = readl(dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
> dw_wdt->timeout = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
>
> + clk_disable_unprepare(dw_wdt->pclk);
> clk_disable_unprepare(dw_wdt->clk);
>
> return 0;
> @@ -287,6 +289,12 @@ static int dw_wdt_resume(struct device *dev)
> if (err)
> return err;
>
> + err = clk_prepare_enable(dw_wdt->pclk);
> + if (err) {
> + clk_disable_unprepare(dw_wdt->clk);
> + return err;
> + }
> +
> writel(dw_wdt->timeout, dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET);
> writel(dw_wdt->control, dw_wdt->regs + WDOG_CONTROL_REG_OFFSET);
>
> @@ -393,9 +401,18 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
> if (IS_ERR(dw_wdt->regs))
> return PTR_ERR(dw_wdt->regs);
>
> - dw_wdt->clk = devm_clk_get(dev, NULL);
> - if (IS_ERR(dw_wdt->clk))
> - return PTR_ERR(dw_wdt->clk);
> + /*
> + * Try to request the watchdog dedicated timer clock source. It must
> + * be supplied if asynchronous mode is enabled. Otherwise fallback
> + * to the common timer/bus clocks configuration, in which the very
> + * first found clock supply both timer and APB signals.
> + */
> + dw_wdt->clk = devm_clk_get(dev, "tclk");
> + if (IS_ERR(dw_wdt->clk)) {
> + dw_wdt->clk = devm_clk_get(dev, NULL);
> + if (IS_ERR(dw_wdt->clk))
> + return PTR_ERR(dw_wdt->clk);
> + }
>
> ret = clk_prepare_enable(dw_wdt->clk);
> if (ret)
> @@ -407,10 +424,27 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
> goto out_disable_clk;
> }
>
> + /*
> + * Request APB clock if device is configured with async clocks mode.
> + * In this case both tclk and pclk clocks are supposed to be specified.
> + * Alas we can't know for sure whether async mode was really activated,
> + * so the pclk phandle reference is left optional. If it couldn't be
> + * found we consider the device configured in synchronous clocks mode.
> + */
> + dw_wdt->pclk = devm_clk_get_optional(dev, "pclk");
> + if (IS_ERR(dw_wdt->pclk)) {
> + ret = PTR_ERR(dw_wdt->pclk);
> + goto out_disable_clk;
> + }
> +
> + ret = clk_prepare_enable(dw_wdt->pclk);
> + if (ret)
> + goto out_disable_clk;
> +
> dw_wdt->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
> if (IS_ERR(dw_wdt->rst)) {
> ret = PTR_ERR(dw_wdt->rst);
> - goto out_disable_clk;
> + goto out_disable_pclk;
> }
>
> reset_control_deassert(dw_wdt->rst);
> @@ -449,10 +483,13 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
>
> ret = watchdog_register_device(wdd);
> if (ret)
> - goto out_disable_clk;
> + goto out_disable_pclk;
>
> return 0;
>
> +out_disable_pclk:
> + clk_disable_unprepare(dw_wdt->pclk);
> +
> out_disable_clk:
> clk_disable_unprepare(dw_wdt->clk);
> return ret;
> @@ -464,6 +501,7 @@ static int dw_wdt_drv_remove(struct platform_device *pdev)
>
> watchdog_unregister_device(&dw_wdt->wdd);
> reset_control_assert(dw_wdt->rst);
> + clk_disable_unprepare(dw_wdt->pclk);
> clk_disable_unprepare(dw_wdt->clk);
>
> return 0;
^ permalink raw reply
* Re: [PATCH v3 4/7] watchdog: dw_wdt: Support devices with non-fixed TOP values
From: Guenter Roeck @ 2020-05-29 22:50 UTC (permalink / raw)
To: Serge Semin
Cc: Wim Van Sebroeck, Serge Semin, Alexey Malahov,
Thomas Bogendoerfer, Arnd Bergmann, Rob Herring, linux-mips,
devicetree, linux-watchdog, linux-kernel
In-Reply-To: <20200526154123.24402-5-Sergey.Semin@baikalelectronics.ru>
On Tue, May 26, 2020 at 06:41:20PM +0300, Serge Semin wrote:
> In case if the DW Watchdog IP core is synthesised with
> WDT_USE_FIX_TOP == false, the TOP interval indexes make the device
> to load a custom periods to the counter. These periods are hardwired
> at the IP synthesis stage and can be within [2^8, 2^(WDT_CNT_WIDTH - 1)].
> Alas their values can't be detected at runtime and must be somehow
> supplied to the driver so one could properly determine the watchdog
> timeout intervals. For this purpose we suggest to have a vendor-
> specific dts property "snps,watchdog-tops" utilized, which would
> provide an array of sixteen counter values. At device probe stage they
> will be used to initialize the watchdog device timeouts determined
> from the array values and current clocks source rate.
>
> In order to have custom TOP values supported the driver must be
> altered in the following way. First of all the fixed-top values
> ready-to-use array must be determined for compatibility with currently
> supported devices, which were synthesised with WDT_USE_FIX_TOP == true.
> It will be used if either fixed TOP feature is detected being enabled or
> no custom TOPs are fetched from the device dt node. Secondly at the probe
> stage we must initialize an array of the watchdog timeouts corresponding
> to the detected TOPs list and the reference clock rate. For generality the
> procedure of initialization is designed in a way to support the TOPs array
> with no limitations on the items order or value. Finally the watchdog
> period search methods should be altered to support the new timeouts data
> structure.
>
> Signed-off-by: Serge Semin <Sergey.Semin@baikalelectronics.ru>
> Cc: Alexey Malahov <Alexey.Malahov@baikalelectronics.ru>
> Cc: Thomas Bogendoerfer <tsbogend@alpha.franken.de>
> Cc: Arnd Bergmann <arnd@arndb.de>
> Cc: Rob Herring <robh+dt@kernel.org>
> Cc: linux-mips@vger.kernel.org
> Cc: devicetree@vger.kernel.org
Reviewed-by: Guenter Roeck <linux@roeck-us.net>
> ---
>
> Changelog v2:
> - Rearrange SoBs.
> - Add "ms" suffix to the methods returning msec and convert the methods
> with no "ms" suffix to return a timeout in sec.
> - Make sure minimum timeout is at least 1 sec.
> - Refactor the timeouts calculation procedure to retain the timeouts in
> the ascending order.
> - Make sure there is no integer overflow in milliseconds calculation. It
> is saved in a dedicated uint field of the timeout structure.
> ---
> drivers/watchdog/dw_wdt.c | 191 +++++++++++++++++++++++++++++++++-----
> 1 file changed, 167 insertions(+), 24 deletions(-)
>
> diff --git a/drivers/watchdog/dw_wdt.c b/drivers/watchdog/dw_wdt.c
> index fba21de2bbad..693c0d1fd796 100644
> --- a/drivers/watchdog/dw_wdt.c
> +++ b/drivers/watchdog/dw_wdt.c
> @@ -13,6 +13,8 @@
> */
>
> #include <linux/bitops.h>
> +#include <linux/limits.h>
> +#include <linux/kernel.h>
> #include <linux/clk.h>
> #include <linux/delay.h>
> #include <linux/err.h>
> @@ -34,21 +36,40 @@
> #define WDOG_CURRENT_COUNT_REG_OFFSET 0x08
> #define WDOG_COUNTER_RESTART_REG_OFFSET 0x0c
> #define WDOG_COUNTER_RESTART_KICK_VALUE 0x76
> +#define WDOG_COMP_PARAMS_1_REG_OFFSET 0xf4
> +#define WDOG_COMP_PARAMS_1_USE_FIX_TOP BIT(6)
>
> -/* The maximum TOP (timeout period) value that can be set in the watchdog. */
> -#define DW_WDT_MAX_TOP 15
> +/* There are sixteen TOPs (timeout periods) that can be set in the watchdog. */
> +#define DW_WDT_NUM_TOPS 16
> +#define DW_WDT_FIX_TOP(_idx) (1U << (16 + _idx))
>
> #define DW_WDT_DEFAULT_SECONDS 30
>
> +static const u32 dw_wdt_fix_tops[DW_WDT_NUM_TOPS] = {
> + DW_WDT_FIX_TOP(0), DW_WDT_FIX_TOP(1), DW_WDT_FIX_TOP(2),
> + DW_WDT_FIX_TOP(3), DW_WDT_FIX_TOP(4), DW_WDT_FIX_TOP(5),
> + DW_WDT_FIX_TOP(6), DW_WDT_FIX_TOP(7), DW_WDT_FIX_TOP(8),
> + DW_WDT_FIX_TOP(9), DW_WDT_FIX_TOP(10), DW_WDT_FIX_TOP(11),
> + DW_WDT_FIX_TOP(12), DW_WDT_FIX_TOP(13), DW_WDT_FIX_TOP(14),
> + DW_WDT_FIX_TOP(15)
> +};
> +
> static bool nowayout = WATCHDOG_NOWAYOUT;
> module_param(nowayout, bool, 0);
> MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started "
> "(default=" __MODULE_STRING(WATCHDOG_NOWAYOUT) ")");
>
> +struct dw_wdt_timeout {
> + u32 top_val;
> + unsigned int sec;
> + unsigned int msec;
> +};
> +
> struct dw_wdt {
> void __iomem *regs;
> struct clk *clk;
> unsigned long rate;
> + struct dw_wdt_timeout timeouts[DW_WDT_NUM_TOPS];
> struct watchdog_device wdd;
> struct reset_control *rst;
> /* Save/restore */
> @@ -64,20 +85,66 @@ static inline int dw_wdt_is_enabled(struct dw_wdt *dw_wdt)
> WDOG_CONTROL_REG_WDT_EN_MASK;
> }
>
> -static inline int dw_wdt_top_in_seconds(struct dw_wdt *dw_wdt, unsigned top)
> +static unsigned int dw_wdt_find_best_top(struct dw_wdt *dw_wdt,
> + unsigned int timeout, u32 *top_val)
> {
> + int idx;
> +
> /*
> - * There are 16 possible timeout values in 0..15 where the number of
> - * cycles is 2 ^ (16 + i) and the watchdog counts down.
> + * Find a TOP with timeout greater or equal to the requested number.
> + * Note we'll select a TOP with maximum timeout if the requested
> + * timeout couldn't be reached.
> */
> - return (1U << (16 + top)) / dw_wdt->rate;
> + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) {
> + if (dw_wdt->timeouts[idx].sec >= timeout)
> + break;
> + }
> +
> + if (idx == DW_WDT_NUM_TOPS)
> + --idx;
> +
> + *top_val = dw_wdt->timeouts[idx].top_val;
> +
> + return dw_wdt->timeouts[idx].sec;
> }
>
> -static int dw_wdt_get_top(struct dw_wdt *dw_wdt)
> +static unsigned int dw_wdt_get_min_timeout(struct dw_wdt *dw_wdt)
> {
> - int top = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
> + int idx;
> +
> + /*
> + * We'll find a timeout greater or equal to one second anyway because
> + * the driver probe would have failed if there was none.
> + */
> + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) {
> + if (dw_wdt->timeouts[idx].sec)
> + break;
> + }
>
> - return dw_wdt_top_in_seconds(dw_wdt, top);
> + return dw_wdt->timeouts[idx].sec;
> +}
> +
> +static unsigned int dw_wdt_get_max_timeout_ms(struct dw_wdt *dw_wdt)
> +{
> + struct dw_wdt_timeout *timeout = &dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1];
> + u64 msec;
> +
> + msec = (u64)timeout->sec * MSEC_PER_SEC + timeout->msec;
> +
> + return msec < UINT_MAX ? msec : UINT_MAX;
> +}
> +
> +static unsigned int dw_wdt_get_timeout(struct dw_wdt *dw_wdt)
> +{
> + int top_val = readl(dw_wdt->regs + WDOG_TIMEOUT_RANGE_REG_OFFSET) & 0xF;
> + int idx;
> +
> + for (idx = 0; idx < DW_WDT_NUM_TOPS; ++idx) {
> + if (dw_wdt->timeouts[idx].top_val == top_val)
> + break;
> + }
> +
> + return dw_wdt->timeouts[idx].sec;
> }
>
> static int dw_wdt_ping(struct watchdog_device *wdd)
> @@ -93,17 +160,10 @@ static int dw_wdt_ping(struct watchdog_device *wdd)
> static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
> {
> struct dw_wdt *dw_wdt = to_dw_wdt(wdd);
> - int i, top_val = DW_WDT_MAX_TOP;
> + unsigned int timeout;
> + u32 top_val;
>
> - /*
> - * Iterate over the timeout values until we find the closest match. We
> - * always look for >=.
> - */
> - for (i = 0; i <= DW_WDT_MAX_TOP; ++i)
> - if (dw_wdt_top_in_seconds(dw_wdt, i) >= top_s) {
> - top_val = i;
> - break;
> - }
> + timeout = dw_wdt_find_best_top(dw_wdt, top_s, &top_val);
>
> /*
> * Set the new value in the watchdog. Some versions of dw_wdt
> @@ -120,7 +180,7 @@ static int dw_wdt_set_timeout(struct watchdog_device *wdd, unsigned int top_s)
> * wdd->max_hw_heartbeat_ms
> */
> if (top_s * 1000 <= wdd->max_hw_heartbeat_ms)
> - wdd->timeout = dw_wdt_top_in_seconds(dw_wdt, top_val);
> + wdd->timeout = timeout;
> else
> wdd->timeout = top_s;
>
> @@ -238,6 +298,86 @@ static int dw_wdt_resume(struct device *dev)
>
> static SIMPLE_DEV_PM_OPS(dw_wdt_pm_ops, dw_wdt_suspend, dw_wdt_resume);
>
> +/*
> + * In case if DW WDT IP core is synthesized with fixed TOP feature disabled the
> + * TOPs array can be arbitrary ordered with nearly any sixteen uint numbers
> + * depending on the system engineer imagination. The next method handles the
> + * passed TOPs array to pre-calculate the effective timeouts and to sort the
> + * TOP items out in the ascending order with respect to the timeouts.
> + */
> +
> +static void dw_wdt_handle_tops(struct dw_wdt *dw_wdt, const u32 *tops)
> +{
> + struct dw_wdt_timeout tout, *dst;
> + int val, tidx;
> + u64 msec;
> +
> + /*
> + * We walk over the passed TOPs array and calculate corresponding
> + * timeouts in seconds and milliseconds. The milliseconds granularity
> + * is needed to distinguish the TOPs with very close timeouts and to
> + * set the watchdog max heartbeat setting further.
> + */
> + for (val = 0; val < DW_WDT_NUM_TOPS; ++val) {
> + tout.top_val = val;
> + tout.sec = tops[val] / dw_wdt->rate;
> + msec = (u64)tops[val] * MSEC_PER_SEC;
> + do_div(msec, dw_wdt->rate);
> + tout.msec = msec - ((u64)tout.sec * MSEC_PER_SEC);
> +
> + /*
> + * Find a suitable place for the current TOP in the timeouts
> + * array so that the list is remained in the ascending order.
> + */
> + for (tidx = 0; tidx < val; ++tidx) {
> + dst = &dw_wdt->timeouts[tidx];
> + if (tout.sec > dst->sec || (tout.sec == dst->sec &&
> + tout.msec >= dst->msec))
> + continue;
> + else
> + swap(*dst, tout);
> + }
> +
> + dw_wdt->timeouts[val] = tout;
> + }
> +}
> +
> +static int dw_wdt_init_timeouts(struct dw_wdt *dw_wdt, struct device *dev)
> +{
> + u32 data, of_tops[DW_WDT_NUM_TOPS];
> + const u32 *tops;
> + int ret;
> +
> + /*
> + * Retrieve custom or fixed counter values depending on the
> + * WDT_USE_FIX_TOP flag found in the component specific parameters
> + * #1 register.
> + */
> + data = readl(dw_wdt->regs + WDOG_COMP_PARAMS_1_REG_OFFSET);
> + if (data & WDOG_COMP_PARAMS_1_USE_FIX_TOP) {
> + tops = dw_wdt_fix_tops;
> + } else {
> + ret = of_property_read_variable_u32_array(dev_of_node(dev),
> + "snps,watchdog-tops", of_tops, DW_WDT_NUM_TOPS,
> + DW_WDT_NUM_TOPS);
> + if (ret < 0) {
> + dev_warn(dev, "No valid TOPs array specified\n");
> + tops = dw_wdt_fix_tops;
> + } else {
> + tops = of_tops;
> + }
> + }
> +
> + /* Convert the specified TOPs into an array of watchdog timeouts. */
> + dw_wdt_handle_tops(dw_wdt, tops);
> + if (!dw_wdt->timeouts[DW_WDT_NUM_TOPS - 1].sec) {
> + dev_err(dev, "No any valid TOP detected\n");
> + return -EINVAL;
> + }
> +
> + return 0;
> +}
> +
> static int dw_wdt_drv_probe(struct platform_device *pdev)
> {
> struct device *dev = &pdev->dev;
> @@ -275,12 +415,15 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
>
> reset_control_deassert(dw_wdt->rst);
>
> + ret = dw_wdt_init_timeouts(dw_wdt, dev);
> + if (ret)
> + goto out_disable_clk;
> +
> wdd = &dw_wdt->wdd;
> wdd->info = &dw_wdt_ident;
> wdd->ops = &dw_wdt_ops;
> - wdd->min_timeout = 1;
> - wdd->max_hw_heartbeat_ms =
> - dw_wdt_top_in_seconds(dw_wdt, DW_WDT_MAX_TOP) * 1000;
> + wdd->min_timeout = dw_wdt_get_min_timeout(dw_wdt);
> + wdd->max_hw_heartbeat_ms = dw_wdt_get_max_timeout_ms(dw_wdt);
> wdd->parent = dev;
>
> watchdog_set_drvdata(wdd, dw_wdt);
> @@ -293,7 +436,7 @@ static int dw_wdt_drv_probe(struct platform_device *pdev)
> * devicetree.
> */
> if (dw_wdt_is_enabled(dw_wdt)) {
> - wdd->timeout = dw_wdt_get_top(dw_wdt);
> + wdd->timeout = dw_wdt_get_timeout(dw_wdt);
> set_bit(WDOG_HW_RUNNING, &wdd->status);
> } else {
> wdd->timeout = DW_WDT_DEFAULT_SECONDS;
^ permalink raw reply
* Re: [PATCH V2] dt-bindings: regulator: Convert anatop regulator to json-schema
From: Mark Brown @ 2020-05-29 22:43 UTC (permalink / raw)
To: lgirdwood, robh+dt, Anson Huang, linux-kernel, devicetree,
paul.liu
Cc: Linux-imx
In-Reply-To: <1590717551-20772-1-git-send-email-Anson.Huang@nxp.com>
On Fri, 29 May 2020 09:59:11 +0800, Anson Huang wrote:
> Convert the anatop regulator binding to DT schema format using json-schema.
Applied to
https://git.kernel.org/pub/scm/linux/kernel/git/broonie/regulator.git for-next
Thanks!
[1/1] dt-bindings: regulator: Convert anatop regulator to json-schema
commit: 81227f49bd272cbcd9bb4650b250519c8aa22065
All being well this means that it will be integrated into the linux-next
tree (usually sometime in the next 24 hours) and sent to Linus during
the next merge window (or sooner if it is a bug fix), however if
problems are discovered then the patch may be dropped or reverted.
You may get further e-mails resulting from automated or manual testing
and review of the tree, please engage with people reporting problems and
send followup patches addressing any issues that are reported if needed.
If any updates are required or you are submitting further changes they
should be sent as incremental updates against current git, existing
patches will not be replaced.
Please add any relevant lists and maintainers to the CCs when replying
to this mail.
Thanks,
Mark
^ permalink raw reply
page: next (older) | prev (newer) | latest
- recent:[subjects (threaded)|topics (new)|topics (active)]
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox