145fcac1aSArnd Bergmann /* Copyright (c) 2009-2013, The Linux Foundation. All rights reserved. 245fcac1aSArnd Bergmann * Copyright (c) 2010, Google Inc. 345fcac1aSArnd Bergmann * 445fcac1aSArnd Bergmann * Original authors: Code Aurora Forum 545fcac1aSArnd Bergmann * 645fcac1aSArnd Bergmann * Author: Dima Zavin <dima@android.com> 745fcac1aSArnd Bergmann * - Largely rewritten from original to not be an i2c driver. 845fcac1aSArnd Bergmann * 945fcac1aSArnd Bergmann * This program is free software; you can redistribute it and/or modify 1045fcac1aSArnd Bergmann * it under the terms of the GNU General Public License version 2 and 1145fcac1aSArnd Bergmann * only version 2 as published by the Free Software Foundation. 1245fcac1aSArnd Bergmann * 1345fcac1aSArnd Bergmann * This program is distributed in the hope that it will be useful, 1445fcac1aSArnd Bergmann * but WITHOUT ANY WARRANTY; without even the implied warranty of 1545fcac1aSArnd Bergmann * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1645fcac1aSArnd Bergmann * GNU General Public License for more details. 1745fcac1aSArnd Bergmann */ 1845fcac1aSArnd Bergmann 1945fcac1aSArnd Bergmann #define pr_fmt(fmt) "%s: " fmt, __func__ 2045fcac1aSArnd Bergmann 2145fcac1aSArnd Bergmann #include <linux/delay.h> 2245fcac1aSArnd Bergmann #include <linux/err.h> 2345fcac1aSArnd Bergmann #include <linux/io.h> 2445fcac1aSArnd Bergmann #include <linux/kernel.h> 2545fcac1aSArnd Bergmann #include <linux/platform_device.h> 2645fcac1aSArnd Bergmann #include <linux/slab.h> 2745fcac1aSArnd Bergmann #include <linux/ssbi.h> 2845fcac1aSArnd Bergmann #include <linux/module.h> 2945fcac1aSArnd Bergmann #include <linux/of.h> 3045fcac1aSArnd Bergmann #include <linux/of_device.h> 3145fcac1aSArnd Bergmann 3245fcac1aSArnd Bergmann /* SSBI 2.0 controller registers */ 3345fcac1aSArnd Bergmann #define SSBI2_CMD 0x0008 3445fcac1aSArnd Bergmann #define SSBI2_RD 0x0010 3545fcac1aSArnd Bergmann #define SSBI2_STATUS 0x0014 3645fcac1aSArnd Bergmann #define SSBI2_MODE2 0x001C 3745fcac1aSArnd Bergmann 3845fcac1aSArnd Bergmann /* SSBI_CMD fields */ 3945fcac1aSArnd Bergmann #define SSBI_CMD_RDWRN (1 << 24) 4045fcac1aSArnd Bergmann 4145fcac1aSArnd Bergmann /* SSBI_STATUS fields */ 4245fcac1aSArnd Bergmann #define SSBI_STATUS_RD_READY (1 << 2) 4345fcac1aSArnd Bergmann #define SSBI_STATUS_READY (1 << 1) 4445fcac1aSArnd Bergmann #define SSBI_STATUS_MCHN_BUSY (1 << 0) 4545fcac1aSArnd Bergmann 4645fcac1aSArnd Bergmann /* SSBI_MODE2 fields */ 4745fcac1aSArnd Bergmann #define SSBI_MODE2_REG_ADDR_15_8_SHFT 0x04 4845fcac1aSArnd Bergmann #define SSBI_MODE2_REG_ADDR_15_8_MASK (0x7f << SSBI_MODE2_REG_ADDR_15_8_SHFT) 4945fcac1aSArnd Bergmann 5045fcac1aSArnd Bergmann #define SET_SSBI_MODE2_REG_ADDR_15_8(MD, AD) \ 5145fcac1aSArnd Bergmann (((MD) & 0x0F) | ((((AD) >> 8) << SSBI_MODE2_REG_ADDR_15_8_SHFT) & \ 5245fcac1aSArnd Bergmann SSBI_MODE2_REG_ADDR_15_8_MASK)) 5345fcac1aSArnd Bergmann 5445fcac1aSArnd Bergmann /* SSBI PMIC Arbiter command registers */ 5545fcac1aSArnd Bergmann #define SSBI_PA_CMD 0x0000 5645fcac1aSArnd Bergmann #define SSBI_PA_RD_STATUS 0x0004 5745fcac1aSArnd Bergmann 5845fcac1aSArnd Bergmann /* SSBI_PA_CMD fields */ 5945fcac1aSArnd Bergmann #define SSBI_PA_CMD_RDWRN (1 << 24) 6045fcac1aSArnd Bergmann #define SSBI_PA_CMD_ADDR_MASK 0x7fff /* REG_ADDR_7_0, REG_ADDR_8_14*/ 6145fcac1aSArnd Bergmann 6245fcac1aSArnd Bergmann /* SSBI_PA_RD_STATUS fields */ 6345fcac1aSArnd Bergmann #define SSBI_PA_RD_STATUS_TRANS_DONE (1 << 27) 6445fcac1aSArnd Bergmann #define SSBI_PA_RD_STATUS_TRANS_DENIED (1 << 26) 6545fcac1aSArnd Bergmann 6645fcac1aSArnd Bergmann #define SSBI_TIMEOUT_US 100 6745fcac1aSArnd Bergmann 68bae911a0SStephen Boyd enum ssbi_controller_type { 69bae911a0SStephen Boyd MSM_SBI_CTRL_SSBI = 0, 70bae911a0SStephen Boyd MSM_SBI_CTRL_SSBI2, 71bae911a0SStephen Boyd MSM_SBI_CTRL_PMIC_ARBITER, 72bae911a0SStephen Boyd }; 73bae911a0SStephen Boyd 7445fcac1aSArnd Bergmann struct ssbi { 7545fcac1aSArnd Bergmann struct device *slave; 7645fcac1aSArnd Bergmann void __iomem *base; 7745fcac1aSArnd Bergmann spinlock_t lock; 7845fcac1aSArnd Bergmann enum ssbi_controller_type controller_type; 7945fcac1aSArnd Bergmann int (*read)(struct ssbi *, u16 addr, u8 *buf, int len); 805eec14ccSStephen Boyd int (*write)(struct ssbi *, u16 addr, const u8 *buf, int len); 8145fcac1aSArnd Bergmann }; 8245fcac1aSArnd Bergmann 8345fcac1aSArnd Bergmann #define to_ssbi(dev) platform_get_drvdata(to_platform_device(dev)) 8445fcac1aSArnd Bergmann 8545fcac1aSArnd Bergmann static inline u32 ssbi_readl(struct ssbi *ssbi, u32 reg) 8645fcac1aSArnd Bergmann { 8745fcac1aSArnd Bergmann return readl(ssbi->base + reg); 8845fcac1aSArnd Bergmann } 8945fcac1aSArnd Bergmann 9045fcac1aSArnd Bergmann static inline void ssbi_writel(struct ssbi *ssbi, u32 val, u32 reg) 9145fcac1aSArnd Bergmann { 9245fcac1aSArnd Bergmann writel(val, ssbi->base + reg); 9345fcac1aSArnd Bergmann } 9445fcac1aSArnd Bergmann 9545fcac1aSArnd Bergmann /* 9645fcac1aSArnd Bergmann * Via private exchange with one of the original authors, the hardware 9745fcac1aSArnd Bergmann * should generally finish a transaction in about 5us. The worst 9845fcac1aSArnd Bergmann * case, is when using the arbiter and both other CPUs have just 9945fcac1aSArnd Bergmann * started trying to use the SSBI bus will result in a time of about 10045fcac1aSArnd Bergmann * 20us. It should never take longer than this. 10145fcac1aSArnd Bergmann * 10245fcac1aSArnd Bergmann * As such, this wait merely spins, with a udelay. 10345fcac1aSArnd Bergmann */ 10445fcac1aSArnd Bergmann static int ssbi_wait_mask(struct ssbi *ssbi, u32 set_mask, u32 clr_mask) 10545fcac1aSArnd Bergmann { 10645fcac1aSArnd Bergmann u32 timeout = SSBI_TIMEOUT_US; 10745fcac1aSArnd Bergmann u32 val; 10845fcac1aSArnd Bergmann 10945fcac1aSArnd Bergmann while (timeout--) { 11045fcac1aSArnd Bergmann val = ssbi_readl(ssbi, SSBI2_STATUS); 11145fcac1aSArnd Bergmann if (((val & set_mask) == set_mask) && ((val & clr_mask) == 0)) 11245fcac1aSArnd Bergmann return 0; 11345fcac1aSArnd Bergmann udelay(1); 11445fcac1aSArnd Bergmann } 11545fcac1aSArnd Bergmann 11645fcac1aSArnd Bergmann return -ETIMEDOUT; 11745fcac1aSArnd Bergmann } 11845fcac1aSArnd Bergmann 11945fcac1aSArnd Bergmann static int 12045fcac1aSArnd Bergmann ssbi_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) 12145fcac1aSArnd Bergmann { 12245fcac1aSArnd Bergmann u32 cmd = SSBI_CMD_RDWRN | ((addr & 0xff) << 16); 12345fcac1aSArnd Bergmann int ret = 0; 12445fcac1aSArnd Bergmann 12545fcac1aSArnd Bergmann if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { 12645fcac1aSArnd Bergmann u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); 12745fcac1aSArnd Bergmann mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); 12845fcac1aSArnd Bergmann ssbi_writel(ssbi, mode2, SSBI2_MODE2); 12945fcac1aSArnd Bergmann } 13045fcac1aSArnd Bergmann 13145fcac1aSArnd Bergmann while (len) { 13245fcac1aSArnd Bergmann ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); 13345fcac1aSArnd Bergmann if (ret) 13445fcac1aSArnd Bergmann goto err; 13545fcac1aSArnd Bergmann 13645fcac1aSArnd Bergmann ssbi_writel(ssbi, cmd, SSBI2_CMD); 13745fcac1aSArnd Bergmann ret = ssbi_wait_mask(ssbi, SSBI_STATUS_RD_READY, 0); 13845fcac1aSArnd Bergmann if (ret) 13945fcac1aSArnd Bergmann goto err; 14045fcac1aSArnd Bergmann *buf++ = ssbi_readl(ssbi, SSBI2_RD) & 0xff; 14145fcac1aSArnd Bergmann len--; 14245fcac1aSArnd Bergmann } 14345fcac1aSArnd Bergmann 14445fcac1aSArnd Bergmann err: 14545fcac1aSArnd Bergmann return ret; 14645fcac1aSArnd Bergmann } 14745fcac1aSArnd Bergmann 14845fcac1aSArnd Bergmann static int 1495eec14ccSStephen Boyd ssbi_write_bytes(struct ssbi *ssbi, u16 addr, const u8 *buf, int len) 15045fcac1aSArnd Bergmann { 15145fcac1aSArnd Bergmann int ret = 0; 15245fcac1aSArnd Bergmann 15345fcac1aSArnd Bergmann if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { 15445fcac1aSArnd Bergmann u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); 15545fcac1aSArnd Bergmann mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); 15645fcac1aSArnd Bergmann ssbi_writel(ssbi, mode2, SSBI2_MODE2); 15745fcac1aSArnd Bergmann } 15845fcac1aSArnd Bergmann 15945fcac1aSArnd Bergmann while (len) { 16045fcac1aSArnd Bergmann ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); 16145fcac1aSArnd Bergmann if (ret) 16245fcac1aSArnd Bergmann goto err; 16345fcac1aSArnd Bergmann 16445fcac1aSArnd Bergmann ssbi_writel(ssbi, ((addr & 0xff) << 16) | *buf, SSBI2_CMD); 16545fcac1aSArnd Bergmann ret = ssbi_wait_mask(ssbi, 0, SSBI_STATUS_MCHN_BUSY); 16645fcac1aSArnd Bergmann if (ret) 16745fcac1aSArnd Bergmann goto err; 16845fcac1aSArnd Bergmann buf++; 16945fcac1aSArnd Bergmann len--; 17045fcac1aSArnd Bergmann } 17145fcac1aSArnd Bergmann 17245fcac1aSArnd Bergmann err: 17345fcac1aSArnd Bergmann return ret; 17445fcac1aSArnd Bergmann } 17545fcac1aSArnd Bergmann 17645fcac1aSArnd Bergmann /* 17745fcac1aSArnd Bergmann * See ssbi_wait_mask for an explanation of the time and the 17845fcac1aSArnd Bergmann * busywait. 17945fcac1aSArnd Bergmann */ 18045fcac1aSArnd Bergmann static inline int 18145fcac1aSArnd Bergmann ssbi_pa_transfer(struct ssbi *ssbi, u32 cmd, u8 *data) 18245fcac1aSArnd Bergmann { 18345fcac1aSArnd Bergmann u32 timeout = SSBI_TIMEOUT_US; 18445fcac1aSArnd Bergmann u32 rd_status = 0; 18545fcac1aSArnd Bergmann 18645fcac1aSArnd Bergmann ssbi_writel(ssbi, cmd, SSBI_PA_CMD); 18745fcac1aSArnd Bergmann 18845fcac1aSArnd Bergmann while (timeout--) { 18945fcac1aSArnd Bergmann rd_status = ssbi_readl(ssbi, SSBI_PA_RD_STATUS); 19045fcac1aSArnd Bergmann 19145fcac1aSArnd Bergmann if (rd_status & SSBI_PA_RD_STATUS_TRANS_DENIED) 19245fcac1aSArnd Bergmann return -EPERM; 19345fcac1aSArnd Bergmann 19445fcac1aSArnd Bergmann if (rd_status & SSBI_PA_RD_STATUS_TRANS_DONE) { 19545fcac1aSArnd Bergmann if (data) 19645fcac1aSArnd Bergmann *data = rd_status & 0xff; 19745fcac1aSArnd Bergmann return 0; 19845fcac1aSArnd Bergmann } 19945fcac1aSArnd Bergmann udelay(1); 20045fcac1aSArnd Bergmann } 20145fcac1aSArnd Bergmann 20245fcac1aSArnd Bergmann return -ETIMEDOUT; 20345fcac1aSArnd Bergmann } 20445fcac1aSArnd Bergmann 20545fcac1aSArnd Bergmann static int 20645fcac1aSArnd Bergmann ssbi_pa_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) 20745fcac1aSArnd Bergmann { 20845fcac1aSArnd Bergmann u32 cmd; 20945fcac1aSArnd Bergmann int ret = 0; 21045fcac1aSArnd Bergmann 21145fcac1aSArnd Bergmann cmd = SSBI_PA_CMD_RDWRN | (addr & SSBI_PA_CMD_ADDR_MASK) << 8; 21245fcac1aSArnd Bergmann 21345fcac1aSArnd Bergmann while (len) { 21445fcac1aSArnd Bergmann ret = ssbi_pa_transfer(ssbi, cmd, buf); 21545fcac1aSArnd Bergmann if (ret) 21645fcac1aSArnd Bergmann goto err; 21745fcac1aSArnd Bergmann buf++; 21845fcac1aSArnd Bergmann len--; 21945fcac1aSArnd Bergmann } 22045fcac1aSArnd Bergmann 22145fcac1aSArnd Bergmann err: 22245fcac1aSArnd Bergmann return ret; 22345fcac1aSArnd Bergmann } 22445fcac1aSArnd Bergmann 22545fcac1aSArnd Bergmann static int 2265eec14ccSStephen Boyd ssbi_pa_write_bytes(struct ssbi *ssbi, u16 addr, const u8 *buf, int len) 22745fcac1aSArnd Bergmann { 22845fcac1aSArnd Bergmann u32 cmd; 22945fcac1aSArnd Bergmann int ret = 0; 23045fcac1aSArnd Bergmann 23145fcac1aSArnd Bergmann while (len) { 23245fcac1aSArnd Bergmann cmd = (addr & SSBI_PA_CMD_ADDR_MASK) << 8 | *buf; 23345fcac1aSArnd Bergmann ret = ssbi_pa_transfer(ssbi, cmd, NULL); 23445fcac1aSArnd Bergmann if (ret) 23545fcac1aSArnd Bergmann goto err; 23645fcac1aSArnd Bergmann buf++; 23745fcac1aSArnd Bergmann len--; 23845fcac1aSArnd Bergmann } 23945fcac1aSArnd Bergmann 24045fcac1aSArnd Bergmann err: 24145fcac1aSArnd Bergmann return ret; 24245fcac1aSArnd Bergmann } 24345fcac1aSArnd Bergmann 24445fcac1aSArnd Bergmann int ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) 24545fcac1aSArnd Bergmann { 24645fcac1aSArnd Bergmann struct ssbi *ssbi = to_ssbi(dev); 24745fcac1aSArnd Bergmann unsigned long flags; 24845fcac1aSArnd Bergmann int ret; 24945fcac1aSArnd Bergmann 25045fcac1aSArnd Bergmann spin_lock_irqsave(&ssbi->lock, flags); 25145fcac1aSArnd Bergmann ret = ssbi->read(ssbi, addr, buf, len); 25245fcac1aSArnd Bergmann spin_unlock_irqrestore(&ssbi->lock, flags); 25345fcac1aSArnd Bergmann 25445fcac1aSArnd Bergmann return ret; 25545fcac1aSArnd Bergmann } 25645fcac1aSArnd Bergmann EXPORT_SYMBOL_GPL(ssbi_read); 25745fcac1aSArnd Bergmann 2585eec14ccSStephen Boyd int ssbi_write(struct device *dev, u16 addr, const u8 *buf, int len) 25945fcac1aSArnd Bergmann { 26045fcac1aSArnd Bergmann struct ssbi *ssbi = to_ssbi(dev); 26145fcac1aSArnd Bergmann unsigned long flags; 26245fcac1aSArnd Bergmann int ret; 26345fcac1aSArnd Bergmann 26445fcac1aSArnd Bergmann spin_lock_irqsave(&ssbi->lock, flags); 26545fcac1aSArnd Bergmann ret = ssbi->write(ssbi, addr, buf, len); 26645fcac1aSArnd Bergmann spin_unlock_irqrestore(&ssbi->lock, flags); 26745fcac1aSArnd Bergmann 26845fcac1aSArnd Bergmann return ret; 26945fcac1aSArnd Bergmann } 27045fcac1aSArnd Bergmann EXPORT_SYMBOL_GPL(ssbi_write); 27145fcac1aSArnd Bergmann 27245fcac1aSArnd Bergmann static int ssbi_probe(struct platform_device *pdev) 27345fcac1aSArnd Bergmann { 27445fcac1aSArnd Bergmann struct device_node *np = pdev->dev.of_node; 27545fcac1aSArnd Bergmann struct resource *mem_res; 27645fcac1aSArnd Bergmann struct ssbi *ssbi; 27745fcac1aSArnd Bergmann const char *type; 27845fcac1aSArnd Bergmann 279e5784388SStephen Boyd ssbi = devm_kzalloc(&pdev->dev, sizeof(*ssbi), GFP_KERNEL); 280e5784388SStephen Boyd if (!ssbi) 28145fcac1aSArnd Bergmann return -ENOMEM; 28245fcac1aSArnd Bergmann 28345fcac1aSArnd Bergmann mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 284e5784388SStephen Boyd ssbi->base = devm_ioremap_resource(&pdev->dev, mem_res); 285e5784388SStephen Boyd if (IS_ERR(ssbi->base)) 286e5784388SStephen Boyd return PTR_ERR(ssbi->base); 28745fcac1aSArnd Bergmann 28845fcac1aSArnd Bergmann platform_set_drvdata(pdev, ssbi); 28945fcac1aSArnd Bergmann 29045fcac1aSArnd Bergmann type = of_get_property(np, "qcom,controller-type", NULL); 29145fcac1aSArnd Bergmann if (type == NULL) { 292e5784388SStephen Boyd dev_err(&pdev->dev, "Missing qcom,controller-type property\n"); 293e5784388SStephen Boyd return -EINVAL; 29445fcac1aSArnd Bergmann } 29545fcac1aSArnd Bergmann dev_info(&pdev->dev, "SSBI controller type: '%s'\n", type); 29645fcac1aSArnd Bergmann if (strcmp(type, "ssbi") == 0) 29745fcac1aSArnd Bergmann ssbi->controller_type = MSM_SBI_CTRL_SSBI; 29845fcac1aSArnd Bergmann else if (strcmp(type, "ssbi2") == 0) 29945fcac1aSArnd Bergmann ssbi->controller_type = MSM_SBI_CTRL_SSBI2; 30045fcac1aSArnd Bergmann else if (strcmp(type, "pmic-arbiter") == 0) 30145fcac1aSArnd Bergmann ssbi->controller_type = MSM_SBI_CTRL_PMIC_ARBITER; 30245fcac1aSArnd Bergmann else { 303e5784388SStephen Boyd dev_err(&pdev->dev, "Unknown qcom,controller-type\n"); 304e5784388SStephen Boyd return -EINVAL; 30545fcac1aSArnd Bergmann } 30645fcac1aSArnd Bergmann 30745fcac1aSArnd Bergmann if (ssbi->controller_type == MSM_SBI_CTRL_PMIC_ARBITER) { 30845fcac1aSArnd Bergmann ssbi->read = ssbi_pa_read_bytes; 30945fcac1aSArnd Bergmann ssbi->write = ssbi_pa_write_bytes; 31045fcac1aSArnd Bergmann } else { 31145fcac1aSArnd Bergmann ssbi->read = ssbi_read_bytes; 31245fcac1aSArnd Bergmann ssbi->write = ssbi_write_bytes; 31345fcac1aSArnd Bergmann } 31445fcac1aSArnd Bergmann 31545fcac1aSArnd Bergmann spin_lock_init(&ssbi->lock); 31645fcac1aSArnd Bergmann 317e5784388SStephen Boyd return of_platform_populate(np, NULL, NULL, &pdev->dev); 31845fcac1aSArnd Bergmann } 31945fcac1aSArnd Bergmann 32045fcac1aSArnd Bergmann static struct of_device_id ssbi_match_table[] = { 32145fcac1aSArnd Bergmann { .compatible = "qcom,ssbi" }, 32245fcac1aSArnd Bergmann {} 32345fcac1aSArnd Bergmann }; 3246378c1e5SStephen Boyd MODULE_DEVICE_TABLE(of, ssbi_match_table); 32545fcac1aSArnd Bergmann 32645fcac1aSArnd Bergmann static struct platform_driver ssbi_driver = { 32745fcac1aSArnd Bergmann .probe = ssbi_probe, 32845fcac1aSArnd Bergmann .driver = { 32945fcac1aSArnd Bergmann .name = "ssbi", 33045fcac1aSArnd Bergmann .owner = THIS_MODULE, 33145fcac1aSArnd Bergmann .of_match_table = ssbi_match_table, 33245fcac1aSArnd Bergmann }, 33345fcac1aSArnd Bergmann }; 334e5784388SStephen Boyd module_platform_driver(ssbi_driver); 33545fcac1aSArnd Bergmann 33645fcac1aSArnd Bergmann MODULE_LICENSE("GPL v2"); 33745fcac1aSArnd Bergmann MODULE_VERSION("1.0"); 33845fcac1aSArnd Bergmann MODULE_ALIAS("platform:ssbi"); 33945fcac1aSArnd Bergmann MODULE_AUTHOR("Dima Zavin <dima@android.com>"); 340