18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 28c2ecf20Sopenharmony_ci/* Copyright (c) 2009-2013, The Linux Foundation. All rights reserved. 38c2ecf20Sopenharmony_ci * Copyright (c) 2010, Google Inc. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Original authors: Code Aurora Forum 68c2ecf20Sopenharmony_ci * 78c2ecf20Sopenharmony_ci * Author: Dima Zavin <dima@android.com> 88c2ecf20Sopenharmony_ci * - Largely rewritten from original to not be an i2c driver. 98c2ecf20Sopenharmony_ci */ 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#define pr_fmt(fmt) "%s: " fmt, __func__ 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_ci#include <linux/delay.h> 148c2ecf20Sopenharmony_ci#include <linux/err.h> 158c2ecf20Sopenharmony_ci#include <linux/io.h> 168c2ecf20Sopenharmony_ci#include <linux/kernel.h> 178c2ecf20Sopenharmony_ci#include <linux/platform_device.h> 188c2ecf20Sopenharmony_ci#include <linux/slab.h> 198c2ecf20Sopenharmony_ci#include <linux/ssbi.h> 208c2ecf20Sopenharmony_ci#include <linux/module.h> 218c2ecf20Sopenharmony_ci#include <linux/of.h> 228c2ecf20Sopenharmony_ci#include <linux/of_device.h> 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_ci/* SSBI 2.0 controller registers */ 258c2ecf20Sopenharmony_ci#define SSBI2_CMD 0x0008 268c2ecf20Sopenharmony_ci#define SSBI2_RD 0x0010 278c2ecf20Sopenharmony_ci#define SSBI2_STATUS 0x0014 288c2ecf20Sopenharmony_ci#define SSBI2_MODE2 0x001C 298c2ecf20Sopenharmony_ci 308c2ecf20Sopenharmony_ci/* SSBI_CMD fields */ 318c2ecf20Sopenharmony_ci#define SSBI_CMD_RDWRN (1 << 24) 328c2ecf20Sopenharmony_ci 338c2ecf20Sopenharmony_ci/* SSBI_STATUS fields */ 348c2ecf20Sopenharmony_ci#define SSBI_STATUS_RD_READY (1 << 2) 358c2ecf20Sopenharmony_ci#define SSBI_STATUS_READY (1 << 1) 368c2ecf20Sopenharmony_ci#define SSBI_STATUS_MCHN_BUSY (1 << 0) 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci/* SSBI_MODE2 fields */ 398c2ecf20Sopenharmony_ci#define SSBI_MODE2_REG_ADDR_15_8_SHFT 0x04 408c2ecf20Sopenharmony_ci#define SSBI_MODE2_REG_ADDR_15_8_MASK (0x7f << SSBI_MODE2_REG_ADDR_15_8_SHFT) 418c2ecf20Sopenharmony_ci 428c2ecf20Sopenharmony_ci#define SET_SSBI_MODE2_REG_ADDR_15_8(MD, AD) \ 438c2ecf20Sopenharmony_ci (((MD) & 0x0F) | ((((AD) >> 8) << SSBI_MODE2_REG_ADDR_15_8_SHFT) & \ 448c2ecf20Sopenharmony_ci SSBI_MODE2_REG_ADDR_15_8_MASK)) 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci/* SSBI PMIC Arbiter command registers */ 478c2ecf20Sopenharmony_ci#define SSBI_PA_CMD 0x0000 488c2ecf20Sopenharmony_ci#define SSBI_PA_RD_STATUS 0x0004 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci/* SSBI_PA_CMD fields */ 518c2ecf20Sopenharmony_ci#define SSBI_PA_CMD_RDWRN (1 << 24) 528c2ecf20Sopenharmony_ci#define SSBI_PA_CMD_ADDR_MASK 0x7fff /* REG_ADDR_7_0, REG_ADDR_8_14*/ 538c2ecf20Sopenharmony_ci 548c2ecf20Sopenharmony_ci/* SSBI_PA_RD_STATUS fields */ 558c2ecf20Sopenharmony_ci#define SSBI_PA_RD_STATUS_TRANS_DONE (1 << 27) 568c2ecf20Sopenharmony_ci#define SSBI_PA_RD_STATUS_TRANS_DENIED (1 << 26) 578c2ecf20Sopenharmony_ci 588c2ecf20Sopenharmony_ci#define SSBI_TIMEOUT_US 100 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_cienum ssbi_controller_type { 618c2ecf20Sopenharmony_ci MSM_SBI_CTRL_SSBI = 0, 628c2ecf20Sopenharmony_ci MSM_SBI_CTRL_SSBI2, 638c2ecf20Sopenharmony_ci MSM_SBI_CTRL_PMIC_ARBITER, 648c2ecf20Sopenharmony_ci}; 658c2ecf20Sopenharmony_ci 668c2ecf20Sopenharmony_cistruct ssbi { 678c2ecf20Sopenharmony_ci struct device *slave; 688c2ecf20Sopenharmony_ci void __iomem *base; 698c2ecf20Sopenharmony_ci spinlock_t lock; 708c2ecf20Sopenharmony_ci enum ssbi_controller_type controller_type; 718c2ecf20Sopenharmony_ci int (*read)(struct ssbi *, u16 addr, u8 *buf, int len); 728c2ecf20Sopenharmony_ci int (*write)(struct ssbi *, u16 addr, const u8 *buf, int len); 738c2ecf20Sopenharmony_ci}; 748c2ecf20Sopenharmony_ci 758c2ecf20Sopenharmony_cistatic inline u32 ssbi_readl(struct ssbi *ssbi, u32 reg) 768c2ecf20Sopenharmony_ci{ 778c2ecf20Sopenharmony_ci return readl(ssbi->base + reg); 788c2ecf20Sopenharmony_ci} 798c2ecf20Sopenharmony_ci 808c2ecf20Sopenharmony_cistatic inline void ssbi_writel(struct ssbi *ssbi, u32 val, u32 reg) 818c2ecf20Sopenharmony_ci{ 828c2ecf20Sopenharmony_ci writel(val, ssbi->base + reg); 838c2ecf20Sopenharmony_ci} 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci/* 868c2ecf20Sopenharmony_ci * Via private exchange with one of the original authors, the hardware 878c2ecf20Sopenharmony_ci * should generally finish a transaction in about 5us. The worst 888c2ecf20Sopenharmony_ci * case, is when using the arbiter and both other CPUs have just 898c2ecf20Sopenharmony_ci * started trying to use the SSBI bus will result in a time of about 908c2ecf20Sopenharmony_ci * 20us. It should never take longer than this. 918c2ecf20Sopenharmony_ci * 928c2ecf20Sopenharmony_ci * As such, this wait merely spins, with a udelay. 938c2ecf20Sopenharmony_ci */ 948c2ecf20Sopenharmony_cistatic int ssbi_wait_mask(struct ssbi *ssbi, u32 set_mask, u32 clr_mask) 958c2ecf20Sopenharmony_ci{ 968c2ecf20Sopenharmony_ci u32 timeout = SSBI_TIMEOUT_US; 978c2ecf20Sopenharmony_ci u32 val; 988c2ecf20Sopenharmony_ci 998c2ecf20Sopenharmony_ci while (timeout--) { 1008c2ecf20Sopenharmony_ci val = ssbi_readl(ssbi, SSBI2_STATUS); 1018c2ecf20Sopenharmony_ci if (((val & set_mask) == set_mask) && ((val & clr_mask) == 0)) 1028c2ecf20Sopenharmony_ci return 0; 1038c2ecf20Sopenharmony_ci udelay(1); 1048c2ecf20Sopenharmony_ci } 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_ci return -ETIMEDOUT; 1078c2ecf20Sopenharmony_ci} 1088c2ecf20Sopenharmony_ci 1098c2ecf20Sopenharmony_cistatic int 1108c2ecf20Sopenharmony_cissbi_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) 1118c2ecf20Sopenharmony_ci{ 1128c2ecf20Sopenharmony_ci u32 cmd = SSBI_CMD_RDWRN | ((addr & 0xff) << 16); 1138c2ecf20Sopenharmony_ci int ret = 0; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { 1168c2ecf20Sopenharmony_ci u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); 1178c2ecf20Sopenharmony_ci mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); 1188c2ecf20Sopenharmony_ci ssbi_writel(ssbi, mode2, SSBI2_MODE2); 1198c2ecf20Sopenharmony_ci } 1208c2ecf20Sopenharmony_ci 1218c2ecf20Sopenharmony_ci while (len) { 1228c2ecf20Sopenharmony_ci ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); 1238c2ecf20Sopenharmony_ci if (ret) 1248c2ecf20Sopenharmony_ci goto err; 1258c2ecf20Sopenharmony_ci 1268c2ecf20Sopenharmony_ci ssbi_writel(ssbi, cmd, SSBI2_CMD); 1278c2ecf20Sopenharmony_ci ret = ssbi_wait_mask(ssbi, SSBI_STATUS_RD_READY, 0); 1288c2ecf20Sopenharmony_ci if (ret) 1298c2ecf20Sopenharmony_ci goto err; 1308c2ecf20Sopenharmony_ci *buf++ = ssbi_readl(ssbi, SSBI2_RD) & 0xff; 1318c2ecf20Sopenharmony_ci len--; 1328c2ecf20Sopenharmony_ci } 1338c2ecf20Sopenharmony_ci 1348c2ecf20Sopenharmony_cierr: 1358c2ecf20Sopenharmony_ci return ret; 1368c2ecf20Sopenharmony_ci} 1378c2ecf20Sopenharmony_ci 1388c2ecf20Sopenharmony_cistatic int 1398c2ecf20Sopenharmony_cissbi_write_bytes(struct ssbi *ssbi, u16 addr, const u8 *buf, int len) 1408c2ecf20Sopenharmony_ci{ 1418c2ecf20Sopenharmony_ci int ret = 0; 1428c2ecf20Sopenharmony_ci 1438c2ecf20Sopenharmony_ci if (ssbi->controller_type == MSM_SBI_CTRL_SSBI2) { 1448c2ecf20Sopenharmony_ci u32 mode2 = ssbi_readl(ssbi, SSBI2_MODE2); 1458c2ecf20Sopenharmony_ci mode2 = SET_SSBI_MODE2_REG_ADDR_15_8(mode2, addr); 1468c2ecf20Sopenharmony_ci ssbi_writel(ssbi, mode2, SSBI2_MODE2); 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci 1498c2ecf20Sopenharmony_ci while (len) { 1508c2ecf20Sopenharmony_ci ret = ssbi_wait_mask(ssbi, SSBI_STATUS_READY, 0); 1518c2ecf20Sopenharmony_ci if (ret) 1528c2ecf20Sopenharmony_ci goto err; 1538c2ecf20Sopenharmony_ci 1548c2ecf20Sopenharmony_ci ssbi_writel(ssbi, ((addr & 0xff) << 16) | *buf, SSBI2_CMD); 1558c2ecf20Sopenharmony_ci ret = ssbi_wait_mask(ssbi, 0, SSBI_STATUS_MCHN_BUSY); 1568c2ecf20Sopenharmony_ci if (ret) 1578c2ecf20Sopenharmony_ci goto err; 1588c2ecf20Sopenharmony_ci buf++; 1598c2ecf20Sopenharmony_ci len--; 1608c2ecf20Sopenharmony_ci } 1618c2ecf20Sopenharmony_ci 1628c2ecf20Sopenharmony_cierr: 1638c2ecf20Sopenharmony_ci return ret; 1648c2ecf20Sopenharmony_ci} 1658c2ecf20Sopenharmony_ci 1668c2ecf20Sopenharmony_ci/* 1678c2ecf20Sopenharmony_ci * See ssbi_wait_mask for an explanation of the time and the 1688c2ecf20Sopenharmony_ci * busywait. 1698c2ecf20Sopenharmony_ci */ 1708c2ecf20Sopenharmony_cistatic inline int 1718c2ecf20Sopenharmony_cissbi_pa_transfer(struct ssbi *ssbi, u32 cmd, u8 *data) 1728c2ecf20Sopenharmony_ci{ 1738c2ecf20Sopenharmony_ci u32 timeout = SSBI_TIMEOUT_US; 1748c2ecf20Sopenharmony_ci u32 rd_status = 0; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci ssbi_writel(ssbi, cmd, SSBI_PA_CMD); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci while (timeout--) { 1798c2ecf20Sopenharmony_ci rd_status = ssbi_readl(ssbi, SSBI_PA_RD_STATUS); 1808c2ecf20Sopenharmony_ci 1818c2ecf20Sopenharmony_ci if (rd_status & SSBI_PA_RD_STATUS_TRANS_DENIED) 1828c2ecf20Sopenharmony_ci return -EPERM; 1838c2ecf20Sopenharmony_ci 1848c2ecf20Sopenharmony_ci if (rd_status & SSBI_PA_RD_STATUS_TRANS_DONE) { 1858c2ecf20Sopenharmony_ci if (data) 1868c2ecf20Sopenharmony_ci *data = rd_status & 0xff; 1878c2ecf20Sopenharmony_ci return 0; 1888c2ecf20Sopenharmony_ci } 1898c2ecf20Sopenharmony_ci udelay(1); 1908c2ecf20Sopenharmony_ci } 1918c2ecf20Sopenharmony_ci 1928c2ecf20Sopenharmony_ci return -ETIMEDOUT; 1938c2ecf20Sopenharmony_ci} 1948c2ecf20Sopenharmony_ci 1958c2ecf20Sopenharmony_cistatic int 1968c2ecf20Sopenharmony_cissbi_pa_read_bytes(struct ssbi *ssbi, u16 addr, u8 *buf, int len) 1978c2ecf20Sopenharmony_ci{ 1988c2ecf20Sopenharmony_ci u32 cmd; 1998c2ecf20Sopenharmony_ci int ret = 0; 2008c2ecf20Sopenharmony_ci 2018c2ecf20Sopenharmony_ci cmd = SSBI_PA_CMD_RDWRN | (addr & SSBI_PA_CMD_ADDR_MASK) << 8; 2028c2ecf20Sopenharmony_ci 2038c2ecf20Sopenharmony_ci while (len) { 2048c2ecf20Sopenharmony_ci ret = ssbi_pa_transfer(ssbi, cmd, buf); 2058c2ecf20Sopenharmony_ci if (ret) 2068c2ecf20Sopenharmony_ci goto err; 2078c2ecf20Sopenharmony_ci buf++; 2088c2ecf20Sopenharmony_ci len--; 2098c2ecf20Sopenharmony_ci } 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cierr: 2128c2ecf20Sopenharmony_ci return ret; 2138c2ecf20Sopenharmony_ci} 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_cistatic int 2168c2ecf20Sopenharmony_cissbi_pa_write_bytes(struct ssbi *ssbi, u16 addr, const u8 *buf, int len) 2178c2ecf20Sopenharmony_ci{ 2188c2ecf20Sopenharmony_ci u32 cmd; 2198c2ecf20Sopenharmony_ci int ret = 0; 2208c2ecf20Sopenharmony_ci 2218c2ecf20Sopenharmony_ci while (len) { 2228c2ecf20Sopenharmony_ci cmd = (addr & SSBI_PA_CMD_ADDR_MASK) << 8 | *buf; 2238c2ecf20Sopenharmony_ci ret = ssbi_pa_transfer(ssbi, cmd, NULL); 2248c2ecf20Sopenharmony_ci if (ret) 2258c2ecf20Sopenharmony_ci goto err; 2268c2ecf20Sopenharmony_ci buf++; 2278c2ecf20Sopenharmony_ci len--; 2288c2ecf20Sopenharmony_ci } 2298c2ecf20Sopenharmony_ci 2308c2ecf20Sopenharmony_cierr: 2318c2ecf20Sopenharmony_ci return ret; 2328c2ecf20Sopenharmony_ci} 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ciint ssbi_read(struct device *dev, u16 addr, u8 *buf, int len) 2358c2ecf20Sopenharmony_ci{ 2368c2ecf20Sopenharmony_ci struct ssbi *ssbi = dev_get_drvdata(dev); 2378c2ecf20Sopenharmony_ci unsigned long flags; 2388c2ecf20Sopenharmony_ci int ret; 2398c2ecf20Sopenharmony_ci 2408c2ecf20Sopenharmony_ci spin_lock_irqsave(&ssbi->lock, flags); 2418c2ecf20Sopenharmony_ci ret = ssbi->read(ssbi, addr, buf, len); 2428c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ssbi->lock, flags); 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_ci return ret; 2458c2ecf20Sopenharmony_ci} 2468c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ssbi_read); 2478c2ecf20Sopenharmony_ci 2488c2ecf20Sopenharmony_ciint ssbi_write(struct device *dev, u16 addr, const u8 *buf, int len) 2498c2ecf20Sopenharmony_ci{ 2508c2ecf20Sopenharmony_ci struct ssbi *ssbi = dev_get_drvdata(dev); 2518c2ecf20Sopenharmony_ci unsigned long flags; 2528c2ecf20Sopenharmony_ci int ret; 2538c2ecf20Sopenharmony_ci 2548c2ecf20Sopenharmony_ci spin_lock_irqsave(&ssbi->lock, flags); 2558c2ecf20Sopenharmony_ci ret = ssbi->write(ssbi, addr, buf, len); 2568c2ecf20Sopenharmony_ci spin_unlock_irqrestore(&ssbi->lock, flags); 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci return ret; 2598c2ecf20Sopenharmony_ci} 2608c2ecf20Sopenharmony_ciEXPORT_SYMBOL_GPL(ssbi_write); 2618c2ecf20Sopenharmony_ci 2628c2ecf20Sopenharmony_cistatic int ssbi_probe(struct platform_device *pdev) 2638c2ecf20Sopenharmony_ci{ 2648c2ecf20Sopenharmony_ci struct device_node *np = pdev->dev.of_node; 2658c2ecf20Sopenharmony_ci struct resource *mem_res; 2668c2ecf20Sopenharmony_ci struct ssbi *ssbi; 2678c2ecf20Sopenharmony_ci const char *type; 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci ssbi = devm_kzalloc(&pdev->dev, sizeof(*ssbi), GFP_KERNEL); 2708c2ecf20Sopenharmony_ci if (!ssbi) 2718c2ecf20Sopenharmony_ci return -ENOMEM; 2728c2ecf20Sopenharmony_ci 2738c2ecf20Sopenharmony_ci mem_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); 2748c2ecf20Sopenharmony_ci ssbi->base = devm_ioremap_resource(&pdev->dev, mem_res); 2758c2ecf20Sopenharmony_ci if (IS_ERR(ssbi->base)) 2768c2ecf20Sopenharmony_ci return PTR_ERR(ssbi->base); 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci platform_set_drvdata(pdev, ssbi); 2798c2ecf20Sopenharmony_ci 2808c2ecf20Sopenharmony_ci type = of_get_property(np, "qcom,controller-type", NULL); 2818c2ecf20Sopenharmony_ci if (type == NULL) { 2828c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Missing qcom,controller-type property\n"); 2838c2ecf20Sopenharmony_ci return -EINVAL; 2848c2ecf20Sopenharmony_ci } 2858c2ecf20Sopenharmony_ci dev_info(&pdev->dev, "SSBI controller type: '%s'\n", type); 2868c2ecf20Sopenharmony_ci if (strcmp(type, "ssbi") == 0) 2878c2ecf20Sopenharmony_ci ssbi->controller_type = MSM_SBI_CTRL_SSBI; 2888c2ecf20Sopenharmony_ci else if (strcmp(type, "ssbi2") == 0) 2898c2ecf20Sopenharmony_ci ssbi->controller_type = MSM_SBI_CTRL_SSBI2; 2908c2ecf20Sopenharmony_ci else if (strcmp(type, "pmic-arbiter") == 0) 2918c2ecf20Sopenharmony_ci ssbi->controller_type = MSM_SBI_CTRL_PMIC_ARBITER; 2928c2ecf20Sopenharmony_ci else { 2938c2ecf20Sopenharmony_ci dev_err(&pdev->dev, "Unknown qcom,controller-type\n"); 2948c2ecf20Sopenharmony_ci return -EINVAL; 2958c2ecf20Sopenharmony_ci } 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci if (ssbi->controller_type == MSM_SBI_CTRL_PMIC_ARBITER) { 2988c2ecf20Sopenharmony_ci ssbi->read = ssbi_pa_read_bytes; 2998c2ecf20Sopenharmony_ci ssbi->write = ssbi_pa_write_bytes; 3008c2ecf20Sopenharmony_ci } else { 3018c2ecf20Sopenharmony_ci ssbi->read = ssbi_read_bytes; 3028c2ecf20Sopenharmony_ci ssbi->write = ssbi_write_bytes; 3038c2ecf20Sopenharmony_ci } 3048c2ecf20Sopenharmony_ci 3058c2ecf20Sopenharmony_ci spin_lock_init(&ssbi->lock); 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci return devm_of_platform_populate(&pdev->dev); 3088c2ecf20Sopenharmony_ci} 3098c2ecf20Sopenharmony_ci 3108c2ecf20Sopenharmony_cistatic const struct of_device_id ssbi_match_table[] = { 3118c2ecf20Sopenharmony_ci { .compatible = "qcom,ssbi" }, 3128c2ecf20Sopenharmony_ci {} 3138c2ecf20Sopenharmony_ci}; 3148c2ecf20Sopenharmony_ciMODULE_DEVICE_TABLE(of, ssbi_match_table); 3158c2ecf20Sopenharmony_ci 3168c2ecf20Sopenharmony_cistatic struct platform_driver ssbi_driver = { 3178c2ecf20Sopenharmony_ci .probe = ssbi_probe, 3188c2ecf20Sopenharmony_ci .driver = { 3198c2ecf20Sopenharmony_ci .name = "ssbi", 3208c2ecf20Sopenharmony_ci .of_match_table = ssbi_match_table, 3218c2ecf20Sopenharmony_ci }, 3228c2ecf20Sopenharmony_ci}; 3238c2ecf20Sopenharmony_cimodule_platform_driver(ssbi_driver); 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL v2"); 3268c2ecf20Sopenharmony_ciMODULE_VERSION("1.0"); 3278c2ecf20Sopenharmony_ciMODULE_ALIAS("platform:ssbi"); 3288c2ecf20Sopenharmony_ciMODULE_AUTHOR("Dima Zavin <dima@android.com>"); 329