X-Git-Url: https://git.archive.openwrt.org/?a=blobdiff_plain;f=target%2Flinux%2Fipq806x%2Fpatches%2F0056-spmi-Add-MSM-PMIC-Arbiter-SPMI-controller.patch;fp=target%2Flinux%2Fipq806x%2Fpatches%2F0056-spmi-Add-MSM-PMIC-Arbiter-SPMI-controller.patch;h=24f841e5899eeb8b4a7b23d2b1060310775b5e7a;hb=5516d46d9dc57e5f6c5002a061ea2f53c13c6375;hp=0000000000000000000000000000000000000000;hpb=a38327ac0f1ba85a1c35c39f1c91de0287ec8b2e;p=openwrt.git diff --git a/target/linux/ipq806x/patches/0056-spmi-Add-MSM-PMIC-Arbiter-SPMI-controller.patch b/target/linux/ipq806x/patches/0056-spmi-Add-MSM-PMIC-Arbiter-SPMI-controller.patch new file mode 100644 index 0000000000..24f841e589 --- /dev/null +++ b/target/linux/ipq806x/patches/0056-spmi-Add-MSM-PMIC-Arbiter-SPMI-controller.patch @@ -0,0 +1,477 @@ +From 1e1f53fbc2848b23af572c16d19e8004f6a7c9c1 Mon Sep 17 00:00:00 2001 +From: Kenneth Heitke +Date: Wed, 12 Feb 2014 13:44:24 -0600 +Subject: [PATCH 056/182] spmi: Add MSM PMIC Arbiter SPMI controller + +Qualcomm's PMIC Arbiter SPMI controller functions as a bus master and +is used to communication with one or more PMIC (slave) devices on the +SPMI bus. The PMIC Arbiter is actually a hardware wrapper around the +SPMI controller that provides concurrent and autonomous PMIC access +to various entities that need to communicate with the PMIC. + +The SPMI controller hardware handles all of the SPMI bus activity (bus +arbitration, sequence start condition, transmission of frames, etc). +This software driver uses the PMIC Arbiter register interface to +initiate command sequences on the SPMI bus. The status register is +read to determine when the command sequence has completed and whether +or not it completed successfully. + +Signed-off-by: Kenneth Heitke +Signed-off-by: Josh Cartwright +Signed-off-by: Greg Kroah-Hartman +--- + drivers/spmi/Kconfig | 17 ++ + drivers/spmi/Makefile | 2 + + drivers/spmi/spmi-pmic-arb.c | 405 ++++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 424 insertions(+) + create mode 100644 drivers/spmi/spmi-pmic-arb.c + +diff --git a/drivers/spmi/Kconfig b/drivers/spmi/Kconfig +index 1dbfee0..80b7901 100644 +--- a/drivers/spmi/Kconfig ++++ b/drivers/spmi/Kconfig +@@ -7,3 +7,20 @@ menuconfig SPMI + SPMI (System Power Management Interface) is a two-wire + serial interface between baseband and application processors + and Power Management Integrated Circuits (PMIC). ++ ++if SPMI ++ ++config SPMI_MSM_PMIC_ARB ++ tristate "Qualcomm MSM SPMI Controller (PMIC Arbiter)" ++ depends on ARM ++ depends on ARCH_MSM || COMPILE_TEST ++ default ARCH_MSM ++ help ++ If you say yes to this option, support will be included for the ++ built-in SPMI PMIC Arbiter interface on Qualcomm MSM family ++ processors. ++ ++ This is required for communicating with Qualcomm PMICs and ++ other devices that have the SPMI interface. ++ ++endif +diff --git a/drivers/spmi/Makefile b/drivers/spmi/Makefile +index 1de1acd..fc75104 100644 +--- a/drivers/spmi/Makefile ++++ b/drivers/spmi/Makefile +@@ -2,3 +2,5 @@ + # Makefile for kernel SPMI framework. + # + obj-$(CONFIG_SPMI) += spmi.o ++ ++obj-$(CONFIG_SPMI_MSM_PMIC_ARB) += spmi-pmic-arb.o +diff --git a/drivers/spmi/spmi-pmic-arb.c b/drivers/spmi/spmi-pmic-arb.c +new file mode 100644 +index 0000000..2dd27e8 +--- /dev/null ++++ b/drivers/spmi/spmi-pmic-arb.c +@@ -0,0 +1,405 @@ ++/* Copyright (c) 2012-2013, 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 ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++#include ++ ++/* PMIC Arbiter configuration registers */ ++#define PMIC_ARB_VERSION 0x0000 ++#define PMIC_ARB_INT_EN 0x0004 ++ ++/* PMIC Arbiter channel registers */ ++#define PMIC_ARB_CMD(N) (0x0800 + (0x80 * (N))) ++#define PMIC_ARB_CONFIG(N) (0x0804 + (0x80 * (N))) ++#define PMIC_ARB_STATUS(N) (0x0808 + (0x80 * (N))) ++#define PMIC_ARB_WDATA0(N) (0x0810 + (0x80 * (N))) ++#define PMIC_ARB_WDATA1(N) (0x0814 + (0x80 * (N))) ++#define PMIC_ARB_RDATA0(N) (0x0818 + (0x80 * (N))) ++#define PMIC_ARB_RDATA1(N) (0x081C + (0x80 * (N))) ++ ++/* Interrupt Controller */ ++#define SPMI_PIC_OWNER_ACC_STATUS(M, N) (0x0000 + ((32 * (M)) + (4 * (N)))) ++#define SPMI_PIC_ACC_ENABLE(N) (0x0200 + (4 * (N))) ++#define SPMI_PIC_IRQ_STATUS(N) (0x0600 + (4 * (N))) ++#define SPMI_PIC_IRQ_CLEAR(N) (0x0A00 + (4 * (N))) ++ ++/* Mapping Table */ ++#define SPMI_MAPPING_TABLE_REG(N) (0x0B00 + (4 * (N))) ++#define SPMI_MAPPING_BIT_INDEX(X) (((X) >> 18) & 0xF) ++#define SPMI_MAPPING_BIT_IS_0_FLAG(X) (((X) >> 17) & 0x1) ++#define SPMI_MAPPING_BIT_IS_0_RESULT(X) (((X) >> 9) & 0xFF) ++#define SPMI_MAPPING_BIT_IS_1_FLAG(X) (((X) >> 8) & 0x1) ++#define SPMI_MAPPING_BIT_IS_1_RESULT(X) (((X) >> 0) & 0xFF) ++ ++#define SPMI_MAPPING_TABLE_LEN 255 ++#define SPMI_MAPPING_TABLE_TREE_DEPTH 16 /* Maximum of 16-bits */ ++ ++/* Ownership Table */ ++#define SPMI_OWNERSHIP_TABLE_REG(N) (0x0700 + (4 * (N))) ++#define SPMI_OWNERSHIP_PERIPH2OWNER(X) ((X) & 0x7) ++ ++/* Channel Status fields */ ++enum pmic_arb_chnl_status { ++ PMIC_ARB_STATUS_DONE = (1 << 0), ++ PMIC_ARB_STATUS_FAILURE = (1 << 1), ++ PMIC_ARB_STATUS_DENIED = (1 << 2), ++ PMIC_ARB_STATUS_DROPPED = (1 << 3), ++}; ++ ++/* Command register fields */ ++#define PMIC_ARB_CMD_MAX_BYTE_COUNT 8 ++ ++/* Command Opcodes */ ++enum pmic_arb_cmd_op_code { ++ PMIC_ARB_OP_EXT_WRITEL = 0, ++ PMIC_ARB_OP_EXT_READL = 1, ++ PMIC_ARB_OP_EXT_WRITE = 2, ++ PMIC_ARB_OP_RESET = 3, ++ PMIC_ARB_OP_SLEEP = 4, ++ PMIC_ARB_OP_SHUTDOWN = 5, ++ PMIC_ARB_OP_WAKEUP = 6, ++ PMIC_ARB_OP_AUTHENTICATE = 7, ++ PMIC_ARB_OP_MSTR_READ = 8, ++ PMIC_ARB_OP_MSTR_WRITE = 9, ++ PMIC_ARB_OP_EXT_READ = 13, ++ PMIC_ARB_OP_WRITE = 14, ++ PMIC_ARB_OP_READ = 15, ++ PMIC_ARB_OP_ZERO_WRITE = 16, ++}; ++ ++/* Maximum number of support PMIC peripherals */ ++#define PMIC_ARB_MAX_PERIPHS 256 ++#define PMIC_ARB_PERIPH_ID_VALID (1 << 15) ++#define PMIC_ARB_TIMEOUT_US 100 ++#define PMIC_ARB_MAX_TRANS_BYTES (8) ++ ++#define PMIC_ARB_APID_MASK 0xFF ++#define PMIC_ARB_PPID_MASK 0xFFF ++ ++/* interrupt enable bit */ ++#define SPMI_PIC_ACC_ENABLE_BIT BIT(0) ++ ++/** ++ * spmi_pmic_arb_dev - SPMI PMIC Arbiter object ++ * ++ * @base: address of the PMIC Arbiter core registers. ++ * @intr: address of the SPMI interrupt control registers. ++ * @cnfg: address of the PMIC Arbiter configuration registers. ++ * @lock: lock to synchronize accesses. ++ * @channel: which channel to use for accesses. ++ */ ++struct spmi_pmic_arb_dev { ++ void __iomem *base; ++ void __iomem *intr; ++ void __iomem *cnfg; ++ raw_spinlock_t lock; ++ u8 channel; ++}; ++ ++static inline u32 pmic_arb_base_read(struct spmi_pmic_arb_dev *dev, u32 offset) ++{ ++ return readl_relaxed(dev->base + offset); ++} ++ ++static inline void pmic_arb_base_write(struct spmi_pmic_arb_dev *dev, ++ u32 offset, u32 val) ++{ ++ writel_relaxed(val, dev->base + offset); ++} ++ ++/** ++ * pa_read_data: reads pmic-arb's register and copy 1..4 bytes to buf ++ * @bc: byte count -1. range: 0..3 ++ * @reg: register's address ++ * @buf: output parameter, length must be bc + 1 ++ */ ++static void pa_read_data(struct spmi_pmic_arb_dev *dev, u8 *buf, u32 reg, u8 bc) ++{ ++ u32 data = pmic_arb_base_read(dev, reg); ++ memcpy(buf, &data, (bc & 3) + 1); ++} ++ ++/** ++ * pa_write_data: write 1..4 bytes from buf to pmic-arb's register ++ * @bc: byte-count -1. range: 0..3. ++ * @reg: register's address. ++ * @buf: buffer to write. length must be bc + 1. ++ */ ++static void ++pa_write_data(struct spmi_pmic_arb_dev *dev, const u8 *buf, u32 reg, u8 bc) ++{ ++ u32 data = 0; ++ memcpy(&data, buf, (bc & 3) + 1); ++ pmic_arb_base_write(dev, reg, data); ++} ++ ++static int pmic_arb_wait_for_done(struct spmi_controller *ctrl) ++{ ++ struct spmi_pmic_arb_dev *dev = spmi_controller_get_drvdata(ctrl); ++ u32 status = 0; ++ u32 timeout = PMIC_ARB_TIMEOUT_US; ++ u32 offset = PMIC_ARB_STATUS(dev->channel); ++ ++ while (timeout--) { ++ status = pmic_arb_base_read(dev, offset); ++ ++ if (status & PMIC_ARB_STATUS_DONE) { ++ if (status & PMIC_ARB_STATUS_DENIED) { ++ dev_err(&ctrl->dev, ++ "%s: transaction denied (0x%x)\n", ++ __func__, status); ++ return -EPERM; ++ } ++ ++ if (status & PMIC_ARB_STATUS_FAILURE) { ++ dev_err(&ctrl->dev, ++ "%s: transaction failed (0x%x)\n", ++ __func__, status); ++ return -EIO; ++ } ++ ++ if (status & PMIC_ARB_STATUS_DROPPED) { ++ dev_err(&ctrl->dev, ++ "%s: transaction dropped (0x%x)\n", ++ __func__, status); ++ return -EIO; ++ } ++ ++ return 0; ++ } ++ udelay(1); ++ } ++ ++ dev_err(&ctrl->dev, ++ "%s: timeout, status 0x%x\n", ++ __func__, status); ++ return -ETIMEDOUT; ++} ++ ++/* Non-data command */ ++static int pmic_arb_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid) ++{ ++ struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl); ++ unsigned long flags; ++ u32 cmd; ++ int rc; ++ ++ /* Check for valid non-data command */ ++ if (opc < SPMI_CMD_RESET || opc > SPMI_CMD_WAKEUP) ++ return -EINVAL; ++ ++ cmd = ((opc | 0x40) << 27) | ((sid & 0xf) << 20); ++ ++ raw_spin_lock_irqsave(&pmic_arb->lock, flags); ++ pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); ++ rc = pmic_arb_wait_for_done(ctrl); ++ raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); ++ ++ return rc; ++} ++ ++static int pmic_arb_read_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, ++ u16 addr, u8 *buf, size_t len) ++{ ++ struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl); ++ unsigned long flags; ++ u8 bc = len - 1; ++ u32 cmd; ++ int rc; ++ ++ if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { ++ dev_err(&ctrl->dev, ++ "pmic-arb supports 1..%d bytes per trans, but %d requested", ++ PMIC_ARB_MAX_TRANS_BYTES, len); ++ return -EINVAL; ++ } ++ ++ /* Check the opcode */ ++ if (opc >= 0x60 && opc <= 0x7F) ++ opc = PMIC_ARB_OP_READ; ++ else if (opc >= 0x20 && opc <= 0x2F) ++ opc = PMIC_ARB_OP_EXT_READ; ++ else if (opc >= 0x38 && opc <= 0x3F) ++ opc = PMIC_ARB_OP_EXT_READL; ++ else ++ return -EINVAL; ++ ++ cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); ++ ++ raw_spin_lock_irqsave(&pmic_arb->lock, flags); ++ pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); ++ rc = pmic_arb_wait_for_done(ctrl); ++ if (rc) ++ goto done; ++ ++ pa_read_data(pmic_arb, buf, PMIC_ARB_RDATA0(pmic_arb->channel), ++ min_t(u8, bc, 3)); ++ ++ if (bc > 3) ++ pa_read_data(pmic_arb, buf + 4, ++ PMIC_ARB_RDATA1(pmic_arb->channel), bc - 4); ++ ++done: ++ raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); ++ return rc; ++} ++ ++static int pmic_arb_write_cmd(struct spmi_controller *ctrl, u8 opc, u8 sid, ++ u16 addr, const u8 *buf, size_t len) ++{ ++ struct spmi_pmic_arb_dev *pmic_arb = spmi_controller_get_drvdata(ctrl); ++ unsigned long flags; ++ u8 bc = len - 1; ++ u32 cmd; ++ int rc; ++ ++ if (bc >= PMIC_ARB_MAX_TRANS_BYTES) { ++ dev_err(&ctrl->dev, ++ "pmic-arb supports 1..%d bytes per trans, but:%d requested", ++ PMIC_ARB_MAX_TRANS_BYTES, len); ++ return -EINVAL; ++ } ++ ++ /* Check the opcode */ ++ if (opc >= 0x40 && opc <= 0x5F) ++ opc = PMIC_ARB_OP_WRITE; ++ else if (opc >= 0x00 && opc <= 0x0F) ++ opc = PMIC_ARB_OP_EXT_WRITE; ++ else if (opc >= 0x30 && opc <= 0x37) ++ opc = PMIC_ARB_OP_EXT_WRITEL; ++ else if (opc >= 0x80 && opc <= 0xFF) ++ opc = PMIC_ARB_OP_ZERO_WRITE; ++ else ++ return -EINVAL; ++ ++ cmd = (opc << 27) | ((sid & 0xf) << 20) | (addr << 4) | (bc & 0x7); ++ ++ /* Write data to FIFOs */ ++ raw_spin_lock_irqsave(&pmic_arb->lock, flags); ++ pa_write_data(pmic_arb, buf, PMIC_ARB_WDATA0(pmic_arb->channel) ++ , min_t(u8, bc, 3)); ++ if (bc > 3) ++ pa_write_data(pmic_arb, buf + 4, ++ PMIC_ARB_WDATA1(pmic_arb->channel), bc - 4); ++ ++ /* Start the transaction */ ++ pmic_arb_base_write(pmic_arb, PMIC_ARB_CMD(pmic_arb->channel), cmd); ++ rc = pmic_arb_wait_for_done(ctrl); ++ raw_spin_unlock_irqrestore(&pmic_arb->lock, flags); ++ ++ return rc; ++} ++ ++static int spmi_pmic_arb_probe(struct platform_device *pdev) ++{ ++ struct spmi_pmic_arb_dev *pa; ++ struct spmi_controller *ctrl; ++ struct resource *res; ++ u32 channel; ++ int err, i; ++ ++ ctrl = spmi_controller_alloc(&pdev->dev, sizeof(*pa)); ++ if (!ctrl) ++ return -ENOMEM; ++ ++ pa = spmi_controller_get_drvdata(ctrl); ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "core"); ++ pa->base = devm_ioremap_resource(&ctrl->dev, res); ++ if (IS_ERR(pa->base)) { ++ err = PTR_ERR(pa->base); ++ goto err_put_ctrl; ++ } ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "intr"); ++ pa->intr = devm_ioremap_resource(&ctrl->dev, res); ++ if (IS_ERR(pa->intr)) { ++ err = PTR_ERR(pa->intr); ++ goto err_put_ctrl; ++ } ++ ++ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "cnfg"); ++ pa->cnfg = devm_ioremap_resource(&ctrl->dev, res); ++ if (IS_ERR(pa->cnfg)) { ++ err = PTR_ERR(pa->cnfg); ++ goto err_put_ctrl; ++ } ++ ++ err = of_property_read_u32(pdev->dev.of_node, "qcom,channel", &channel); ++ if (err) { ++ dev_err(&pdev->dev, "channel unspecified.\n"); ++ goto err_put_ctrl; ++ } ++ ++ if (channel > 5) { ++ dev_err(&pdev->dev, "invalid channel (%u) specified.\n", ++ channel); ++ goto err_put_ctrl; ++ } ++ ++ pa->channel = channel; ++ ++ platform_set_drvdata(pdev, ctrl); ++ raw_spin_lock_init(&pa->lock); ++ ++ ctrl->cmd = pmic_arb_cmd; ++ ctrl->read_cmd = pmic_arb_read_cmd; ++ ctrl->write_cmd = pmic_arb_write_cmd; ++ ++ err = spmi_controller_add(ctrl); ++ if (err) ++ goto err_put_ctrl; ++ ++ dev_dbg(&ctrl->dev, "PMIC Arb Version 0x%x\n", ++ pmic_arb_base_read(pa, PMIC_ARB_VERSION)); ++ ++ return 0; ++ ++err_put_ctrl: ++ spmi_controller_put(ctrl); ++ return err; ++} ++ ++static int spmi_pmic_arb_remove(struct platform_device *pdev) ++{ ++ struct spmi_controller *ctrl = platform_get_drvdata(pdev); ++ spmi_controller_remove(ctrl); ++ spmi_controller_put(ctrl); ++ return 0; ++} ++ ++static const struct of_device_id spmi_pmic_arb_match_table[] = { ++ { .compatible = "qcom,spmi-pmic-arb", }, ++ {}, ++}; ++MODULE_DEVICE_TABLE(of, spmi_pmic_arb_match_table); ++ ++static struct platform_driver spmi_pmic_arb_driver = { ++ .probe = spmi_pmic_arb_probe, ++ .remove = spmi_pmic_arb_remove, ++ .driver = { ++ .name = "spmi_pmic_arb", ++ .owner = THIS_MODULE, ++ .of_match_table = spmi_pmic_arb_match_table, ++ }, ++}; ++module_platform_driver(spmi_pmic_arb_driver); ++ ++MODULE_LICENSE("GPL v2"); ++MODULE_ALIAS("platform:spmi_pmic_arb"); +-- +1.7.10.4 +