| /* Copyright (c) 2014, The Linux Foundation. All rights reserved. |
| * |
| * 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 <linux/kernel.h> |
| #include <linux/module.h> |
| #include <linux/init.h> |
| #include <linux/device.h> |
| #include <linux/platform_device.h> |
| #include <linux/io.h> |
| #include <linux/err.h> |
| #include <linux/slab.h> |
| #include <linux/delay.h> |
| #include <linux/mutex.h> |
| #include <linux/clk.h> |
| #include <linux/of_coresight.h> |
| #include <linux/coresight.h> |
| #include <linux/regulator/consumer.h> |
| #include <linux/gpio.h> |
| #include <linux/of_gpio.h> |
| #include <linux/pinctrl/consumer.h> |
| #include <linux/interrupt.h> |
| #include "coresight-priv.h" |
| #include "coresight-nidnt.h" |
| |
| #define nidnt_writel(drvdata, val, off) __raw_writel((val), drvdata->base + off) |
| #define nidnt_readl(drvdata, off) __raw_readl(drvdata->base + off) |
| |
| #define NIDNT_MAX_PINS 6 |
| #define NIDNT_BOOT_FROM_SD BIT(0) |
| #define NIDNT_SDCARD_ONLY BIT(1) |
| #define NIDNT_CARD_DETECT_GPIO_POLARITY BIT(8) |
| #define NIDNT_CARD_DETECT_GPIO_SHIFT 0 |
| #define NIDNT_DEBOUNCE_MASK 0xfffff000UL |
| #define NIDNT_DEBUGMODE_MASK 0x3fcUL |
| #define NIDNT_STATUS_DEBUGMODE_MASK 0x7f8UL |
| #define NIDNT_TIMEOUT_MASK 0xffffffUL |
| |
| /* NIDnT Registers */ |
| #define TLMM_QDSD_HDRV_PULL_DEBUG_GPIO_CTL (0x9c000) |
| #define TLMM_QDSD_BOOT_CTL (0x9d000) |
| #define TLMM_QDSD_CONFIG_CTL (0x9e000) |
| #define TLMM_QDSD_STATUS_CTL (0x9f000) |
| #define TLMM_QDSD_DEBUG_HDRV_PULL_CTL (0xa0000) |
| #define TLMM_QDSD_GPIO_CTL (0xa1000) |
| #define TLMM_QDSD_INTR_ENABLE_CTL (0xa2000) |
| #define TLMM_QDSD_INTR_CLEAR_CTL (0xa3000) |
| #define TLMM_QDSD_TIMEOUT_VALUE_CTL (0xa4000) |
| #define TLMM_QDSD_SPARE1_CTL (0xa5000) |
| #define TLMM_QDSD_SPARE2_CTL (0xa6000) |
| |
| struct nidnt_pinctrl_data { |
| struct pinctrl *pctrl; |
| struct pinctrl_state *pins_active[NIDNT_MAX_PINS]; |
| }; |
| |
| struct nidnt_drvdata { |
| void __iomem *base; |
| struct nidnt_pinctrl_data *nidnt_pctrl_data; |
| unsigned int nidnt_gpio; |
| unsigned int debounce_value; |
| unsigned int timeout_value; |
| bool is_active_low; |
| /* nidnt functionality enable */ |
| bool enable; |
| /* nidnt hw detect enable */ |
| bool nidnt_hwdetect_enable; |
| int nidnt_irq; |
| int sd_irq; |
| spinlock_t spinlock; |
| }; |
| struct nidnt_drvdata *nidnt_drvdata; |
| |
| static int nidnt_timeout_value = 0xFFFFFF; |
| module_param_named(nidnt_timeout_value, |
| nidnt_timeout_value, int, S_IRUGO | S_IWUSR | S_IWGRP); |
| |
| void coresight_nidnt_writel(unsigned int val, unsigned int off) |
| { |
| nidnt_writel(nidnt_drvdata, val, off); |
| } |
| EXPORT_SYMBOL(coresight_nidnt_writel); |
| |
| static int coresight_nidnt_setup_pinctrl(struct nidnt_drvdata *nidnt_drvdata, |
| enum nidnt_debug_mode mode) |
| { |
| int ret = 0; |
| |
| switch (mode) { |
| case NIDNT_MODE_SDCARD: |
| default: |
| ret = pinctrl_select_state( |
| nidnt_drvdata->nidnt_pctrl_data->pctrl, |
| nidnt_drvdata->nidnt_pctrl_data->pins_active[0]); |
| break; |
| case NIDNT_MODE_SDC_TRACE: |
| ret = pinctrl_select_state( |
| nidnt_drvdata->nidnt_pctrl_data->pctrl, |
| nidnt_drvdata->nidnt_pctrl_data->pins_active[1]); |
| break; |
| case NIDNT_MODE_SDC_SWDUART: |
| ret = pinctrl_select_state( |
| nidnt_drvdata->nidnt_pctrl_data->pctrl, |
| nidnt_drvdata->nidnt_pctrl_data->pins_active[2]); |
| break; |
| case NIDNT_MODE_SDC_SWDTRC: |
| ret = pinctrl_select_state( |
| nidnt_drvdata->nidnt_pctrl_data->pctrl, |
| nidnt_drvdata->nidnt_pctrl_data->pins_active[3]); |
| break; |
| case NIDNT_MODE_SDC_JTAG: |
| ret = pinctrl_select_state( |
| nidnt_drvdata->nidnt_pctrl_data->pctrl, |
| nidnt_drvdata->nidnt_pctrl_data->pins_active[4]); |
| break; |
| case NIDNT_MODE_SDC_SPMI: |
| ret = pinctrl_select_state( |
| nidnt_drvdata->nidnt_pctrl_data->pctrl, |
| nidnt_drvdata->nidnt_pctrl_data->pins_active[5]); |
| break; |
| } |
| |
| if (ret < 0) |
| pr_err("Setting %x state for pinctrl failed with %d\n", |
| mode, ret); |
| |
| return ret; |
| } |
| |
| int coresight_nidnt_config_swoverride(enum nidnt_debug_mode mode) |
| { |
| unsigned int reg; |
| int ret; |
| |
| if (!nidnt_drvdata->enable) |
| return -EPERM; |
| |
| ret = coresight_nidnt_setup_pinctrl(nidnt_drvdata, mode); |
| if (ret < 0) |
| return ret; |
| |
| spin_lock(&nidnt_drvdata->spinlock); |
| |
| reg = nidnt_readl(nidnt_drvdata, TLMM_QDSD_CONFIG_CTL); |
| reg &= ~(NIDNT_DEBUGMODE_MASK | NIDNT_DEBOUNCE_MASK); |
| /* Configure debug mode and set sw override bit */ |
| reg |= ((mode << 2) & NIDNT_DEBUGMODE_MASK) | BIT(0); |
| reg |= (nidnt_drvdata->debounce_value << 12 | BIT(11)); |
| /* Add the pin configuration */ |
| nidnt_writel(nidnt_drvdata, reg, TLMM_QDSD_CONFIG_CTL); |
| |
| /* Configure the timeout value */ |
| nidnt_writel(nidnt_drvdata, nidnt_drvdata->timeout_value | BIT(31), |
| TLMM_QDSD_TIMEOUT_VALUE_CTL); |
| |
| spin_unlock(&nidnt_drvdata->spinlock); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(coresight_nidnt_config_swoverride); |
| |
| static void coresight_nidnt_config_hwdetect(struct nidnt_drvdata *nidnt_drvdata) |
| { |
| uint32_t nidnt_gpio_reg; |
| |
| nidnt_gpio_reg = (nidnt_drvdata->nidnt_gpio & 0xff) |
| << NIDNT_CARD_DETECT_GPIO_SHIFT; |
| nidnt_gpio_reg |= nidnt_drvdata->is_active_low ? |
| 0 : NIDNT_CARD_DETECT_GPIO_POLARITY; |
| nidnt_gpio_reg |= BIT(15); |
| |
| spin_lock(&nidnt_drvdata->spinlock); |
| nidnt_writel(nidnt_drvdata, nidnt_gpio_reg, TLMM_QDSD_GPIO_CTL); |
| |
| /* enable SD and QDSD interrupt */ |
| nidnt_writel(nidnt_drvdata, 0x3, TLMM_QDSD_INTR_ENABLE_CTL); |
| spin_unlock(&nidnt_drvdata->spinlock); |
| |
| pr_info("nidnt gpio reg: 0x%x\n", (unsigned int)nidnt_gpio_reg); |
| } |
| |
| void coresight_nidnt_set_hwdetect_param(bool val) |
| { |
| if (nidnt_drvdata->enable) |
| nidnt_drvdata->nidnt_hwdetect_enable = val; |
| } |
| EXPORT_SYMBOL(coresight_nidnt_set_hwdetect_param); |
| |
| static void __coresight_nidnt_enable_hwdetect(struct nidnt_drvdata |
| *nidnt_drvdata) |
| { |
| unsigned int regval; |
| |
| spin_lock(&nidnt_drvdata->spinlock); |
| |
| regval = nidnt_readl(nidnt_drvdata, TLMM_QDSD_CONFIG_CTL); |
| /* clear the soft override bit to support the hardware detect */ |
| regval &= ~(BIT(0) | BIT(11)); |
| nidnt_writel(nidnt_drvdata, regval, TLMM_QDSD_CONFIG_CTL); |
| |
| /* set the timeout value */ |
| regval = nidnt_readl(nidnt_drvdata, TLMM_QDSD_TIMEOUT_VALUE_CTL); |
| regval = nidnt_drvdata->timeout_value | BIT(31); |
| nidnt_writel(nidnt_drvdata, regval, TLMM_QDSD_TIMEOUT_VALUE_CTL); |
| |
| spin_unlock(&nidnt_drvdata->spinlock); |
| } |
| |
| int coresight_nidnt_enable_hwdetect(void) |
| { |
| if (!nidnt_drvdata->nidnt_hwdetect_enable) |
| return -EPERM; |
| |
| coresight_nidnt_config_hwdetect(nidnt_drvdata); |
| /* Enable the TLMM debug mode for nidnt detect.*/ |
| __coresight_nidnt_enable_hwdetect(nidnt_drvdata); |
| |
| return 0; |
| } |
| EXPORT_SYMBOL(coresight_nidnt_enable_hwdetect); |
| |
| ssize_t coresight_nidnt_show_timeout_value(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| unsigned int val = nidnt_drvdata->timeout_value; |
| |
| if (!nidnt_drvdata->enable) |
| return -EPERM; |
| |
| return scnprintf(buf, PAGE_SIZE, "%#x", val); |
| } |
| EXPORT_SYMBOL(coresight_nidnt_show_timeout_value); |
| |
| ssize_t coresight_nidnt_store_timeout_value(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| unsigned int val; |
| |
| if (!nidnt_drvdata->enable) |
| return -EPERM; |
| |
| if (sscanf(buf, "%x", &val) != 1) |
| return -EINVAL; |
| |
| nidnt_drvdata->timeout_value = val & NIDNT_TIMEOUT_MASK; |
| |
| return size; |
| } |
| EXPORT_SYMBOL(coresight_nidnt_store_timeout_value); |
| |
| ssize_t coresight_nidnt_show_debounce_value(struct device *dev, |
| struct device_attribute *attr, |
| char *buf) |
| { |
| unsigned int val = nidnt_drvdata->debounce_value; |
| |
| if (!nidnt_drvdata->enable) |
| return -EPERM; |
| |
| return scnprintf(buf, PAGE_SIZE, "%x\n", val); |
| } |
| EXPORT_SYMBOL(coresight_nidnt_show_debounce_value); |
| |
| ssize_t coresight_nidnt_store_debounce_value(struct device *dev, |
| struct device_attribute *attr, |
| const char *buf, size_t size) |
| { |
| unsigned int val; |
| |
| if (!nidnt_drvdata->enable) |
| return -EPERM; |
| |
| if (sscanf(buf, "%x", &val) != 1) |
| return -EINVAL; |
| |
| nidnt_drvdata->debounce_value = val; |
| |
| return size; |
| } |
| EXPORT_SYMBOL(coresight_nidnt_store_debounce_value); |
| |
| int coresight_nidnt_get_status(void) |
| { |
| unsigned int reg; |
| uint32_t nidnt_mode = 0; |
| |
| reg = nidnt_readl(nidnt_drvdata, TLMM_QDSD_STATUS_CTL); |
| if ((reg & BIT(1)) || !(reg & BIT(2)) || !(reg & BIT(0))) |
| nidnt_mode = 0; |
| else |
| nidnt_mode = (reg & NIDNT_STATUS_DEBUGMODE_MASK); |
| |
| return nidnt_mode; |
| |
| } |
| EXPORT_SYMBOL(coresight_nidnt_get_status); |
| |
| static bool coresight_nidnt_is_boot_from_sd(struct nidnt_drvdata *nidnt_drvdata) |
| { |
| uint16_t nidnt_boot_config; |
| bool ret; |
| |
| nidnt_boot_config = nidnt_readl(nidnt_drvdata, TLMM_QDSD_BOOT_CTL); |
| |
| pr_info("nidnt boot config: %x\n", |
| (unsigned int)nidnt_boot_config); |
| |
| if (nidnt_boot_config & NIDNT_BOOT_FROM_SD) |
| ret = true; |
| else if (nidnt_boot_config & NIDNT_SDCARD_ONLY) |
| ret = true; |
| else |
| ret = false; |
| |
| if (ret) |
| pr_err("NIDnT disabled, only sd mode supported.\n"); |
| |
| return ret; |
| } |
| |
| int coresight_nidnt_config_qdsd_enable(bool enable) |
| { |
| unsigned long val; |
| |
| if (!nidnt_drvdata) |
| return -EINVAL; |
| |
| val = nidnt_readl(nidnt_drvdata, TLMM_QDSD_BOOT_CTL); |
| if (enable) |
| val &= ~NIDNT_SDCARD_ONLY; |
| else |
| val |= NIDNT_SDCARD_ONLY; |
| nidnt_writel(nidnt_drvdata, val, TLMM_QDSD_BOOT_CTL); |
| |
| return 0; |
| } |
| |
| static int coresight_nidnt_parse_pinctrl_info(struct device *dev, |
| struct nidnt_drvdata * |
| nidnt_drvdata) |
| { |
| struct nidnt_pinctrl_data *pctrl_data; |
| struct pinctrl *pctrl; |
| int ret = 0; |
| |
| /* Try to obtain pinctrl handle */ |
| pctrl = devm_pinctrl_get(dev); |
| if (IS_ERR(pctrl)) { |
| ret = PTR_ERR(pctrl); |
| goto err; |
| } |
| pctrl_data = devm_kzalloc(dev, sizeof(*pctrl_data), GFP_KERNEL); |
| if (!pctrl_data) { |
| ret = -ENOMEM; |
| goto err; |
| } |
| pctrl_data->pctrl = pctrl; |
| /* Look-up and keep the states handy to be used later */ |
| pctrl_data->pins_active[0] = pinctrl_lookup_state( |
| pctrl_data->pctrl, "sdcard"); |
| if (IS_ERR(pctrl_data->pins_active[0])) { |
| ret = PTR_ERR(pctrl_data->pins_active[0]); |
| dev_err(dev, "Could not get sdcard pinstates, err:%d\n", ret); |
| goto err; |
| } |
| pctrl_data->pins_active[1] = pinctrl_lookup_state( |
| pctrl_data->pctrl, "trace"); |
| if (IS_ERR(pctrl_data->pins_active[1])) { |
| ret = PTR_ERR(pctrl_data->pins_active[1]); |
| dev_err(dev, "Could not get trace pinstates, err:%d\n", ret); |
| goto err; |
| } |
| pctrl_data->pins_active[2] = pinctrl_lookup_state( |
| pctrl_data->pctrl, "swduart"); |
| if (IS_ERR(pctrl_data->pins_active[2])) { |
| ret = PTR_ERR(pctrl_data->pins_active[2]); |
| dev_err(dev, "Could not get swduart pinstates, err:%d\n", ret); |
| goto err; |
| } |
| pctrl_data->pins_active[3] = pinctrl_lookup_state( |
| pctrl_data->pctrl, "swdtrc"); |
| if (IS_ERR(pctrl_data->pins_active[3])) { |
| ret = PTR_ERR(pctrl_data->pins_active[3]); |
| dev_err(dev, "Could not get swdtrc pinstates, err:%d\n", ret); |
| goto err; |
| } |
| pctrl_data->pins_active[4] = pinctrl_lookup_state( |
| pctrl_data->pctrl, "jtag"); |
| if (IS_ERR(pctrl_data->pins_active[4])) { |
| ret = PTR_ERR(pctrl_data->pins_active[4]); |
| dev_err(dev, "Could not get jtag pinstates, err:%d\n", ret); |
| goto err; |
| } |
| pctrl_data->pins_active[5] = pinctrl_lookup_state( |
| pctrl_data->pctrl, "spmi"); |
| if (IS_ERR(pctrl_data->pins_active[5])) { |
| ret = PTR_ERR(pctrl_data->pins_active[5]); |
| dev_err(dev, "Could not get spmi pinstates, err:%d\n", ret); |
| goto err; |
| } |
| nidnt_drvdata->nidnt_pctrl_data = pctrl_data; |
| err: |
| return ret; |
| } |
| |
| static void dump_nidnt_reg(struct nidnt_drvdata *nidnt_drvdata) |
| { |
| unsigned int val; |
| |
| val = nidnt_readl(nidnt_drvdata, TLMM_QDSD_CONFIG_CTL); |
| pr_debug("TLMM_QDSD_CONFIG_CTL: 0x%08x\n", val); |
| val = nidnt_readl(nidnt_drvdata, TLMM_QDSD_STATUS_CTL); |
| pr_debug("TLMM_QDSD_STATUS_CTL: 0x%08x\n", val); |
| val = nidnt_readl(nidnt_drvdata, TLMM_QDSD_DEBUG_HDRV_PULL_CTL); |
| pr_debug("TLMM_QDSD_DEBUG_HDRV_PULL_CTL: 0x%08x\n", val); |
| } |
| |
| static irqreturn_t nidnt_qdsd_irq(int irq, void *data) |
| { |
| struct nidnt_drvdata *nidnt_drvdata = data; |
| |
| nidnt_writel(nidnt_drvdata, 0x1, TLMM_QDSD_INTR_CLEAR_CTL); |
| dump_nidnt_reg(nidnt_drvdata); |
| |
| return IRQ_HANDLED; |
| } |
| |
| int coresight_nidnt_init(struct platform_device *pdev) |
| { |
| struct device_node *node = pdev->dev.of_node; |
| struct device *dev = &pdev->dev; |
| struct resource *res; |
| int ret = 0; |
| uint32_t nidnt_gpio; |
| uint32_t nidnt_gpio_polarity; |
| bool nidnthw = false; |
| |
| nidnt_drvdata = devm_kzalloc(dev, |
| sizeof(struct nidnt_drvdata), |
| GFP_KERNEL); |
| if (!nidnt_drvdata) |
| return -ENOMEM; |
| |
| res = platform_get_resource_byname(pdev, |
| IORESOURCE_MEM, |
| "nidnt-base"); |
| if (!res) |
| return -ENODEV; |
| |
| nidnt_drvdata->base = devm_ioremap(dev, res->start, |
| resource_size(res)); |
| if (!nidnt_drvdata->base) |
| return -ENOMEM; |
| |
| nidnthw = of_property_read_bool(node, "qcom,nidnthw"); |
| if (!nidnthw) |
| return 0; |
| |
| spin_lock_init(&nidnt_drvdata->spinlock); |
| |
| /* |
| * NIDnT can be disabled in the following cases: |
| * 1. NIDnT fuse is blown |
| * 2. If TLMM_QDSD_BOOT_CTL 0,1 bit is set. |
| */ |
| if (coresight_nidnt_is_boot_from_sd(nidnt_drvdata) || |
| coresight_fuse_nidnt_access_disabled()) { |
| dev_info(dev, "NIDnT hw support disabled\n"); |
| /* |
| * Do not return error an error if NIDnT support |
| * is disabled to allow continuing with other |
| * TPIU functionalities. |
| */ |
| return 0; |
| } |
| nidnt_drvdata->enable = true; |
| |
| ret = coresight_nidnt_parse_pinctrl_info(&pdev->dev, nidnt_drvdata); |
| if (ret) |
| return ret; |
| |
| ret = of_property_read_u32(node, "nidnt-gpio", &nidnt_gpio); |
| nidnt_drvdata->nidnt_gpio = nidnt_gpio; |
| |
| ret = of_property_read_u32(node, |
| "nidnt-gpio-polarity", |
| &nidnt_gpio_polarity); |
| nidnt_drvdata->is_active_low = nidnt_gpio_polarity & |
| OF_GPIO_ACTIVE_LOW ? true : false; |
| |
| nidnt_drvdata->timeout_value = nidnt_timeout_value; |
| |
| nidnt_drvdata->nidnt_irq = platform_get_irq_byname(pdev, "nidnt-irq"); |
| if (nidnt_drvdata->nidnt_irq < 0) { |
| /* |
| * Even though this is an error condition, we do not fail |
| * the probe as the byte counter feature is optional |
| */ |
| dev_info(dev, "nidnt-irq not specified\n"); |
| return 0; |
| } |
| |
| ret = devm_request_irq(&pdev->dev, nidnt_drvdata->nidnt_irq, |
| nidnt_qdsd_irq, |
| IRQF_TRIGGER_RISING | IRQF_SHARED, |
| "nidnt_nidnt", nidnt_drvdata); |
| if (ret) { |
| dev_info(&pdev->dev, "request for nidnt irq failed\n"); |
| return -EINVAL; |
| } |
| return ret; |
| } |
| EXPORT_SYMBOL(coresight_nidnt_init); |
| |