From patchwork Wed Sep 30 12:54:59 2015 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Srinivas Kandagatla X-Patchwork-Id: 54327 Return-Path: X-Original-To: linaro@patches.linaro.org Delivered-To: linaro@patches.linaro.org Received: from mail-wi0-f200.google.com (mail-wi0-f200.google.com [209.85.212.200]) by patches.linaro.org (Postfix) with ESMTPS id 1C54E23002 for ; Wed, 30 Sep 2015 12:55:32 +0000 (UTC) Received: by wicgb1 with SMTP id gb1sf22195617wic.3 for ; Wed, 30 Sep 2015 05:55:31 -0700 (PDT) X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20130820; h=x-gm-message-state:mime-version:delivered-to:from:to:cc:subject :date:message-id:in-reply-to:references:sender:precedence:list-id :x-original-sender:x-original-authentication-results:mailing-list :list-post:list-help:list-archive:list-unsubscribe; bh=kV1L0vslspV0bwcljE8Pk2Fgxj4OxyTqo6aNo7AIvM0=; b=gnQjN/BO9mBD4UwDltdd/d+RrCgiZJJ1V2xHJEhIprFQVitQPmSQFMwgRdjBoinlwv 1g8v9JNQ7Og2ASq0ATAXwRUMX8QhCJKvnM3G9AmIpNSOePnK5iBSgi8w3IQMtrgWu9rZ vKNfwIJTlvCNA/+BALFIT2uTaJLzfAOAWmAsyjMwU23yJrFf861Vs7gn1fh08DflucZd 7XdG9FVeUUW2k8y5Q7OteL4NuIeNKgGQJqdZyIJ2OjOrR0e5nLG2c51C98+RqIwUOaE6 ShObxs7HvDRTHCiDQ7WKRfp0ksmbdum2ss5kLVhHVXMgwIYgzH1F/K8AAINfudF3D6GO 5sNg== X-Gm-Message-State: ALoCoQnovYaRgkwnqrarpuT3GhJNdxG3YaHm13bgRMbFp+QqSZA5K+bVojoEm4Ksd9jXUjQVRb8D X-Received: by 10.194.240.230 with SMTP id wd6mr527580wjc.0.1443617731427; Wed, 30 Sep 2015 05:55:31 -0700 (PDT) MIME-Version: 1.0 X-BeenThere: patchwork-forward@linaro.org Received: by 10.25.150.209 with SMTP id y200ls33079lfd.17.gmail; Wed, 30 Sep 2015 05:55:31 -0700 (PDT) X-Received: by 10.152.37.135 with SMTP id y7mr1112175laj.110.1443617731232; Wed, 30 Sep 2015 05:55:31 -0700 (PDT) Received: from mail-la0-f52.google.com (mail-la0-f52.google.com. [209.85.215.52]) by mx.google.com with ESMTPS id dl10si214370lad.154.2015.09.30.05.55.31 for (version=TLSv1.2 cipher=ECDHE-RSA-AES128-GCM-SHA256 bits=128/128); Wed, 30 Sep 2015 05:55:31 -0700 (PDT) Received-SPF: pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.215.52 as permitted sender) client-ip=209.85.215.52; Received: by laer8 with SMTP id r8so44658124lae.2 for ; Wed, 30 Sep 2015 05:55:31 -0700 (PDT) X-Received: by 10.152.7.38 with SMTP id g6mr1066295laa.106.1443617731085; Wed, 30 Sep 2015 05:55:31 -0700 (PDT) X-Forwarded-To: patchwork-forward@linaro.org X-Forwarded-For: patch@linaro.org patchwork-forward@linaro.org Delivered-To: patch@linaro.org Received: by 10.112.59.35 with SMTP id w3csp2739112lbq; Wed, 30 Sep 2015 05:55:29 -0700 (PDT) X-Received: by 10.68.65.104 with SMTP id w8mr4876393pbs.48.1443617729065; Wed, 30 Sep 2015 05:55:29 -0700 (PDT) Received: from vger.kernel.org (vger.kernel.org. [209.132.180.67]) by mx.google.com with ESMTP id zj1si710627pbb.30.2015.09.30.05.55.25; Wed, 30 Sep 2015 05:55:29 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of linux-kernel-owner@vger.kernel.org designates 209.132.180.67 as permitted sender) client-ip=209.132.180.67; Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S933603AbbI3MzU (ORCPT + 30 others); Wed, 30 Sep 2015 08:55:20 -0400 Received: from mail-wi0-f173.google.com ([209.85.212.173]:36680 "EHLO mail-wi0-f173.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S932144AbbI3MzK (ORCPT ); Wed, 30 Sep 2015 08:55:10 -0400 Received: by wicgb1 with SMTP id gb1so193909091wic.1 for ; Wed, 30 Sep 2015 05:55:09 -0700 (PDT) X-Received: by 10.180.99.66 with SMTP id eo2mr33077597wib.88.1443617709040; Wed, 30 Sep 2015 05:55:09 -0700 (PDT) Received: from localhost.localdomain (host-92-13-246-184.as43234.net. [92.13.246.184]) by smtp.gmail.com with ESMTPSA id it4sm560968wjb.0.2015.09.30.05.55.07 (version=TLSv1.2 cipher=ECDHE-RSA-AES128-SHA bits=128/128); Wed, 30 Sep 2015 05:55:08 -0700 (PDT) From: Srinivas Kandagatla To: gregkh@linuxfoundation.org Cc: maxime.ripard@free-electrons.com, linux-kernel@vger.kernel.org, srinivas.kandagatla@linaro.org, linux-arm-kernel@lists.infradead.org, Sanchayan Maity Subject: [PATCH 2/8] nvmem: Add Vybrid OCOTP support Date: Wed, 30 Sep 2015 13:54:59 +0100 Message-Id: <1443617699-26355-1-git-send-email-srinivas.kandagatla@linaro.org> X-Mailer: git-send-email 1.9.1 In-Reply-To: <1443617615-26261-1-git-send-email-srinivas.kandagatla@linaro.org> References: <1443617615-26261-1-git-send-email-srinivas.kandagatla@linaro.org> Sender: linux-kernel-owner@vger.kernel.org Precedence: list List-ID: X-Mailing-List: linux-kernel@vger.kernel.org X-Removed-Original-Auth: Dkim didn't pass. X-Original-Sender: srinivas.kandagatla@linaro.org X-Original-Authentication-Results: mx.google.com; spf=pass (google.com: domain of patch+caf_=patchwork-forward=linaro.org@linaro.org designates 209.85.215.52 as permitted sender) smtp.mailfrom=patch+caf_=patchwork-forward=linaro.org@linaro.org Mailing-list: list patchwork-forward@linaro.org; contact patchwork-forward+owners@linaro.org X-Google-Group-Id: 836684582541 List-Post: , List-Help: , List-Archive: List-Unsubscribe: , From: Sanchayan Maity The patch adds support for the On Chip One Time Programmable Peripheral (OCOTP) on the Vybrid platform. Signed-off-by: Sanchayan Maity Acked-by: Srinivas Kandagatla Signed-off-by: Srinivas Kandagatla --- drivers/nvmem/Kconfig | 10 ++ drivers/nvmem/Makefile | 2 + drivers/nvmem/vf610-ocotp.c | 302 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 314 insertions(+) create mode 100644 drivers/nvmem/vf610-ocotp.c diff --git a/drivers/nvmem/Kconfig b/drivers/nvmem/Kconfig index 8db2978..1d4f2b4 100644 --- a/drivers/nvmem/Kconfig +++ b/drivers/nvmem/Kconfig @@ -36,4 +36,14 @@ config NVMEM_SUNXI_SID This driver can also be built as a module. If so, the module will be called nvmem_sunxi_sid. +config NVMEM_VF610_OCOTP + tristate "VF610 SoC OCOTP support" + depends on SOC_VF610 || COMPILE_TEST + help + This is a driver for the 'OCOTP' peripheral available on Vybrid + devices like VF5xx and VF6xx. + + This driver can also be build as a module. If so, the module will + be called nvmem-vf610-ocotp. + endif diff --git a/drivers/nvmem/Makefile b/drivers/nvmem/Makefile index 4328b93..3bdcfd4 100644 --- a/drivers/nvmem/Makefile +++ b/drivers/nvmem/Makefile @@ -10,3 +10,5 @@ obj-$(CONFIG_QCOM_QFPROM) += nvmem_qfprom.o nvmem_qfprom-y := qfprom.o obj-$(CONFIG_NVMEM_SUNXI_SID) += nvmem_sunxi_sid.o nvmem_sunxi_sid-y := sunxi_sid.o +obj-$(CONFIG_NVMEM_VF610_OCOTP) += nvmem-vf610-ocotp.o +nvmem-vf610-ocotp-y := vf610-ocotp.o diff --git a/drivers/nvmem/vf610-ocotp.c b/drivers/nvmem/vf610-ocotp.c new file mode 100644 index 0000000..8641319 --- /dev/null +++ b/drivers/nvmem/vf610-ocotp.c @@ -0,0 +1,302 @@ +/* + * Copyright (C) 2015 Toradex AG. + * + * Author: Sanchayan Maity + * + * Based on the barebox ocotp driver, + * Copyright (c) 2010 Baruch Siach + * Orex Computed Radiography + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 and + * only version 2 as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* OCOTP Register Offsets */ +#define OCOTP_CTRL_REG 0x00 +#define OCOTP_CTRL_SET 0x04 +#define OCOTP_CTRL_CLR 0x08 +#define OCOTP_TIMING 0x10 +#define OCOTP_DATA 0x20 +#define OCOTP_READ_CTRL_REG 0x30 +#define OCOTP_READ_FUSE_DATA 0x40 + +/* OCOTP Register bits and masks */ +#define OCOTP_CTRL_WR_UNLOCK 16 +#define OCOTP_CTRL_WR_UNLOCK_KEY 0x3E77 +#define OCOTP_CTRL_WR_UNLOCK_MASK GENMASK(31, 16) +#define OCOTP_CTRL_ADDR 0 +#define OCOTP_CTRL_ADDR_MASK GENMASK(6, 0) +#define OCOTP_CTRL_RELOAD_SHADOWS BIT(10) +#define OCOTP_CTRL_ERR BIT(9) +#define OCOTP_CTRL_BUSY BIT(8) + +#define OCOTP_TIMING_STROBE_READ 16 +#define OCOTP_TIMING_STROBE_READ_MASK GENMASK(21, 16) +#define OCOTP_TIMING_RELAX 12 +#define OCOTP_TIMING_RELAX_MASK GENMASK(15, 12) +#define OCOTP_TIMING_STROBE_PROG 0 +#define OCOTP_TIMING_STROBE_PROG_MASK GENMASK(11, 0) + +#define OCOTP_READ_CTRL_READ_FUSE 0x1 + +#define VF610_OCOTP_TIMEOUT 100000 + +#define BF(value, field) (((value) << field) & field##_MASK) + +#define DEF_RELAX 20 + +static const int base_to_fuse_addr_mappings[][2] = { + {0x400, 0x00}, + {0x410, 0x01}, + {0x420, 0x02}, + {0x450, 0x05}, + {0x4F0, 0x0F}, + {0x600, 0x20}, + {0x610, 0x21}, + {0x620, 0x22}, + {0x630, 0x23}, + {0x640, 0x24}, + {0x650, 0x25}, + {0x660, 0x26}, + {0x670, 0x27}, + {0x6F0, 0x2F}, + {0x880, 0x38}, + {0x890, 0x39}, + {0x8A0, 0x3A}, + {0x8B0, 0x3B}, + {0x8C0, 0x3C}, + {0x8D0, 0x3D}, + {0x8E0, 0x3E}, + {0x8F0, 0x3F}, + {0xC80, 0x78}, + {0xC90, 0x79}, + {0xCA0, 0x7A}, + {0xCB0, 0x7B}, + {0xCC0, 0x7C}, + {0xCD0, 0x7D}, + {0xCE0, 0x7E}, + {0xCF0, 0x7F}, +}; + +struct vf610_ocotp { + void __iomem *base; + struct clk *clk; + struct device *dev; + struct nvmem_device *nvmem; + int timing; +}; + +static int vf610_ocotp_wait_busy(void __iomem *base) +{ + int timeout = VF610_OCOTP_TIMEOUT; + + while ((readl(base) & OCOTP_CTRL_BUSY) && --timeout) + udelay(10); + + if (!timeout) { + writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); + return -ETIMEDOUT; + } + + udelay(10); + + return 0; +} + +static int vf610_ocotp_calculate_timing(struct vf610_ocotp *ocotp_dev) +{ + u32 clk_rate; + u32 relax, strobe_read, strobe_prog; + u32 timing; + + clk_rate = clk_get_rate(ocotp_dev->clk); + + /* Refer section OTP read/write timing parameters in TRM */ + relax = clk_rate / (1000000000 / DEF_RELAX) - 1; + strobe_prog = clk_rate / (1000000000 / 10000) + 2 * (DEF_RELAX + 1) - 1; + strobe_read = clk_rate / (1000000000 / 40) + 2 * (DEF_RELAX + 1) - 1; + + timing = BF(relax, OCOTP_TIMING_RELAX); + timing |= BF(strobe_read, OCOTP_TIMING_STROBE_READ); + timing |= BF(strobe_prog, OCOTP_TIMING_STROBE_PROG); + + return timing; +} + +static int vf610_get_fuse_address(int base_addr_offset) +{ + int i; + + for (i = 0; i < ARRAY_SIZE(base_to_fuse_addr_mappings); i++) { + if (base_to_fuse_addr_mappings[i][0] == base_addr_offset) + return base_to_fuse_addr_mappings[i][1]; + } + + return -EINVAL; +} + +static int vf610_ocotp_write(void *context, const void *data, size_t count) +{ + return 0; +} + +static int vf610_ocotp_read(void *context, + const void *off, size_t reg_size, + void *val, size_t val_size) +{ + struct vf610_ocotp *ocotp = context; + void __iomem *base = ocotp->base; + unsigned int offset = *(u32 *)off; + u32 reg, *buf = val; + int fuse_addr; + int ret; + + while (val_size > 0) { + fuse_addr = vf610_get_fuse_address(offset); + if (fuse_addr > 0) { + writel(ocotp->timing, base + OCOTP_TIMING); + ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); + if (ret) + return ret; + + reg = readl(base + OCOTP_CTRL_REG); + reg &= ~OCOTP_CTRL_ADDR_MASK; + reg &= ~OCOTP_CTRL_WR_UNLOCK_MASK; + reg |= BF(fuse_addr, OCOTP_CTRL_ADDR); + writel(reg, base + OCOTP_CTRL_REG); + + writel(OCOTP_READ_CTRL_READ_FUSE, + base + OCOTP_READ_CTRL_REG); + ret = vf610_ocotp_wait_busy(base + OCOTP_CTRL_REG); + if (ret) + return ret; + + if (readl(base) & OCOTP_CTRL_ERR) { + dev_dbg(ocotp->dev, "Error reading from fuse address %x\n", + fuse_addr); + writel(OCOTP_CTRL_ERR, base + OCOTP_CTRL_CLR); + } + + /* + * In case of error, we do not abort and expect to read + * 0xBADABADA as mentioned by the TRM. We just read this + * value and return. + */ + *buf = readl(base + OCOTP_READ_FUSE_DATA); + } else { + *buf = 0; + } + + buf++; + val_size--; + offset += reg_size; + } + + return 0; +} + +static struct regmap_bus vf610_ocotp_bus = { + .read = vf610_ocotp_read, + .write = vf610_ocotp_write, + .reg_format_endian_default = REGMAP_ENDIAN_NATIVE, + .val_format_endian_default = REGMAP_ENDIAN_NATIVE, +}; + +static struct regmap_config ocotp_regmap_config = { + .reg_bits = 32, + .val_bits = 32, + .reg_stride = 4, +}; + +static struct nvmem_config ocotp_config = { + .name = "ocotp", + .owner = THIS_MODULE, +}; + +static const struct of_device_id ocotp_of_match[] = { + { .compatible = "fsl,vf610-ocotp", }, + {/* sentinel */}, +}; +MODULE_DEVICE_TABLE(of, ocotp_of_match); + +static int vf610_ocotp_remove(struct platform_device *pdev) +{ + struct vf610_ocotp *ocotp_dev = platform_get_drvdata(pdev); + + return nvmem_unregister(ocotp_dev->nvmem); +} + +static int vf610_ocotp_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct resource *res; + struct regmap *regmap; + struct vf610_ocotp *ocotp_dev; + + ocotp_dev = devm_kzalloc(&pdev->dev, + sizeof(struct vf610_ocotp), GFP_KERNEL); + if (!ocotp_dev) + return -ENOMEM; + + res = platform_get_resource(pdev, IORESOURCE_MEM, 0); + ocotp_dev->base = devm_ioremap_resource(dev, res); + if (IS_ERR(ocotp_dev->base)) + return PTR_ERR(ocotp_dev->base); + + ocotp_dev->clk = devm_clk_get(dev, NULL); + if (IS_ERR(ocotp_dev->clk)) { + dev_err(dev, "failed getting clock, err = %ld\n", + PTR_ERR(ocotp_dev->clk)); + return PTR_ERR(ocotp_dev->clk); + } + + ocotp_regmap_config.max_register = resource_size(res); + regmap = devm_regmap_init(dev, + &vf610_ocotp_bus, ocotp_dev, &ocotp_regmap_config); + if (IS_ERR(regmap)) { + dev_err(dev, "regmap init failed\n"); + return PTR_ERR(regmap); + } + ocotp_config.dev = dev; + + ocotp_dev->nvmem = nvmem_register(&ocotp_config); + if (IS_ERR(ocotp_dev->nvmem)) + return PTR_ERR(ocotp_dev->nvmem); + + ocotp_dev->dev = dev; + platform_set_drvdata(pdev, ocotp_dev); + + ocotp_dev->timing = vf610_ocotp_calculate_timing(ocotp_dev); + + return 0; +} + +static struct platform_driver vf610_ocotp_driver = { + .probe = vf610_ocotp_probe, + .remove = vf610_ocotp_remove, + .driver = { + .name = "vf610-ocotp", + .of_match_table = ocotp_of_match, + }, +}; +module_platform_driver(vf610_ocotp_driver); +MODULE_AUTHOR("Sanchayan Maity "); +MODULE_DESCRIPTION("Vybrid OCOTP driver"); +MODULE_LICENSE("GPL v2");