525 lines
14 KiB
C
525 lines
14 KiB
C
/* 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);
|
|
|