282 lines
6.3 KiB
C
282 lines
6.3 KiB
C
/* Copyright (c) 2019-2020, 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/interrupt.h>
|
|
#include <linux/mfd/syscon.h>
|
|
#include <linux/module.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_irq.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regmap.h>
|
|
|
|
#include "hgsl_tcsr.h"
|
|
|
|
/* Sender registers */
|
|
#define TCSR_GLB_CFG_COMPUTE_SIGNALING_REG 0x000
|
|
#define TCSR_COMPUTE_SIGNALING_REG 0x000
|
|
|
|
/* Receiver registers */
|
|
#define TCSR_COMPUTE_SIGNAL_STATUS_REG 0x000
|
|
#define TCSR_COMPUTE_SIGNAL_CLEAR_REG 0x400
|
|
#define TCSR_COMPUTE_SIGNAL_MASK_REG 0x800
|
|
|
|
struct hgsl_tcsr {
|
|
struct platform_device *pdev;
|
|
struct device *client_dev;
|
|
|
|
struct regmap *regmap;
|
|
struct regmap *glb_regmap;
|
|
|
|
enum hgsl_tcsr_role role;
|
|
unsigned int enable_count;
|
|
struct mutex dev_mutex;
|
|
|
|
unsigned int irq_num;
|
|
irqreturn_t (*isr)(struct device *, u32);
|
|
};
|
|
|
|
static irqreturn_t hgsl_tcsr_isr(int irq, void *ptr)
|
|
{
|
|
struct hgsl_tcsr *tcsr = ptr;
|
|
u32 status;
|
|
|
|
regmap_read(tcsr->regmap, TCSR_COMPUTE_SIGNAL_STATUS_REG, &status);
|
|
regmap_write(tcsr->regmap, TCSR_COMPUTE_SIGNAL_CLEAR_REG, status);
|
|
|
|
return tcsr->isr(tcsr->client_dev, status);
|
|
}
|
|
|
|
static int hgsl_tcsr_init_sender(struct hgsl_tcsr *tcsr)
|
|
{
|
|
struct device *dev = &tcsr->pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
|
|
if (tcsr->glb_regmap != NULL)
|
|
return 0;
|
|
|
|
tcsr->regmap = syscon_regmap_lookup_by_phandle(np, "syscon");
|
|
if (IS_ERR_OR_NULL(tcsr->regmap)) {
|
|
dev_err(dev, "failed to map sender register\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
tcsr->glb_regmap = syscon_regmap_lookup_by_phandle(np, "syscon-glb");
|
|
if (IS_ERR_OR_NULL(tcsr->glb_regmap)) {
|
|
dev_err(dev, "failed to map sender global register\n");
|
|
tcsr->glb_regmap = NULL;
|
|
return -ENODEV;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hgsl_tcsr_init_receiver(struct hgsl_tcsr *tcsr)
|
|
{
|
|
struct device *dev = &tcsr->pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
int ret;
|
|
|
|
if (tcsr->irq_num != 0)
|
|
return 0;
|
|
|
|
tcsr->regmap = syscon_regmap_lookup_by_phandle(np, "syscon");
|
|
if (IS_ERR_OR_NULL(tcsr->regmap)) {
|
|
dev_err(dev, "failed to map receiver register\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
tcsr->irq_num = irq_of_parse_and_map(np, 0);
|
|
if (tcsr->irq_num == 0) {
|
|
dev_err(dev, "failed to get irq\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = devm_request_irq(dev, tcsr->irq_num, hgsl_tcsr_isr,
|
|
IRQF_TRIGGER_HIGH, "hgsl-tcsr", tcsr);
|
|
if (ret < 0) {
|
|
dev_err(dev, "failed to request IRQ%u: %d\n",
|
|
tcsr->irq_num, ret);
|
|
tcsr->irq_num = 0;
|
|
return ret;
|
|
}
|
|
|
|
disable_irq(tcsr->irq_num);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct hgsl_tcsr *hgsl_tcsr_request(struct platform_device *pdev,
|
|
enum hgsl_tcsr_role role,
|
|
struct device *client,
|
|
irqreturn_t (*isr)(struct device *, u32))
|
|
{
|
|
struct hgsl_tcsr *tcsr = platform_get_drvdata(pdev);
|
|
int ret;
|
|
|
|
if (!tcsr)
|
|
return ERR_PTR(-ENODEV);
|
|
else if (tcsr->role != role)
|
|
return ERR_PTR(-EINVAL);
|
|
else if (tcsr->client_dev)
|
|
return ERR_PTR(-EBUSY);
|
|
|
|
if (role == HGSL_TCSR_ROLE_RECEIVER) {
|
|
if (tcsr->isr)
|
|
return ERR_PTR(-EBUSY);
|
|
else if (!isr)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
ret = hgsl_tcsr_init_receiver(tcsr);
|
|
} else { /* HGSL_TCSR_ROLE_SENDER */
|
|
if (isr)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
ret = hgsl_tcsr_init_sender(tcsr);
|
|
}
|
|
|
|
if (ret == 0) {
|
|
tcsr->client_dev = client;
|
|
tcsr->isr = isr;
|
|
}
|
|
|
|
return tcsr;
|
|
}
|
|
|
|
void hgsl_tcsr_free(struct hgsl_tcsr *tcsr)
|
|
{
|
|
tcsr->client_dev = NULL;
|
|
tcsr->isr = NULL;
|
|
}
|
|
|
|
int hgsl_tcsr_enable(struct hgsl_tcsr *tcsr)
|
|
{
|
|
mutex_lock(&tcsr->dev_mutex);
|
|
if (tcsr->enable_count > 0)
|
|
goto done;
|
|
|
|
if (tcsr->irq_num)
|
|
enable_irq(tcsr->irq_num);
|
|
|
|
done:
|
|
tcsr->enable_count++;
|
|
mutex_unlock(&tcsr->dev_mutex);
|
|
return 0;
|
|
}
|
|
|
|
void hgsl_tcsr_disable(struct hgsl_tcsr *tcsr)
|
|
{
|
|
mutex_lock(&tcsr->dev_mutex);
|
|
if (--tcsr->enable_count > 0)
|
|
goto done;
|
|
|
|
if (tcsr->irq_num)
|
|
disable_irq(tcsr->irq_num);
|
|
|
|
done:
|
|
mutex_unlock(&tcsr->dev_mutex);
|
|
}
|
|
|
|
bool hgsl_tcsr_is_enabled(struct hgsl_tcsr *tcsr)
|
|
{
|
|
return (tcsr->enable_count > 0);
|
|
}
|
|
|
|
void hgsl_tcsr_irq_trigger(struct hgsl_tcsr *tcsr, int irq_id)
|
|
{
|
|
u32 reg;
|
|
|
|
/*
|
|
* Read back this global config register in case
|
|
* it has been modified by others.
|
|
*/
|
|
regmap_read(tcsr->glb_regmap,
|
|
TCSR_GLB_CFG_COMPUTE_SIGNALING_REG, ®);
|
|
reg = irq_id << reg;
|
|
regmap_write(tcsr->regmap, TCSR_COMPUTE_SIGNALING_REG, reg);
|
|
}
|
|
|
|
void hgsl_tcsr_irq_enable(struct hgsl_tcsr *tcsr, u32 mask, bool enable)
|
|
{
|
|
u32 reg;
|
|
|
|
regmap_read(tcsr->regmap, TCSR_COMPUTE_SIGNAL_MASK_REG, ®);
|
|
reg = enable ? (reg | mask) : (reg & ~mask);
|
|
regmap_write(tcsr->regmap, TCSR_COMPUTE_SIGNAL_MASK_REG, reg);
|
|
}
|
|
|
|
static const struct of_device_id hgsl_tcsr_match_table[] = {
|
|
{ .compatible = "qcom,hgsl-tcsr-sender" },
|
|
{ .compatible = "qcom,hgsl-tcsr-receiver" },
|
|
{}
|
|
};
|
|
|
|
static int hgsl_tcsr_probe(struct platform_device *pdev)
|
|
{
|
|
struct device *dev = &pdev->dev;
|
|
struct device_node *np = dev->of_node;
|
|
struct hgsl_tcsr *tcsr = devm_kzalloc(dev, sizeof(*tcsr),
|
|
GFP_KERNEL);
|
|
|
|
if (!tcsr)
|
|
return -ENOMEM;
|
|
|
|
if (of_device_is_compatible(np, "qcom,hgsl-tcsr-receiver")) {
|
|
tcsr->role = HGSL_TCSR_ROLE_RECEIVER;
|
|
} else if (of_device_is_compatible(np, "qcom,hgsl-tcsr-sender")) {
|
|
tcsr->role = HGSL_TCSR_ROLE_SENDER;
|
|
} else {
|
|
dev_err(dev, "Not compatible device\n");
|
|
devm_kfree(dev, tcsr);
|
|
return -ENODEV;
|
|
}
|
|
|
|
mutex_init(&tcsr->dev_mutex);
|
|
tcsr->pdev = pdev;
|
|
platform_set_drvdata(pdev, tcsr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int hgsl_tcsr_remove(struct platform_device *pdev)
|
|
{
|
|
struct hgsl_tcsr *tcsr = platform_get_drvdata(pdev);
|
|
|
|
mutex_destroy(&tcsr->dev_mutex);
|
|
devm_kfree(&pdev->dev, tcsr);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct platform_driver hgsl_tcsr_driver = {
|
|
.probe = hgsl_tcsr_probe,
|
|
.remove = hgsl_tcsr_remove,
|
|
.driver = {
|
|
.owner = THIS_MODULE,
|
|
.name = "hgsl-tcsr",
|
|
.of_match_table = hgsl_tcsr_match_table,
|
|
}
|
|
};
|
|
|
|
static int __init hgsl_tcsr_init(void)
|
|
{
|
|
return platform_driver_register(&hgsl_tcsr_driver);
|
|
}
|
|
|
|
static void __exit hgsl_tcsr_exit(void)
|
|
{
|
|
platform_driver_unregister(&hgsl_tcsr_driver);
|
|
}
|
|
|
|
subsys_initcall(hgsl_tcsr_init);
|
|
module_exit(hgsl_tcsr_exit);
|