tegrakernel/kernel/nvidia/drivers/platform/tegra/mselect.c

242 lines
6.7 KiB
C

/*
* Copyright (c) 2013-2018, NVIDIA CORPORATION. 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 as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
#include <linux/module.h>
#include <linux/interrupt.h>
#include <linux/platform_device.h>
#include <linux/err.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_address.h>
#define HIER_GROUP_CPU_ENABLE 0x00000000
#define HIER_GROUP_CPU_STATUS 0x00000004
#define HIER_GROUP1_MSELECT_ERROR 15
#define MSELECT_CONFIG_0 0x0
#define MSELECT_CONFIG_0_READ_TIMEOUT_EN_SLAVE0_SHIFT 16
#define MSELECT_CONFIG_0_WRITE_TIMEOUT_EN_SLAVE0_SHIFT 17
#define MSELECT_CONFIG_0_READ_TIMEOUT_EN_SLAVE1_SHIFT 18
#define MSELECT_CONFIG_0_WRITE_TIMEOUT_EN_SLAVE1_SHIFT 19
#define MSELECT_CONFIG_0_READ_TIMEOUT_EN_SLAVE2_SHIFT 20
#define MSELECT_CONFIG_0_WRITE_TIMEOUT_EN_SLAVE2_SHIFT 21
#define MSELECT_CONFIG_0_ERR_RESP_EN_SLAVE1_SHIFT 24
#define MSELECT_CONFIG_0_ERR_RESP_EN_SLAVE2_SHIFT 25
#define MSELECT_TIMEOUT_TIMER_0 0x5c
#define MSELECT_ERROR_STATUS_0 0x60
#define MSELECT_DEFAULT_TIMEOUT 0xFFFFFF
struct tegra_mselect_control {
u32 irq;
u32 mselect_timeout_cycles;
void __iomem *mselect_base;
};
static irqreturn_t tegra_mselect_irq_handler(int irq, void *data)
{
struct device *dev = data;
struct tegra_mselect_control *msel = dev_get_drvdata(dev);
unsigned long status;
status = readl(msel->mselect_base + MSELECT_ERROR_STATUS_0);
if (status != 0) {
pr_err("MSELECT error detected! status=0x%x\n",
(unsigned int)status);
BUG();
}
return IRQ_HANDLED;
}
static int tegra_mselect_map_memory(struct device *dp,
struct tegra_mselect_control *msel)
{
msel->mselect_base = of_iomap(dp->of_node, 0);
if (!msel->mselect_base) {
dev_err(dp, "Unable to map memory (mselect).\n");
return -EBUSY;
}
return 0;
}
static int tegra_mselect_irq_init(struct platform_device *pdev,
struct tegra_mselect_control *msel)
{
int ret;
msel->irq = platform_get_irq(pdev, 0);
if (msel->irq <= 0)
return -EBUSY;
ret = devm_request_irq(&pdev->dev, msel->irq,
tegra_mselect_irq_handler, IRQF_TRIGGER_HIGH,
"mselect_irq", &pdev->dev);
if (ret) {
dev_err(&pdev->dev,
"Unable to request interrupt for device (err=%d).\n",
ret);
return ret;
}
return 0;
}
static void tegra_mselect_set_timeout(struct tegra_mselect_control *msel,
u32 timeout_cycles)
{
msel->mselect_timeout_cycles = timeout_cycles;
writel(msel->mselect_timeout_cycles, msel->mselect_base +
MSELECT_TIMEOUT_TIMER_0);
}
static int tegra_mselect_timeout_init(struct tegra_mselect_control *msel)
{
unsigned long reg;
tegra_mselect_set_timeout(msel, MSELECT_DEFAULT_TIMEOUT);
reg = readl(msel->mselect_base + MSELECT_CONFIG_0);
writel(reg |
((1 << MSELECT_CONFIG_0_READ_TIMEOUT_EN_SLAVE0_SHIFT) |
(1 << MSELECT_CONFIG_0_WRITE_TIMEOUT_EN_SLAVE0_SHIFT) |
(1 << MSELECT_CONFIG_0_READ_TIMEOUT_EN_SLAVE1_SHIFT) |
(1 << MSELECT_CONFIG_0_WRITE_TIMEOUT_EN_SLAVE1_SHIFT) |
(1 << MSELECT_CONFIG_0_READ_TIMEOUT_EN_SLAVE2_SHIFT) |
(1 << MSELECT_CONFIG_0_WRITE_TIMEOUT_EN_SLAVE2_SHIFT) |
(1 << MSELECT_CONFIG_0_ERR_RESP_EN_SLAVE1_SHIFT) |
(1 << MSELECT_CONFIG_0_ERR_RESP_EN_SLAVE2_SHIFT)),
msel->mselect_base + MSELECT_CONFIG_0);
return 0;
}
static ssize_t mselect_timeout_show(struct device *device,
struct device_attribute *attr, char *buf)
{
struct tegra_mselect_control *msel = dev_get_drvdata(device);
return sprintf(buf, "%d\n", msel->mselect_timeout_cycles);
}
static ssize_t mselect_timeout_store(struct device *device,
struct device_attribute *attr, const char *buf, size_t count)
{
struct tegra_mselect_control *msel = dev_get_drvdata(device);
unsigned long val = 0;
if (kstrtoul(buf, 10, &val) < 0)
return -EINVAL;
tegra_mselect_set_timeout(msel, val);
return count;
}
static DEVICE_ATTR(mselect_timeout, S_IWUSR | S_IRUGO, mselect_timeout_show,
mselect_timeout_store);
static void tegra_mselect_create_sysfs(struct platform_device *pdev)
{
int error;
error = device_create_file(&pdev->dev, &dev_attr_mselect_timeout);
if (error)
dev_err(&pdev->dev, "Failed to create sysfs attributes!\n");
}
static void tegra_mselect_remove_sysfs(struct platform_device *pdev)
{
device_remove_file(&pdev->dev, &dev_attr_mselect_timeout);
}
static int tegra_mselect_probe(struct platform_device *pdev)
{
struct tegra_mselect_control *msel;
int ret;
msel = devm_kzalloc(&pdev->dev, sizeof(struct tegra_mselect_control),
GFP_KERNEL);
if (!msel)
return -ENOMEM;
ret = tegra_mselect_map_memory(&pdev->dev, msel);
if (ret)
return ret;
ret = tegra_mselect_timeout_init(msel);
if (ret)
return ret;
dev_set_drvdata(&pdev->dev, msel);
ret = tegra_mselect_irq_init(pdev, msel);
if (ret)
return ret;
tegra_mselect_create_sysfs(pdev);
dev_notice(&pdev->dev, "probed\n");
return 0;
}
static int __exit tegra_mselect_remove(struct platform_device *pdev)
{
tegra_mselect_remove_sysfs(pdev);
return 0;
}
#ifdef CONFIG_OF
static struct of_device_id tegra_mselect_of_match[] = {
{ .compatible = "nvidia,tegra-mselect", },
{ },
};
MODULE_DEVICE_TABLE(of, tegra_mselect_of_match);
#endif
static struct platform_driver tegra_mselect_driver = {
.driver = {
.name = "tegra-mselect",
.of_match_table = of_match_ptr(tegra_mselect_of_match),
.owner = THIS_MODULE,
},
.probe = tegra_mselect_probe,
.remove = __exit_p(tegra_mselect_remove),
};
static int __init tegra_mselect_init(void)
{
return platform_driver_register(&tegra_mselect_driver);
}
static void __exit tegra_mselect_exit(void)
{
platform_driver_unregister(&tegra_mselect_driver);
}
module_init(tegra_mselect_init);
module_exit(tegra_mselect_exit);
MODULE_DESCRIPTION("Tegra Hierarchical Interrupt Controller Driver");
MODULE_LICENSE("GPL v2");