Merge "spmi: spmi-pmic-arb: make interrupt support optional"

This commit is contained in:
qctecmdr 2020-03-22 05:55:58 -07:00 • committed by Gerrit - the friendly Code Review server
commit 2402446736
2 changed files with 168 additions and 18 deletions

View file

@ -1,5 +1,5 @@
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2012-2019, The Linux Foundation. All rights reserved. */
/* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved. */
#include <linux/bitmap.h>
#include <linux/delay.h>
@ -12,9 +12,11 @@
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/of_platform.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/spmi.h>
#include <linux/soc/qcom/spmi-pmic-arb.h>
/* PMIC Arbiter configuration registers */
#define PMIC_ARB_VERSION 0x0000
@ -123,6 +125,8 @@ struct apid_data {
*
* @rd_base: on v1 "core", on v2 "observer" register base off DT.
* @wr_base: on v1 "core", on v2 "chnls" register base off DT.
* @wr_base_phys: Base physical address of the register range used for
* SPMI write commands.
* @intr: address of the SPMI interrupt control registers.
* @cnfg: address of the PMIC Arbiter configuration registers.
* @lock: lock to synchronize accesses.
@ -140,6 +144,7 @@ struct apid_data {
struct spmi_pmic_arb {
void __iomem *rd_base;
void __iomem *wr_base;
phys_addr_t wr_base_phys;
void __iomem *intr;
void __iomem *cnfg;
void __iomem *core;
@ -179,6 +184,9 @@ struct spmi_pmic_arb {
* @irq_clear: on v1 address of PMIC_ARB_SPMI_PIC_IRQ_CLEARn
* on v2 address of SPMI_PIC_IRQ_CLEARn.
* @apid_map_offset: offset of PMIC_ARB_REG_CHNLn
* @wr_addr_map: maps from an SPMI address to the physical address
* range of the registers used to perform an SPMI write
* command to the SPMI address.
*/
struct pmic_arb_ver_ops {
const char *ver_str;
@ -195,6 +203,8 @@ struct pmic_arb_ver_ops {
void __iomem *(*irq_status)(struct spmi_pmic_arb *pmic_arb, u16 n);
void __iomem *(*irq_clear)(struct spmi_pmic_arb *pmic_arb, u16 n);
u32 (*apid_map_offset)(u16 n);
int (*wr_addr_map)(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
struct resource *res_out);
};
static inline void pmic_arb_base_write(struct spmi_pmic_arb *pmic_arb,
@ -861,6 +871,21 @@ static int pmic_arb_offset_v1(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
return 0x800 + 0x80 * pmic_arb->channel;
}
static int pmic_arb_wr_addr_map_v1(struct spmi_pmic_arb *pmic_arb, u8 sid,
u16 addr, struct resource *res_out)
{
int rc;
rc = pmic_arb_offset_v1(pmic_arb, sid, addr, PMIC_ARB_CHANNEL_RW);
if (rc < 0)
return rc;
res_out->start = pmic_arb->wr_base_phys + rc;
res_out->end = res_out->start + 0x80 - 1;
return 0;
}
static u16 pmic_arb_find_apid(struct spmi_pmic_arb *pmic_arb, u16 ppid)
{
struct apid_data *apidd = &pmic_arb->apid_data[pmic_arb->last_apid];
@ -1000,6 +1025,21 @@ static int pmic_arb_offset_v2(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
return 0x1000 * pmic_arb->ee + 0x8000 * apid;
}
static int pmic_arb_wr_addr_map_v2(struct spmi_pmic_arb *pmic_arb, u8 sid,
u16 addr, struct resource *res_out)
{
int rc;
rc = pmic_arb_offset_v2(pmic_arb, sid, addr, PMIC_ARB_CHANNEL_RW);
if (rc < 0)
return rc;
res_out->start = pmic_arb->wr_base_phys + rc;
res_out->end = res_out->start + 0x1000 - 1;
return 0;
}
/*
* v5 offset per ee and per apid for observer channels and per apid for
* read/write channels.
@ -1034,6 +1074,21 @@ static int pmic_arb_offset_v5(struct spmi_pmic_arb *pmic_arb, u8 sid, u16 addr,
return offset;
}
static int pmic_arb_wr_addr_map_v5(struct spmi_pmic_arb *pmic_arb, u8 sid,
u16 addr, struct resource *res_out)
{
int rc;
rc = pmic_arb_offset_v5(pmic_arb, sid, addr, PMIC_ARB_CHANNEL_RW);
if (rc < 0)
return rc;
res_out->start = pmic_arb->wr_base_phys + rc;
res_out->end = res_out->start + 0x10000 - 1;
return 0;
}
static u32 pmic_arb_fmt_cmd_v1(u8 opc, u8 sid, u16 addr, u8 bc)
{
return (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7);
@ -1143,6 +1198,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v1 = {
.irq_status = pmic_arb_irq_status_v1,
.irq_clear = pmic_arb_irq_clear_v1,
.apid_map_offset = pmic_arb_apid_map_offset_v2,
.wr_addr_map = pmic_arb_wr_addr_map_v1,
};
static const struct pmic_arb_ver_ops pmic_arb_v2 = {
@ -1156,6 +1212,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v2 = {
.irq_status = pmic_arb_irq_status_v2,
.irq_clear = pmic_arb_irq_clear_v2,
.apid_map_offset = pmic_arb_apid_map_offset_v2,
.wr_addr_map = pmic_arb_wr_addr_map_v2,
};
static const struct pmic_arb_ver_ops pmic_arb_v3 = {
@ -1169,6 +1226,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v3 = {
.irq_status = pmic_arb_irq_status_v2,
.irq_clear = pmic_arb_irq_clear_v2,
.apid_map_offset = pmic_arb_apid_map_offset_v2,
.wr_addr_map = pmic_arb_wr_addr_map_v2,
};
static const struct pmic_arb_ver_ops pmic_arb_v5 = {
@ -1182,6 +1240,7 @@ static const struct pmic_arb_ver_ops pmic_arb_v5 = {
.irq_status = pmic_arb_irq_status_v5,
.irq_clear = pmic_arb_irq_clear_v5,
.apid_map_offset = pmic_arb_apid_map_offset_v5,
.wr_addr_map = pmic_arb_wr_addr_map_v5,
};
static const struct irq_domain_ops pmic_arb_irq_domain_ops = {
@ -1191,6 +1250,62 @@ static const struct irq_domain_ops pmic_arb_irq_domain_ops = {
.translate = qpnpint_irq_domain_translate,
};
/**
* spmi_pmic_arb_map_address() - returns physical addresses of registers used to
* write to the PMIC peripheral at spmi_address
* @dev: Consumer device pointer
* @spmi_address: 20-bit SPMI address of the form: 0xSPPPP
* where S = global PMIC SID and
* PPPP = SPMI address within the slave
* @res_out: Resource struct (allocated by the caller) in which
* physical addresses for the range are passed via start
* and end elements
*
* Returns: 0 on success or an errno on failure.
*/
int spmi_pmic_arb_map_address(const struct device *dev, u32 spmi_address,
struct resource *res_out)
{
struct device_node *ctrl_node;
struct platform_device *ctrl_pdev;
struct spmi_controller *ctrl;
struct spmi_pmic_arb *pmic_arb;
u32 sid, addr;
if (!dev || !dev->of_node || !res_out) {
pr_err("%s: Invalid pointer\n", __func__);
return -EINVAL;
}
ctrl_node = of_parse_phandle(dev->of_node, "qcom,pmic-arb", 0);
if (!ctrl_node) {
pr_err("%s: Could not find PMIC arbiter node via qcom,pmic-arb property\n",
__func__);
return -ENODEV;
}
ctrl_pdev = of_find_device_by_node(ctrl_node);
of_node_put(ctrl_node);
if (!ctrl_pdev)
return -EPROBE_DEFER;
ctrl = platform_get_drvdata(ctrl_pdev);
if (!ctrl)
return -EPROBE_DEFER;
pmic_arb = spmi_controller_get_drvdata(ctrl);
if (!pmic_arb) {
pr_err("Missing PMIC arbiter device\n");
return -ENODEV;
}
sid = (spmi_address >> 16) & 0xF;
addr = spmi_address & 0xFFFF;
return pmic_arb->ver_ops->wr_addr_map(pmic_arb, sid, addr, res_out);
}
EXPORT_SYMBOL(spmi_pmic_arb_map_address);
static int spmi_pmic_arb_probe(struct platform_device *pdev)
{
struct spmi_pmic_arb *pmic_arb;
@ -1230,6 +1345,7 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
if (hw_ver < PMIC_ARB_VERSION_V2_MIN) {
pmic_arb->ver_ops = &pmic_arb_v1;
pmic_arb->wr_base = core;
pmic_arb->wr_base_phys = res->start;
pmic_arb->rd_base = core;
} else {
pmic_arb->core = core;
@ -1256,6 +1372,7 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
err = PTR_ERR(pmic_arb->wr_base);
goto err_put_ctrl;
}
pmic_arb->wr_base_phys = res->start;
}
dev_info(&ctrl->dev, "PMIC arbiter version %s (0x%x)\n",
@ -1275,10 +1392,12 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
goto err_put_ctrl;
}
pmic_arb->irq = platform_get_irq_byname(pdev, "periph_irq");
if (pmic_arb->irq < 0) {
err = pmic_arb->irq;
goto err_put_ctrl;
if (of_find_property(pdev->dev.of_node, "interrupt-names", NULL)) {
pmic_arb->irq = platform_get_irq_byname(pdev, "periph_irq");
if (pmic_arb->irq < 0) {
err = pmic_arb->irq;
goto err_put_ctrl;
}
}
err = of_property_read_u32(pdev->dev.of_node, "qcom,channel", &channel);
@ -1338,17 +1457,22 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
}
}
dev_dbg(&pdev->dev, "adding irq domain\n");
pmic_arb->domain = irq_domain_add_tree(pdev->dev.of_node,
&pmic_arb_irq_domain_ops, pmic_arb);
if (!pmic_arb->domain) {
dev_err(&pdev->dev, "unable to create irq_domain\n");
err = -ENOMEM;
goto err_put_ctrl;
if (pmic_arb->irq > 0) {
dev_dbg(&pdev->dev, "adding irq domain\n");
pmic_arb->domain = irq_domain_add_tree(pdev->dev.of_node,
&pmic_arb_irq_domain_ops, pmic_arb);
if (!pmic_arb->domain) {
dev_err(&pdev->dev, "unable to create irq_domain\n");
err = -ENOMEM;
goto err_put_ctrl;
}
irq_set_chained_handler_and_data(pmic_arb->irq,
pmic_arb_chained_irq, pmic_arb);
} else {
dev_dbg(&pdev->dev, "not supporting PMIC interrupts\n");
}
irq_set_chained_handler_and_data(pmic_arb->irq, pmic_arb_chained_irq,
pmic_arb);
err = spmi_controller_add(ctrl);
if (err)
goto err_domain_remove;
@ -1356,8 +1480,10 @@ static int spmi_pmic_arb_probe(struct platform_device *pdev)
return 0;
err_domain_remove:
irq_set_chained_handler_and_data(pmic_arb->irq, NULL, NULL);
irq_domain_remove(pmic_arb->domain);
if (pmic_arb->irq > 0) {
irq_set_chained_handler_and_data(pmic_arb->irq, NULL, NULL);
irq_domain_remove(pmic_arb->domain);
}
err_put_ctrl:
spmi_controller_put(ctrl);
return err;
@ -1368,8 +1494,10 @@ static int spmi_pmic_arb_remove(struct platform_device *pdev)
struct spmi_controller *ctrl = platform_get_drvdata(pdev);
struct spmi_pmic_arb *pmic_arb = spmi_controller_get_drvdata(ctrl);
spmi_controller_remove(ctrl);
irq_set_chained_handler_and_data(pmic_arb->irq, NULL, NULL);
irq_domain_remove(pmic_arb->domain);
if (pmic_arb->irq > 0) {
irq_set_chained_handler_and_data(pmic_arb->irq, NULL, NULL);
irq_domain_remove(pmic_arb->domain);
}
spmi_controller_put(ctrl);
return 0;
}

View file

@ -0,0 +1,22 @@
/* SPDX-License-Identifier: GPL-2.0-only */
/* Copyright (c) 2020, The Linux Foundation. All rights reserved. */
#ifndef _SPMI_PMIC_ARB_H
#define _SPMI_PMIC_ARB_H
#include <linux/device.h>
#include <linux/ioport.h>
#include <linux/types.h>
#if IS_ENABLED(CONFIG_SPMI_MSM_PMIC_ARB)
int spmi_pmic_arb_map_address(const struct device *dev, u32 spmi_address,
struct resource *res_out);
#else
static inline int spmi_pmic_arb_map_address(const struct device *dev,
u32 spmi_address, struct resource *res_out)
{
return -ENODEV;
}
#endif
#endif