/* Copyright (c) 2012, 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. */ #define pr_fmt(fmt) "%s: " fmt, __func__ #include #include #include #include #include #include #include #include #define PM8XXX_SPK_CTL1_REG_OFF 0 #define PM8XXX_SPK_CTL2_REG_OFF 1 #define PM8XXX_SPK_CTL3_REG_OFF 2 #define PM8XXX_SPK_CTL4_REG_OFF 3 #define PM8XXX_SPK_TEST_REG_1_OFF 4 #define PM8XXX_SPK_TEST_REG_2_OFF 5 #define PM8XXX_SPK_BANK_SEL 4 #define PM8XXX_SPK_BANK_WRITE 0x80 #define PM8XXX_SPK_BANK_VAL_MASK 0xF #define BOOST_6DB_GAIN_EN_MASK 0x8 #define VSEL_LD0_1P1 0x0 #define VSEL_LD0_1P2 0x2 #define VSEL_LD0_1P0 0x4 #define PWM_EN_MASK 0xF #define PM8XXX_SPK_TEST_REG_1_BANKS 8 #define PM8XXX_SPK_TEST_REG_2_BANKS 2 #define PM8XXX_SPK_GAIN 0x5 #define PM8XXX_ADD_EN 0x1 struct pm8xxx_spk_chip { struct list_head link; struct pm8xxx_spk_platform_data pdata; struct device *dev; enum pm8xxx_version version; struct mutex spk_mutex; u16 base; u16 end; }; static struct pm8xxx_spk_chip *the_spk_chip; static inline bool spk_defined(void) { if (the_spk_chip == NULL || IS_ERR(the_spk_chip)) return false; return true; } static int pm8xxx_spk_bank_write(u16 reg, u16 bank, u8 val) { int rc = 0; u8 bank_val = PM8XXX_SPK_BANK_WRITE | (bank << PM8XXX_SPK_BANK_SEL); bank_val |= (val & PM8XXX_SPK_BANK_VAL_MASK); mutex_lock(&the_spk_chip->spk_mutex); rc = pm8xxx_writeb(the_spk_chip->dev->parent, reg, bank_val); if (rc) pr_err("pm8xxx_writeb(): rc=%d\n", rc); mutex_unlock(&the_spk_chip->spk_mutex); return rc; } static int pm8xxx_spk_read(u16 addr) { int rc = 0; u8 val = 0; mutex_lock(&the_spk_chip->spk_mutex); rc = pm8xxx_readb(the_spk_chip->dev->parent, the_spk_chip->base + addr, &val); if (rc) { pr_err("pm8xxx_spk_readb() failed: rc=%d\n", rc); val = rc; } mutex_unlock(&the_spk_chip->spk_mutex); return val; } static int pm8xxx_spk_write(u16 addr, u8 val) { int rc = 0; mutex_lock(&the_spk_chip->spk_mutex); rc = pm8xxx_writeb(the_spk_chip->dev->parent, the_spk_chip->base + addr, val); if (rc) pr_err("pm8xxx_writeb() failed: rc=%d\n", rc); mutex_unlock(&the_spk_chip->spk_mutex); return rc; } int pm8xxx_spk_mute(bool mute) { u8 val = 0; int ret = 0; if (spk_defined() == false) { pr_err("Invalid spk handle or no spk_chip\n"); return -ENODEV; } val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); val |= mute << 2; ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); return ret; } EXPORT_SYMBOL_GPL(pm8xxx_spk_mute); int pm8xxx_spk_gain(u8 gain) { u8 val; int ret = 0; if (spk_defined() == false) { pr_err("Invalid spk handle or no spk_chip\n"); return -ENODEV; } val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); val = (gain << 4) | (val & 0xF); ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); if (!ret) { pm8xxx_spk_bank_write(the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF, 0, BOOST_6DB_GAIN_EN_MASK | VSEL_LD0_1P2); } return ret; } EXPORT_SYMBOL_GPL(pm8xxx_spk_gain); int pm8xxx_spk_enable(int enable) { int val = 0; u16 addr; int ret = 0; if (spk_defined() == false) { pr_err("Invalid spk handle or no spk_chip\n"); return -ENODEV; } addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF; val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); if (val < 0) return val; if (enable) val |= (1 << 3); else val &= ~(1 << 3); ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); if (!ret) ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK); return ret; } EXPORT_SYMBOL_GPL(pm8xxx_spk_enable); static int pm8xxx_spk_config(void) { u16 addr; int ret = 0; if (spk_defined() == false) { pr_err("Invalid spk handle or no spk_chip\n"); return -ENODEV; } addr = the_spk_chip->base + PM8XXX_SPK_TEST_REG_1_OFF; ret = pm8xxx_spk_bank_write(addr, 6, PWM_EN_MASK & 0); if (!ret) ret = pm8xxx_spk_gain(PM8XXX_SPK_GAIN); return ret; } static int __devinit pm8xxx_spk_probe(struct platform_device *pdev) { const struct pm8xxx_spk_platform_data *pdata = pdev->dev.platform_data; int ret = 0; u8 value = 0; if (!pdata) { pr_err("missing platform data\n"); return -EINVAL; } the_spk_chip = kzalloc(sizeof(struct pm8xxx_spk_chip), GFP_KERNEL); if (the_spk_chip == NULL) { pr_err("kzalloc() failed.\n"); return -ENOMEM; } mutex_init(&the_spk_chip->spk_mutex); the_spk_chip->dev = &pdev->dev; the_spk_chip->version = pm8xxx_get_version(the_spk_chip->dev->parent); switch (pm8xxx_get_version(the_spk_chip->dev->parent)) { case PM8XXX_VERSION_8038: break; default: ret = -ENODEV; goto err_handle; } memcpy(&(the_spk_chip->pdata), pdata, sizeof(struct pm8xxx_spk_platform_data)); the_spk_chip->base = pdev->resource[0].start; the_spk_chip->end = pdev->resource[0].end; if (the_spk_chip->pdata.spk_add_enable) { int val; val = pm8xxx_spk_read(PM8XXX_SPK_CTL1_REG_OFF); if (val < 0) { ret = val; goto err_handle; } val |= (the_spk_chip->pdata.spk_add_enable & PM8XXX_ADD_EN); ret = pm8xxx_spk_write(PM8XXX_SPK_CTL1_REG_OFF, val); if (ret < 0) goto err_handle; } value = ((the_spk_chip->pdata.cd_ng_threshold << 5) | the_spk_chip->pdata.cd_nf_preamp_bias << 3); pr_debug("Setting SPK_CTL2_REG = %02x\n", value); pm8xxx_spk_write(PM8XXX_SPK_CTL2_REG_OFF, value); value = ((the_spk_chip->pdata.cd_ng_hold << 5) | (the_spk_chip->pdata.cd_ng_max_atten << 1) | the_spk_chip->pdata.noise_mute); pr_debug("Setting SPK_CTL3_REG = %02x\n", value); pm8xxx_spk_write(PM8XXX_SPK_CTL3_REG_OFF, value); value = ((the_spk_chip->pdata.cd_ng_decay_rate << 5) | (the_spk_chip->pdata.cd_ng_attack_rate << 3) | the_spk_chip->pdata.cd_delay << 2); pr_debug("Setting SPK_CTL4_REG = %02x\n", value); pm8xxx_spk_write(PM8XXX_SPK_CTL4_REG_OFF, value); return pm8xxx_spk_config(); err_handle: pr_err("pm8xxx_spk_probe failed." "Audio unavailable on speaker.\n"); mutex_destroy(&the_spk_chip->spk_mutex); kfree(the_spk_chip); return ret; } static int __devexit pm8xxx_spk_remove(struct platform_device *pdev) { if (spk_defined() == false) { pr_err("Invalid spk handle or no spk_chip\n"); return -ENODEV; } mutex_destroy(&the_spk_chip->spk_mutex); kfree(the_spk_chip); return 0; } static struct platform_driver pm8xxx_spk_driver = { .probe = pm8xxx_spk_probe, .remove = __devexit_p(pm8xxx_spk_remove), .driver = { .name = PM8XXX_SPK_DEV_NAME, .owner = THIS_MODULE, }, }; static int __init pm8xxx_spk_init(void) { return platform_driver_register(&pm8xxx_spk_driver); } subsys_initcall(pm8xxx_spk_init); static void __exit pm8xxx_spk_exit(void) { platform_driver_unregister(&pm8xxx_spk_driver); } module_exit(pm8xxx_spk_exit); MODULE_LICENSE("GPL v2"); MODULE_DESCRIPTION("PM8XXX SPK driver"); MODULE_ALIAS("platform:" PM8XXX_SPK_DEV_NAME);