mirror of
https://github.com/BobTheBlinker/android_kernel_motorola_sm6375.git
synced 2026-10-29 03:01:39 -04:00
MFD bits for the 3.6 merge window.
We have support for a few new drivers: - Samsung s2mps11 - Wolfson Microelectronics wm5102 and wm5110 - Marvell 88PM800 and 88PM805 - TI twl6041 We also have our regular driver improvements: - Device tree and IRQ domain support for STE AB8500 - Regmap and devm_* API conversion for TI tps6586x - Device tree support for Samsung max77686 - devm_* API conversion for STE AB3100 Besides that, quite a lot of fixing and cleanup for mc13xxx, tps65910, tps65090, da9052 and twl-core. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.12 (GNU/Linux) iQIcBAABAgAGBQJQFpGVAAoJEIqAPN1PVmxKFNoP/1dkYngrxxV6cxdyLJ74APhG lVKPgaDxQhdgfwCZJmMeZK1UphZo80cWnEXG6sTHZUEQdTaslSJu5SuPfUM+fo7e 52/dU0nx0ZE04pwPQLHbidS4TmHlbLg9oM2kmIf9RO5rg34GodwVgRL/4+k1qvhz aWYJt9erFhQOpqaSX66mXHSuhvzHypbcE7d2B2Ykmh3NoYiH2w9H9KmIbbb+ZLq8 +Bp/i5Ys/vfooo+8IE2w6KZfIzMwsmmtWjjr/38yuQJaKZCh/zn23DM9HsdrVf++ RzfniRF4YBxmeKi7zi8MFIYys8filTCXA9dXbGSAKCuUCT37yZRnUxTeN1bn7Bux A7KRpG7pUKQKVKqCTndvK5LcQKlT33XyW2ZzV1wVWX2JkCJ+gilPeykb8IabNvGX nIp0STEGR/WdCLEAKo8pJF7Usn0RuUzAug02SG/mQ6dpnLoZqp0Od5W7gRhT7M7h hXr/xKJ6cG5YwicpAdy5kJJ0dRgQrtaHwxrF0B68AXZ7CmAtkPuEGCYhUCFnGQUH XJ0CodAqqVBRyYiQS4zIpIh2nqhIdsgv4OC1+kVLbubQk+PR88zG9Jvg6i1HQi/A OHi7N5Wite3YUrs3sBzDKnEc/Il2YRhVaz2SLVNfZR0PS7hywHN3rK/tVFINTgei jNEz1H6hu7ToNLfs0UzP =c28c -----END PGP SIGNATURE----- Merge tag 'mfd-3.6-1' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6 Pull MFD bits from Samuel Ortiz: "We have support for a few new drivers: - Samsung s2mps11 - Wolfson Microelectronics wm5102 and wm5110 - Marvell 88PM800 and 88PM805 - TI twl6041 We also have our regular driver improvements: - Device tree and IRQ domain support for STE AB8500 - Regmap and devm_* API conversion for TI tps6586x - Device tree support for Samsung max77686 - devm_* API conversion for STE AB3100 Besides that, quite a lot of fixing and cleanup for mc13xxx, tps65910, tps65090, da9052 and twl-core." Fix up mostly trivial conflicts, with the exception of drivers/usb/host/ehci-omap.c in particular, which had some re-organization of the reset sequence (commit1a49e2ac96: "EHCI: centralize controller initialization") that clashed with commit2761a63945("mfd: USB: Fix the omap-usb EHCI ULPI PHY reset fix issues"). In particular, commit2761a63945moved the usb_add_hcd() to the *middle* of the reset sequence, which clashes fairly badly with the reset sequence re-organization (although it could have been done inside the new omap_ehci_init() function). I left that part of commit2761a63945just undone. * tag 'mfd-3.6-1' of git://git.kernel.org/pub/scm/linux/kernel/git/sameo/mfd-2.6: (110 commits) mfd: Ensure AB8500 platform data is passed through db8500-prcmu to MFD Core mfd: Arizone core should select MFD_CORE mfd: Fix arizona-irq.c build by selecting REGMAP_IRQ mfd: Add debug trace on entering and leaving arizone runtime suspend mfd: Correct tps65090 cell names mfd: Remove gpio support from tps6586x core driver ARM: tegra: defconfig: Enable tps6586x gpio gpio: tps6586x: Add gpio support through platform driver mfd: Cache tps6586x register through regmap mfd: Use regmap for tps6586x register access. mfd: Use devm managed resources for tps6586x input: Add onkey support for 88PM80X PMIC mfd: Add support for twl6041 mfd: Fix twl6040 revision information mfd: Matches should be NULL when populate anatop child devices input: ab8500-ponkey: Create AB8500 domain IRQ mapping mfd: Add missing out of memory check for pcf50633 Documentation: Describe the AB8500 Device Tree bindings mfd: Add tps65910 32-kHz-crystal-input init mfd: Drop modifying mc13xxx driver's id_table in probe ...
This commit is contained in:
commit
3e701cdfe6
107 changed files with 17915 additions and 5391 deletions
596
drivers/mfd/88pm800.c
Normal file
596
drivers/mfd/88pm800.c
Normal file
|
|
@ -0,0 +1,596 @@
|
|||
/*
|
||||
* Base driver for Marvell 88PM800
|
||||
*
|
||||
* Copyright (C) 2012 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
* Joseph(Yossi) Hanin <yhanin@marvell.com>
|
||||
* Qiao Zhou <zhouqiao@marvell.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General
|
||||
* Public License. See the file "COPYING" in the main directory of this
|
||||
* archive for more details.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/88pm80x.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#define PM800_CHIP_ID (0x00)
|
||||
|
||||
/* Interrupt Registers */
|
||||
#define PM800_INT_STATUS1 (0x05)
|
||||
#define PM800_ONKEY_INT_STS1 (1 << 0)
|
||||
#define PM800_EXTON_INT_STS1 (1 << 1)
|
||||
#define PM800_CHG_INT_STS1 (1 << 2)
|
||||
#define PM800_BAT_INT_STS1 (1 << 3)
|
||||
#define PM800_RTC_INT_STS1 (1 << 4)
|
||||
#define PM800_CLASSD_OC_INT_STS1 (1 << 5)
|
||||
|
||||
#define PM800_INT_STATUS2 (0x06)
|
||||
#define PM800_VBAT_INT_STS2 (1 << 0)
|
||||
#define PM800_VSYS_INT_STS2 (1 << 1)
|
||||
#define PM800_VCHG_INT_STS2 (1 << 2)
|
||||
#define PM800_TINT_INT_STS2 (1 << 3)
|
||||
#define PM800_GPADC0_INT_STS2 (1 << 4)
|
||||
#define PM800_TBAT_INT_STS2 (1 << 5)
|
||||
#define PM800_GPADC2_INT_STS2 (1 << 6)
|
||||
#define PM800_GPADC3_INT_STS2 (1 << 7)
|
||||
|
||||
#define PM800_INT_STATUS3 (0x07)
|
||||
|
||||
#define PM800_INT_STATUS4 (0x08)
|
||||
#define PM800_GPIO0_INT_STS4 (1 << 0)
|
||||
#define PM800_GPIO1_INT_STS4 (1 << 1)
|
||||
#define PM800_GPIO2_INT_STS4 (1 << 2)
|
||||
#define PM800_GPIO3_INT_STS4 (1 << 3)
|
||||
#define PM800_GPIO4_INT_STS4 (1 << 4)
|
||||
|
||||
#define PM800_INT_ENA_1 (0x09)
|
||||
#define PM800_ONKEY_INT_ENA1 (1 << 0)
|
||||
#define PM800_EXTON_INT_ENA1 (1 << 1)
|
||||
#define PM800_CHG_INT_ENA1 (1 << 2)
|
||||
#define PM800_BAT_INT_ENA1 (1 << 3)
|
||||
#define PM800_RTC_INT_ENA1 (1 << 4)
|
||||
#define PM800_CLASSD_OC_INT_ENA1 (1 << 5)
|
||||
|
||||
#define PM800_INT_ENA_2 (0x0A)
|
||||
#define PM800_VBAT_INT_ENA2 (1 << 0)
|
||||
#define PM800_VSYS_INT_ENA2 (1 << 1)
|
||||
#define PM800_VCHG_INT_ENA2 (1 << 2)
|
||||
#define PM800_TINT_INT_ENA2 (1 << 3)
|
||||
|
||||
#define PM800_INT_ENA_3 (0x0B)
|
||||
#define PM800_GPADC0_INT_ENA3 (1 << 0)
|
||||
#define PM800_GPADC1_INT_ENA3 (1 << 1)
|
||||
#define PM800_GPADC2_INT_ENA3 (1 << 2)
|
||||
#define PM800_GPADC3_INT_ENA3 (1 << 3)
|
||||
#define PM800_GPADC4_INT_ENA3 (1 << 4)
|
||||
|
||||
#define PM800_INT_ENA_4 (0x0C)
|
||||
#define PM800_GPIO0_INT_ENA4 (1 << 0)
|
||||
#define PM800_GPIO1_INT_ENA4 (1 << 1)
|
||||
#define PM800_GPIO2_INT_ENA4 (1 << 2)
|
||||
#define PM800_GPIO3_INT_ENA4 (1 << 3)
|
||||
#define PM800_GPIO4_INT_ENA4 (1 << 4)
|
||||
|
||||
/* number of INT_ENA & INT_STATUS regs */
|
||||
#define PM800_INT_REG_NUM (4)
|
||||
|
||||
/* Interrupt Number in 88PM800 */
|
||||
enum {
|
||||
PM800_IRQ_ONKEY, /*EN1b0 *//*0 */
|
||||
PM800_IRQ_EXTON, /*EN1b1 */
|
||||
PM800_IRQ_CHG, /*EN1b2 */
|
||||
PM800_IRQ_BAT, /*EN1b3 */
|
||||
PM800_IRQ_RTC, /*EN1b4 */
|
||||
PM800_IRQ_CLASSD, /*EN1b5 *//*5 */
|
||||
PM800_IRQ_VBAT, /*EN2b0 */
|
||||
PM800_IRQ_VSYS, /*EN2b1 */
|
||||
PM800_IRQ_VCHG, /*EN2b2 */
|
||||
PM800_IRQ_TINT, /*EN2b3 */
|
||||
PM800_IRQ_GPADC0, /*EN3b0 *//*10 */
|
||||
PM800_IRQ_GPADC1, /*EN3b1 */
|
||||
PM800_IRQ_GPADC2, /*EN3b2 */
|
||||
PM800_IRQ_GPADC3, /*EN3b3 */
|
||||
PM800_IRQ_GPADC4, /*EN3b4 */
|
||||
PM800_IRQ_GPIO0, /*EN4b0 *//*15 */
|
||||
PM800_IRQ_GPIO1, /*EN4b1 */
|
||||
PM800_IRQ_GPIO2, /*EN4b2 */
|
||||
PM800_IRQ_GPIO3, /*EN4b3 */
|
||||
PM800_IRQ_GPIO4, /*EN4b4 *//*19 */
|
||||
PM800_MAX_IRQ,
|
||||
};
|
||||
|
||||
enum {
|
||||
/* Procida */
|
||||
PM800_CHIP_A0 = 0x60,
|
||||
PM800_CHIP_A1 = 0x61,
|
||||
PM800_CHIP_B0 = 0x62,
|
||||
PM800_CHIP_C0 = 0x63,
|
||||
PM800_CHIP_END = PM800_CHIP_C0,
|
||||
|
||||
/* Make sure to update this to the last stepping */
|
||||
PM8XXX_CHIP_END = PM800_CHIP_END
|
||||
};
|
||||
|
||||
static const struct i2c_device_id pm80x_id_table[] = {
|
||||
{"88PM800", CHIP_PM800},
|
||||
{} /* NULL terminated */
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
|
||||
|
||||
static struct resource rtc_resources[] = {
|
||||
{
|
||||
.name = "88pm80x-rtc",
|
||||
.start = PM800_IRQ_RTC,
|
||||
.end = PM800_IRQ_RTC,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell rtc_devs[] = {
|
||||
{
|
||||
.name = "88pm80x-rtc",
|
||||
.num_resources = ARRAY_SIZE(rtc_resources),
|
||||
.resources = &rtc_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static struct resource onkey_resources[] = {
|
||||
{
|
||||
.name = "88pm80x-onkey",
|
||||
.start = PM800_IRQ_ONKEY,
|
||||
.end = PM800_IRQ_ONKEY,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell onkey_devs[] = {
|
||||
{
|
||||
.name = "88pm80x-onkey",
|
||||
.num_resources = 1,
|
||||
.resources = &onkey_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static const struct regmap_irq pm800_irqs[] = {
|
||||
/* INT0 */
|
||||
[PM800_IRQ_ONKEY] = {
|
||||
.mask = PM800_ONKEY_INT_ENA1,
|
||||
},
|
||||
[PM800_IRQ_EXTON] = {
|
||||
.mask = PM800_EXTON_INT_ENA1,
|
||||
},
|
||||
[PM800_IRQ_CHG] = {
|
||||
.mask = PM800_CHG_INT_ENA1,
|
||||
},
|
||||
[PM800_IRQ_BAT] = {
|
||||
.mask = PM800_BAT_INT_ENA1,
|
||||
},
|
||||
[PM800_IRQ_RTC] = {
|
||||
.mask = PM800_RTC_INT_ENA1,
|
||||
},
|
||||
[PM800_IRQ_CLASSD] = {
|
||||
.mask = PM800_CLASSD_OC_INT_ENA1,
|
||||
},
|
||||
/* INT1 */
|
||||
[PM800_IRQ_VBAT] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM800_VBAT_INT_ENA2,
|
||||
},
|
||||
[PM800_IRQ_VSYS] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM800_VSYS_INT_ENA2,
|
||||
},
|
||||
[PM800_IRQ_VCHG] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM800_VCHG_INT_ENA2,
|
||||
},
|
||||
[PM800_IRQ_TINT] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM800_TINT_INT_ENA2,
|
||||
},
|
||||
/* INT2 */
|
||||
[PM800_IRQ_GPADC0] = {
|
||||
.reg_offset = 2,
|
||||
.mask = PM800_GPADC0_INT_ENA3,
|
||||
},
|
||||
[PM800_IRQ_GPADC1] = {
|
||||
.reg_offset = 2,
|
||||
.mask = PM800_GPADC1_INT_ENA3,
|
||||
},
|
||||
[PM800_IRQ_GPADC2] = {
|
||||
.reg_offset = 2,
|
||||
.mask = PM800_GPADC2_INT_ENA3,
|
||||
},
|
||||
[PM800_IRQ_GPADC3] = {
|
||||
.reg_offset = 2,
|
||||
.mask = PM800_GPADC3_INT_ENA3,
|
||||
},
|
||||
[PM800_IRQ_GPADC4] = {
|
||||
.reg_offset = 2,
|
||||
.mask = PM800_GPADC4_INT_ENA3,
|
||||
},
|
||||
/* INT3 */
|
||||
[PM800_IRQ_GPIO0] = {
|
||||
.reg_offset = 3,
|
||||
.mask = PM800_GPIO0_INT_ENA4,
|
||||
},
|
||||
[PM800_IRQ_GPIO1] = {
|
||||
.reg_offset = 3,
|
||||
.mask = PM800_GPIO1_INT_ENA4,
|
||||
},
|
||||
[PM800_IRQ_GPIO2] = {
|
||||
.reg_offset = 3,
|
||||
.mask = PM800_GPIO2_INT_ENA4,
|
||||
},
|
||||
[PM800_IRQ_GPIO3] = {
|
||||
.reg_offset = 3,
|
||||
.mask = PM800_GPIO3_INT_ENA4,
|
||||
},
|
||||
[PM800_IRQ_GPIO4] = {
|
||||
.reg_offset = 3,
|
||||
.mask = PM800_GPIO4_INT_ENA4,
|
||||
},
|
||||
};
|
||||
|
||||
static int __devinit device_gpadc_init(struct pm80x_chip *chip,
|
||||
struct pm80x_platform_data *pdata)
|
||||
{
|
||||
struct pm80x_subchip *subchip = chip->subchip;
|
||||
struct regmap *map = subchip->regmap_gpadc;
|
||||
int data = 0, mask = 0, ret = 0;
|
||||
|
||||
if (!map) {
|
||||
dev_warn(chip->dev,
|
||||
"Warning: gpadc regmap is not available!\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
/*
|
||||
* initialize GPADC without activating it turn on GPADC
|
||||
* measurments
|
||||
*/
|
||||
ret = regmap_update_bits(map,
|
||||
PM800_GPADC_MISC_CONFIG2,
|
||||
PM800_GPADC_MISC_GPFSM_EN,
|
||||
PM800_GPADC_MISC_GPFSM_EN);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
/*
|
||||
* This function configures the ADC as requires for
|
||||
* CP implementation.CP does not "own" the ADC configuration
|
||||
* registers and relies on AP.
|
||||
* Reason: enable automatic ADC measurements needed
|
||||
* for CP to get VBAT and RF temperature readings.
|
||||
*/
|
||||
ret = regmap_update_bits(map, PM800_GPADC_MEAS_EN1,
|
||||
PM800_MEAS_EN1_VBAT, PM800_MEAS_EN1_VBAT);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
ret = regmap_update_bits(map, PM800_GPADC_MEAS_EN2,
|
||||
(PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN),
|
||||
(PM800_MEAS_EN2_RFTMP | PM800_MEAS_GP0_EN));
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* the defult of PM800 is GPADC operates at 100Ks/s rate
|
||||
* and Number of GPADC slots with active current bias prior
|
||||
* to GPADC sampling = 1 slot for all GPADCs set for
|
||||
* Temprature mesurmants
|
||||
*/
|
||||
mask = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 |
|
||||
PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3);
|
||||
|
||||
if (pdata && (pdata->batt_det == 0))
|
||||
data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN1 |
|
||||
PM800_GPADC_GP_BIAS_EN2 | PM800_GPADC_GP_BIAS_EN3);
|
||||
else
|
||||
data = (PM800_GPADC_GP_BIAS_EN0 | PM800_GPADC_GP_BIAS_EN2 |
|
||||
PM800_GPADC_GP_BIAS_EN3);
|
||||
|
||||
ret = regmap_update_bits(map, PM800_GP_BIAS_ENA1, mask, data);
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
dev_info(chip->dev, "pm800 device_gpadc_init: Done\n");
|
||||
return 0;
|
||||
|
||||
out:
|
||||
dev_info(chip->dev, "pm800 device_gpadc_init: Failed!\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devinit device_irq_init_800(struct pm80x_chip *chip)
|
||||
{
|
||||
struct regmap *map = chip->regmap;
|
||||
unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
|
||||
int data, mask, ret = -EINVAL;
|
||||
|
||||
if (!map || !chip->irq) {
|
||||
dev_err(chip->dev, "incorrect parameters\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* irq_mode defines the way of clearing interrupt. it's read-clear by
|
||||
* default.
|
||||
*/
|
||||
mask =
|
||||
PM800_WAKEUP2_INV_INT | PM800_WAKEUP2_INT_CLEAR |
|
||||
PM800_WAKEUP2_INT_MASK;
|
||||
|
||||
data = PM800_WAKEUP2_INT_CLEAR;
|
||||
ret = regmap_update_bits(map, PM800_WAKEUP2, mask, data);
|
||||
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret =
|
||||
regmap_add_irq_chip(chip->regmap, chip->irq, flags, -1,
|
||||
chip->regmap_irq_chip, &chip->irq_data);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void device_irq_exit_800(struct pm80x_chip *chip)
|
||||
{
|
||||
regmap_del_irq_chip(chip->irq, chip->irq_data);
|
||||
}
|
||||
|
||||
static struct regmap_irq_chip pm800_irq_chip = {
|
||||
.name = "88pm800",
|
||||
.irqs = pm800_irqs,
|
||||
.num_irqs = ARRAY_SIZE(pm800_irqs),
|
||||
|
||||
.num_regs = 4,
|
||||
.status_base = PM800_INT_STATUS1,
|
||||
.mask_base = PM800_INT_ENA_1,
|
||||
.ack_base = PM800_INT_STATUS1,
|
||||
};
|
||||
|
||||
static int pm800_pages_init(struct pm80x_chip *chip)
|
||||
{
|
||||
struct pm80x_subchip *subchip;
|
||||
struct i2c_client *client = chip->client;
|
||||
|
||||
subchip = chip->subchip;
|
||||
/* PM800 block power: i2c addr 0x31 */
|
||||
if (subchip->power_page_addr) {
|
||||
subchip->power_page =
|
||||
i2c_new_dummy(client->adapter, subchip->power_page_addr);
|
||||
subchip->regmap_power =
|
||||
devm_regmap_init_i2c(subchip->power_page,
|
||||
&pm80x_regmap_config);
|
||||
i2c_set_clientdata(subchip->power_page, chip);
|
||||
} else
|
||||
dev_info(chip->dev,
|
||||
"PM800 block power 0x31: No power_page_addr\n");
|
||||
|
||||
/* PM800 block GPADC: i2c addr 0x32 */
|
||||
if (subchip->gpadc_page_addr) {
|
||||
subchip->gpadc_page = i2c_new_dummy(client->adapter,
|
||||
subchip->gpadc_page_addr);
|
||||
subchip->regmap_gpadc =
|
||||
devm_regmap_init_i2c(subchip->gpadc_page,
|
||||
&pm80x_regmap_config);
|
||||
i2c_set_clientdata(subchip->gpadc_page, chip);
|
||||
} else
|
||||
dev_info(chip->dev,
|
||||
"PM800 block GPADC 0x32: No gpadc_page_addr\n");
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void pm800_pages_exit(struct pm80x_chip *chip)
|
||||
{
|
||||
struct pm80x_subchip *subchip;
|
||||
|
||||
regmap_exit(chip->regmap);
|
||||
i2c_unregister_device(chip->client);
|
||||
|
||||
subchip = chip->subchip;
|
||||
if (subchip->power_page) {
|
||||
regmap_exit(subchip->regmap_power);
|
||||
i2c_unregister_device(subchip->power_page);
|
||||
}
|
||||
if (subchip->gpadc_page) {
|
||||
regmap_exit(subchip->regmap_gpadc);
|
||||
i2c_unregister_device(subchip->gpadc_page);
|
||||
}
|
||||
}
|
||||
|
||||
static int __devinit device_800_init(struct pm80x_chip *chip,
|
||||
struct pm80x_platform_data *pdata)
|
||||
{
|
||||
int ret, pmic_id;
|
||||
unsigned int val;
|
||||
|
||||
ret = regmap_read(chip->regmap, PM800_CHIP_ID, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
|
||||
pmic_id = val & PM80X_VERSION_MASK;
|
||||
|
||||
if ((pmic_id >= PM800_CHIP_A0) && (pmic_id <= PM800_CHIP_END)) {
|
||||
chip->version = val;
|
||||
dev_info(chip->dev,
|
||||
"88PM80x:Marvell 88PM800 (ID:0x%x) detected\n", val);
|
||||
} else {
|
||||
dev_err(chip->dev,
|
||||
"Failed to detect Marvell 88PM800:ChipID[0x%x]\n", val);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* alarm wake up bit will be clear in device_irq_init(),
|
||||
* read before that
|
||||
*/
|
||||
ret = regmap_read(chip->regmap, PM800_RTC_CONTROL, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read RTC register: %d\n", ret);
|
||||
goto out;
|
||||
}
|
||||
if (val & PM800_ALARM_WAKEUP) {
|
||||
if (pdata && pdata->rtc)
|
||||
pdata->rtc->rtc_wakeup = 1;
|
||||
}
|
||||
|
||||
ret = device_gpadc_init(chip, pdata);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "[%s]Failed to init gpadc\n", __func__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
chip->regmap_irq_chip = &pm800_irq_chip;
|
||||
|
||||
ret = device_irq_init_800(chip);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "[%s]Failed to init pm800 irq\n", __func__);
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret =
|
||||
mfd_add_devices(chip->dev, 0, &onkey_devs[0],
|
||||
ARRAY_SIZE(onkey_devs), &onkey_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add onkey subdev\n");
|
||||
goto out_dev;
|
||||
} else
|
||||
dev_info(chip->dev, "[%s]:Added mfd onkey_devs\n", __func__);
|
||||
|
||||
if (pdata && pdata->rtc) {
|
||||
rtc_devs[0].platform_data = pdata->rtc;
|
||||
rtc_devs[0].pdata_size = sizeof(struct pm80x_rtc_pdata);
|
||||
ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0],
|
||||
ARRAY_SIZE(rtc_devs), NULL, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add rtc subdev\n");
|
||||
goto out_dev;
|
||||
} else
|
||||
dev_info(chip->dev,
|
||||
"[%s]:Added mfd rtc_devs\n", __func__);
|
||||
}
|
||||
|
||||
return 0;
|
||||
out_dev:
|
||||
mfd_remove_devices(chip->dev);
|
||||
device_irq_exit_800(chip);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devinit pm800_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret = 0;
|
||||
struct pm80x_chip *chip;
|
||||
struct pm80x_platform_data *pdata = client->dev.platform_data;
|
||||
struct pm80x_subchip *subchip;
|
||||
|
||||
ret = pm80x_init(client, id);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "pm800_init fail\n");
|
||||
goto out_init;
|
||||
}
|
||||
|
||||
chip = i2c_get_clientdata(client);
|
||||
|
||||
/* init subchip for PM800 */
|
||||
subchip =
|
||||
devm_kzalloc(&client->dev, sizeof(struct pm80x_subchip),
|
||||
GFP_KERNEL);
|
||||
if (!subchip) {
|
||||
ret = -ENOMEM;
|
||||
goto err_subchip_alloc;
|
||||
}
|
||||
|
||||
subchip->power_page_addr = pdata->power_page_addr;
|
||||
subchip->gpadc_page_addr = pdata->gpadc_page_addr;
|
||||
chip->subchip = subchip;
|
||||
|
||||
ret = device_800_init(chip, pdata);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "%s id 0x%x failed!\n", __func__, chip->id);
|
||||
goto err_800_init;
|
||||
}
|
||||
|
||||
ret = pm800_pages_init(chip);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "pm800_pages_init failed!\n");
|
||||
goto err_page_init;
|
||||
}
|
||||
|
||||
if (pdata->plat_config)
|
||||
pdata->plat_config(chip, pdata);
|
||||
|
||||
err_page_init:
|
||||
mfd_remove_devices(chip->dev);
|
||||
device_irq_exit_800(chip);
|
||||
err_800_init:
|
||||
devm_kfree(&client->dev, subchip);
|
||||
err_subchip_alloc:
|
||||
pm80x_deinit(client);
|
||||
out_init:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit pm800_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pm80x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
mfd_remove_devices(chip->dev);
|
||||
device_irq_exit_800(chip);
|
||||
|
||||
pm800_pages_exit(chip);
|
||||
devm_kfree(&client->dev, chip->subchip);
|
||||
|
||||
pm80x_deinit(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver pm800_driver = {
|
||||
.driver = {
|
||||
.name = "88PM80X",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &pm80x_pm_ops,
|
||||
},
|
||||
.probe = pm800_probe,
|
||||
.remove = __devexit_p(pm800_remove),
|
||||
.id_table = pm80x_id_table,
|
||||
};
|
||||
|
||||
static int __init pm800_i2c_init(void)
|
||||
{
|
||||
return i2c_add_driver(&pm800_driver);
|
||||
}
|
||||
subsys_initcall(pm800_i2c_init);
|
||||
|
||||
static void __exit pm800_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pm800_driver);
|
||||
}
|
||||
module_exit(pm800_i2c_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM800");
|
||||
MODULE_AUTHOR("Qiao Zhou <[email protected]>");
|
||||
MODULE_LICENSE("GPL");
|
||||
301
drivers/mfd/88pm805.c
Normal file
301
drivers/mfd/88pm805.c
Normal file
|
|
@ -0,0 +1,301 @@
|
|||
/*
|
||||
* Base driver for Marvell 88PM805
|
||||
*
|
||||
* Copyright (C) 2012 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
* Joseph(Yossi) Hanin <yhanin@marvell.com>
|
||||
* Qiao Zhou <zhouqiao@marvell.com>
|
||||
*
|
||||
* This file is subject to the terms and conditions of the GNU General
|
||||
* Public License. See the file "COPYING" in the main directory of this
|
||||
* archive for more details.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/88pm80x.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/delay.h>
|
||||
|
||||
#define PM805_CHIP_ID (0x00)
|
||||
|
||||
static const struct i2c_device_id pm80x_id_table[] = {
|
||||
{"88PM805", CHIP_PM805},
|
||||
{} /* NULL terminated */
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, pm80x_id_table);
|
||||
|
||||
/* Interrupt Number in 88PM805 */
|
||||
enum {
|
||||
PM805_IRQ_LDO_OFF, /*0 */
|
||||
PM805_IRQ_SRC_DPLL_LOCK, /*1 */
|
||||
PM805_IRQ_CLIP_FAULT,
|
||||
PM805_IRQ_MIC_CONFLICT,
|
||||
PM805_IRQ_HP2_SHRT,
|
||||
PM805_IRQ_HP1_SHRT, /*5 */
|
||||
PM805_IRQ_FINE_PLL_FAULT,
|
||||
PM805_IRQ_RAW_PLL_FAULT,
|
||||
PM805_IRQ_VOLP_BTN_DET,
|
||||
PM805_IRQ_VOLM_BTN_DET,
|
||||
PM805_IRQ_SHRT_BTN_DET, /*10 */
|
||||
PM805_IRQ_MIC_DET, /*11 */
|
||||
|
||||
PM805_MAX_IRQ,
|
||||
};
|
||||
|
||||
static struct resource codec_resources[] = {
|
||||
{
|
||||
/* Headset microphone insertion or removal */
|
||||
.name = "micin",
|
||||
.start = PM805_IRQ_MIC_DET,
|
||||
.end = PM805_IRQ_MIC_DET,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
/* Audio short HP1 */
|
||||
.name = "audio-short1",
|
||||
.start = PM805_IRQ_HP1_SHRT,
|
||||
.end = PM805_IRQ_HP1_SHRT,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
{
|
||||
/* Audio short HP2 */
|
||||
.name = "audio-short2",
|
||||
.start = PM805_IRQ_HP2_SHRT,
|
||||
.end = PM805_IRQ_HP2_SHRT,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell codec_devs[] = {
|
||||
{
|
||||
.name = "88pm80x-codec",
|
||||
.num_resources = ARRAY_SIZE(codec_resources),
|
||||
.resources = &codec_resources[0],
|
||||
.id = -1,
|
||||
},
|
||||
};
|
||||
|
||||
static struct regmap_irq pm805_irqs[] = {
|
||||
/* INT0 */
|
||||
[PM805_IRQ_LDO_OFF] = {
|
||||
.mask = PM805_INT1_HP1_SHRT,
|
||||
},
|
||||
[PM805_IRQ_SRC_DPLL_LOCK] = {
|
||||
.mask = PM805_INT1_HP2_SHRT,
|
||||
},
|
||||
[PM805_IRQ_CLIP_FAULT] = {
|
||||
.mask = PM805_INT1_MIC_CONFLICT,
|
||||
},
|
||||
[PM805_IRQ_MIC_CONFLICT] = {
|
||||
.mask = PM805_INT1_CLIP_FAULT,
|
||||
},
|
||||
[PM805_IRQ_HP2_SHRT] = {
|
||||
.mask = PM805_INT1_LDO_OFF,
|
||||
},
|
||||
[PM805_IRQ_HP1_SHRT] = {
|
||||
.mask = PM805_INT1_SRC_DPLL_LOCK,
|
||||
},
|
||||
/* INT1 */
|
||||
[PM805_IRQ_FINE_PLL_FAULT] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM805_INT2_MIC_DET,
|
||||
},
|
||||
[PM805_IRQ_RAW_PLL_FAULT] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM805_INT2_SHRT_BTN_DET,
|
||||
},
|
||||
[PM805_IRQ_VOLP_BTN_DET] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM805_INT2_VOLM_BTN_DET,
|
||||
},
|
||||
[PM805_IRQ_VOLM_BTN_DET] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM805_INT2_VOLP_BTN_DET,
|
||||
},
|
||||
[PM805_IRQ_SHRT_BTN_DET] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM805_INT2_RAW_PLL_FAULT,
|
||||
},
|
||||
[PM805_IRQ_MIC_DET] = {
|
||||
.reg_offset = 1,
|
||||
.mask = PM805_INT2_FINE_PLL_FAULT,
|
||||
},
|
||||
};
|
||||
|
||||
static int __devinit device_irq_init_805(struct pm80x_chip *chip)
|
||||
{
|
||||
struct regmap *map = chip->regmap;
|
||||
unsigned long flags = IRQF_TRIGGER_FALLING | IRQF_ONESHOT;
|
||||
int data, mask, ret = -EINVAL;
|
||||
|
||||
if (!map || !chip->irq) {
|
||||
dev_err(chip->dev, "incorrect parameters\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/*
|
||||
* irq_mode defines the way of clearing interrupt. it's read-clear by
|
||||
* default.
|
||||
*/
|
||||
mask =
|
||||
PM805_STATUS0_INT_CLEAR | PM805_STATUS0_INV_INT |
|
||||
PM800_STATUS0_INT_MASK;
|
||||
|
||||
data = PM805_STATUS0_INT_CLEAR;
|
||||
ret = regmap_update_bits(map, PM805_INT_STATUS0, mask, data);
|
||||
/*
|
||||
* PM805_INT_STATUS is under 32K clock domain, so need to
|
||||
* add proper delay before the next I2C register access.
|
||||
*/
|
||||
msleep(1);
|
||||
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
|
||||
ret =
|
||||
regmap_add_irq_chip(chip->regmap, chip->irq, flags, -1,
|
||||
chip->regmap_irq_chip, &chip->irq_data);
|
||||
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void device_irq_exit_805(struct pm80x_chip *chip)
|
||||
{
|
||||
regmap_del_irq_chip(chip->irq, chip->irq_data);
|
||||
}
|
||||
|
||||
static struct regmap_irq_chip pm805_irq_chip = {
|
||||
.name = "88pm805",
|
||||
.irqs = pm805_irqs,
|
||||
.num_irqs = ARRAY_SIZE(pm805_irqs),
|
||||
|
||||
.num_regs = 2,
|
||||
.status_base = PM805_INT_STATUS1,
|
||||
.mask_base = PM805_INT_MASK1,
|
||||
.ack_base = PM805_INT_STATUS1,
|
||||
};
|
||||
|
||||
static int __devinit device_805_init(struct pm80x_chip *chip)
|
||||
{
|
||||
int ret = 0;
|
||||
unsigned int val;
|
||||
struct regmap *map = chip->regmap;
|
||||
|
||||
if (!map) {
|
||||
dev_err(chip->dev, "regmap is invalid\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = regmap_read(map, PM805_CHIP_ID, &val);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to read CHIP ID: %d\n", ret);
|
||||
goto out_irq_init;
|
||||
}
|
||||
chip->version = val;
|
||||
|
||||
chip->regmap_irq_chip = &pm805_irq_chip;
|
||||
|
||||
ret = device_irq_init_805(chip);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to init pm805 irq!\n");
|
||||
goto out_irq_init;
|
||||
}
|
||||
|
||||
ret = mfd_add_devices(chip->dev, 0, &codec_devs[0],
|
||||
ARRAY_SIZE(codec_devs), &codec_resources[0], 0);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add codec subdev\n");
|
||||
goto out_codec;
|
||||
} else
|
||||
dev_info(chip->dev, "[%s]:Added mfd codec_devs\n", __func__);
|
||||
|
||||
return 0;
|
||||
|
||||
out_codec:
|
||||
device_irq_exit_805(chip);
|
||||
out_irq_init:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devinit pm805_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
int ret = 0;
|
||||
struct pm80x_chip *chip;
|
||||
struct pm80x_platform_data *pdata = client->dev.platform_data;
|
||||
|
||||
ret = pm80x_init(client, id);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "pm805_init fail!\n");
|
||||
goto out_init;
|
||||
}
|
||||
|
||||
chip = i2c_get_clientdata(client);
|
||||
|
||||
ret = device_805_init(chip);
|
||||
if (ret) {
|
||||
dev_err(chip->dev, "%s id 0x%x failed!\n", __func__, chip->id);
|
||||
goto err_805_init;
|
||||
}
|
||||
|
||||
if (pdata->plat_config)
|
||||
pdata->plat_config(chip, pdata);
|
||||
|
||||
err_805_init:
|
||||
pm80x_deinit(client);
|
||||
out_init:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit pm805_remove(struct i2c_client *client)
|
||||
{
|
||||
struct pm80x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
mfd_remove_devices(chip->dev);
|
||||
device_irq_exit_805(chip);
|
||||
|
||||
pm80x_deinit(client);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct i2c_driver pm805_driver = {
|
||||
.driver = {
|
||||
.name = "88PM80X",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &pm80x_pm_ops,
|
||||
},
|
||||
.probe = pm805_probe,
|
||||
.remove = __devexit_p(pm805_remove),
|
||||
.id_table = pm80x_id_table,
|
||||
};
|
||||
|
||||
static int __init pm805_i2c_init(void)
|
||||
{
|
||||
return i2c_add_driver(&pm805_driver);
|
||||
}
|
||||
subsys_initcall(pm805_i2c_init);
|
||||
|
||||
static void __exit pm805_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&pm805_driver);
|
||||
}
|
||||
module_exit(pm805_i2c_exit);
|
||||
|
||||
MODULE_DESCRIPTION("PMIC Driver for Marvell 88PM805");
|
||||
MODULE_AUTHOR("Qiao Zhou <[email protected]>");
|
||||
MODULE_LICENSE("GPL");
|
||||
145
drivers/mfd/88pm80x.c
Normal file
145
drivers/mfd/88pm80x.c
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/*
|
||||
* I2C driver for Marvell 88PM80x
|
||||
*
|
||||
* Copyright (C) 2012 Marvell International Ltd.
|
||||
* Haojian Zhuang <haojian.zhuang@marvell.com>
|
||||
* Joseph(Yossi) Hanin <yhanin@marvell.com>
|
||||
* Qiao Zhou <zhouqiao@marvell.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/mfd/88pm80x.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
/*
|
||||
* workaround: some registers needed by pm805 are defined in pm800, so
|
||||
* need to use this global variable to maintain the relation between
|
||||
* pm800 and pm805. would remove it after HW chip fixes the issue.
|
||||
*/
|
||||
static struct pm80x_chip *g_pm80x_chip;
|
||||
|
||||
const struct regmap_config pm80x_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(pm80x_regmap_config);
|
||||
|
||||
int __devinit pm80x_init(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct pm80x_chip *chip;
|
||||
struct regmap *map;
|
||||
int ret = 0;
|
||||
|
||||
chip =
|
||||
devm_kzalloc(&client->dev, sizeof(struct pm80x_chip), GFP_KERNEL);
|
||||
if (!chip)
|
||||
return -ENOMEM;
|
||||
|
||||
map = devm_regmap_init_i2c(client, &pm80x_regmap_config);
|
||||
if (IS_ERR(map)) {
|
||||
ret = PTR_ERR(map);
|
||||
dev_err(&client->dev, "Failed to allocate register map: %d\n",
|
||||
ret);
|
||||
goto err_regmap_init;
|
||||
}
|
||||
|
||||
chip->id = id->driver_data;
|
||||
if (chip->id < CHIP_PM800 || chip->id > CHIP_PM805) {
|
||||
ret = -EINVAL;
|
||||
goto err_chip_id;
|
||||
}
|
||||
|
||||
chip->client = client;
|
||||
chip->regmap = map;
|
||||
|
||||
chip->irq = client->irq;
|
||||
|
||||
chip->dev = &client->dev;
|
||||
dev_set_drvdata(chip->dev, chip);
|
||||
i2c_set_clientdata(chip->client, chip);
|
||||
|
||||
device_init_wakeup(&client->dev, 1);
|
||||
|
||||
/*
|
||||
* workaround: set g_pm80x_chip to the first probed chip. if the
|
||||
* second chip is probed, just point to the companion to each
|
||||
* other so that pm805 can access those specific register. would
|
||||
* remove it after HW chip fixes the issue.
|
||||
*/
|
||||
if (!g_pm80x_chip)
|
||||
g_pm80x_chip = chip;
|
||||
else {
|
||||
chip->companion = g_pm80x_chip->client;
|
||||
g_pm80x_chip->companion = chip->client;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_chip_id:
|
||||
regmap_exit(map);
|
||||
err_regmap_init:
|
||||
devm_kfree(&client->dev, chip);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm80x_init);
|
||||
|
||||
int pm80x_deinit(struct i2c_client *client)
|
||||
{
|
||||
struct pm80x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
/*
|
||||
* workaround: clear the dependency between pm800 and pm805.
|
||||
* would remove it after HW chip fixes the issue.
|
||||
*/
|
||||
if (g_pm80x_chip->companion)
|
||||
g_pm80x_chip->companion = NULL;
|
||||
else
|
||||
g_pm80x_chip = NULL;
|
||||
|
||||
regmap_exit(chip->regmap);
|
||||
devm_kfree(&client->dev, chip);
|
||||
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(pm80x_deinit);
|
||||
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int pm80x_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct pm80x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
if (chip && chip->wu_flag)
|
||||
if (device_may_wakeup(chip->dev))
|
||||
enable_irq_wake(chip->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int pm80x_resume(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = container_of(dev, struct i2c_client, dev);
|
||||
struct pm80x_chip *chip = i2c_get_clientdata(client);
|
||||
|
||||
if (chip && chip->wu_flag)
|
||||
if (device_may_wakeup(chip->dev))
|
||||
disable_irq_wake(chip->irq);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
SIMPLE_DEV_PM_OPS(pm80x_pm_ops, pm80x_suspend, pm80x_resume);
|
||||
EXPORT_SYMBOL_GPL(pm80x_pm_ops);
|
||||
|
||||
MODULE_DESCRIPTION("I2C Driver for Marvell 88PM80x");
|
||||
MODULE_AUTHOR("Qiao Zhou <[email protected]>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -90,6 +90,10 @@ static struct resource charger_resources[] __devinitdata = {
|
|||
{PM8607_IRQ_VCHG, PM8607_IRQ_VCHG, "vchg voltage", IORESOURCE_IRQ,},
|
||||
};
|
||||
|
||||
static struct resource preg_resources[] __devinitdata = {
|
||||
{PM8606_ID_PREG, PM8606_ID_PREG, "preg", IORESOURCE_IO,},
|
||||
};
|
||||
|
||||
static struct resource rtc_resources[] __devinitdata = {
|
||||
{PM8607_IRQ_RTC, PM8607_IRQ_RTC, "rtc", IORESOURCE_IRQ,},
|
||||
};
|
||||
|
|
@ -142,9 +146,19 @@ static struct mfd_cell codec_devs[] = {
|
|||
{"88pm860x-codec", -1,},
|
||||
};
|
||||
|
||||
static struct regulator_consumer_supply preg_supply[] = {
|
||||
REGULATOR_SUPPLY("preg", "charger-manager"),
|
||||
};
|
||||
|
||||
static struct regulator_init_data preg_init_data = {
|
||||
.num_consumer_supplies = ARRAY_SIZE(preg_supply),
|
||||
.consumer_supplies = &preg_supply[0],
|
||||
};
|
||||
|
||||
static struct mfd_cell power_devs[] = {
|
||||
{"88pm860x-battery", -1,},
|
||||
{"88pm860x-charger", -1,},
|
||||
{"88pm860x-preg", -1,},
|
||||
};
|
||||
|
||||
static struct mfd_cell rtc_devs[] = {
|
||||
|
|
@ -768,6 +782,15 @@ static void __devinit device_power_init(struct pm860x_chip *chip,
|
|||
&charger_resources[0], chip->irq_base);
|
||||
if (ret < 0)
|
||||
dev_err(chip->dev, "Failed to add charger subdev\n");
|
||||
|
||||
power_devs[2].platform_data = &preg_init_data;
|
||||
power_devs[2].pdata_size = sizeof(struct regulator_init_data);
|
||||
power_devs[2].num_resources = ARRAY_SIZE(preg_resources);
|
||||
power_devs[2].resources = &preg_resources[0],
|
||||
ret = mfd_add_devices(chip->dev, 0, &power_devs[2], 1,
|
||||
&preg_resources[0], chip->irq_base);
|
||||
if (ret < 0)
|
||||
dev_err(chip->dev, "Failed to add preg subdev\n");
|
||||
}
|
||||
|
||||
static void __devinit device_onkey_init(struct pm860x_chip *chip,
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ menu "Multifunction device drivers"
|
|||
|
||||
config MFD_CORE
|
||||
tristate
|
||||
select IRQ_DOMAIN
|
||||
default n
|
||||
|
||||
config MFD_88PM860X
|
||||
|
|
@ -20,6 +21,30 @@ config MFD_88PM860X
|
|||
select individual components like voltage regulators, RTC and
|
||||
battery-charger under the corresponding menus.
|
||||
|
||||
config MFD_88PM800
|
||||
tristate "Support Marvell 88PM800"
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
select REGMAP_I2C
|
||||
select REGMAP_IRQ
|
||||
select MFD_CORE
|
||||
help
|
||||
This supports for Marvell 88PM800 Power Management IC.
|
||||
This includes the I2C driver and the core APIs _only_, you have to
|
||||
select individual components like voltage regulators, RTC and
|
||||
battery-charger under the corresponding menus.
|
||||
|
||||
config MFD_88PM805
|
||||
tristate "Support Marvell 88PM805"
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
select REGMAP_I2C
|
||||
select REGMAP_IRQ
|
||||
select MFD_CORE
|
||||
help
|
||||
This supports for Marvell 88PM805 Power Management IC. This includes
|
||||
the I2C driver and the core APIs _only_, you have to select individual
|
||||
components like codec device, headset/Mic device under the
|
||||
corresponding menus.
|
||||
|
||||
config MFD_SM501
|
||||
tristate "Support for Silicon Motion SM501"
|
||||
---help---
|
||||
|
|
@ -173,8 +198,9 @@ config MFD_TPS65217
|
|||
|
||||
config MFD_TPS6586X
|
||||
bool "TPS6586x Power Management chips"
|
||||
depends on I2C=y && GPIOLIB && GENERIC_HARDIRQS
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
depends on REGULATOR
|
||||
help
|
||||
If you say yes here you get support for the TPS6586X series of
|
||||
|
|
@ -424,6 +450,19 @@ config PMIC_ADP5520
|
|||
individual components like LCD backlight, LEDs, GPIOs and Kepad
|
||||
under the corresponding menus.
|
||||
|
||||
config MFD_MAX77686
|
||||
bool "Maxim Semiconductor MAX77686 PMIC Support"
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
select IRQ_DOMAIN
|
||||
help
|
||||
Say yes here to support for Maxim Semiconductor MAX77686.
|
||||
This is a Power Management IC with RTC on chip.
|
||||
This driver provides common support for accessing the device;
|
||||
additional drivers must be enabled in order to use the functionality
|
||||
of the device.
|
||||
|
||||
config MFD_MAX77693
|
||||
bool "Maxim Semiconductor MAX77693 PMIC Support"
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
|
|
@ -451,6 +490,7 @@ config MFD_MAX8997
|
|||
bool "Maxim Semiconductor MAX8997/8966 PMIC Support"
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
select MFD_CORE
|
||||
select IRQ_DOMAIN
|
||||
help
|
||||
Say yes here to support for Maxim Semiconductor MAX8997/8966.
|
||||
This is a Power Management IC with RTC, Flash, Fuel Gauge, Haptic,
|
||||
|
|
@ -470,17 +510,56 @@ config MFD_MAX8998
|
|||
additional drivers must be enabled in order to use the functionality
|
||||
of the device.
|
||||
|
||||
config MFD_S5M_CORE
|
||||
bool "SAMSUNG S5M Series Support"
|
||||
config MFD_SEC_CORE
|
||||
bool "SAMSUNG Electronics PMIC Series Support"
|
||||
depends on I2C=y && GENERIC_HARDIRQS
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
select REGMAP_IRQ
|
||||
help
|
||||
Support for the Samsung Electronics S5M MFD series.
|
||||
Support for the Samsung Electronics MFD series.
|
||||
This driver provides common support for accessing the device,
|
||||
additional drivers must be enabled in order to use the functionality
|
||||
of the device
|
||||
|
||||
config MFD_ARIZONA
|
||||
select REGMAP
|
||||
select REGMAP_IRQ
|
||||
select MFD_CORE
|
||||
bool
|
||||
|
||||
config MFD_ARIZONA_I2C
|
||||
tristate "Support Wolfson Microelectronics Arizona platform with I2C"
|
||||
select MFD_ARIZONA
|
||||
select MFD_CORE
|
||||
select REGMAP_I2C
|
||||
depends on I2C
|
||||
help
|
||||
Support for the Wolfson Microelectronics Arizona platform audio SoC
|
||||
core functionality controlled via I2C.
|
||||
|
||||
config MFD_ARIZONA_SPI
|
||||
tristate "Support Wolfson Microelectronics Arizona platform with SPI"
|
||||
select MFD_ARIZONA
|
||||
select MFD_CORE
|
||||
select REGMAP_SPI
|
||||
depends on SPI_MASTER
|
||||
help
|
||||
Support for the Wolfson Microelectronics Arizona platform audio SoC
|
||||
core functionality controlled via I2C.
|
||||
|
||||
config MFD_WM5102
|
||||
bool "Support Wolfson Microelectronics WM5102"
|
||||
depends on MFD_ARIZONA
|
||||
help
|
||||
Support for Wolfson Microelectronics WM5102 low power audio SoC
|
||||
|
||||
config MFD_WM5110
|
||||
bool "Support Wolfson Microelectronics WM5110"
|
||||
depends on MFD_ARIZONA
|
||||
help
|
||||
Support for Wolfson Microelectronics WM5110 low power audio SoC
|
||||
|
||||
config MFD_WM8400
|
||||
bool "Support Wolfson Microelectronics WM8400"
|
||||
select MFD_CORE
|
||||
|
|
@ -698,6 +777,7 @@ config AB8500_CORE
|
|||
bool "ST-Ericsson AB8500 Mixed Signal Power Management chip"
|
||||
depends on GENERIC_HARDIRQS && ABX500_CORE && MFD_DB8500_PRCMU
|
||||
select MFD_CORE
|
||||
select IRQ_DOMAIN
|
||||
help
|
||||
Select this option to enable access to AB8500 power management
|
||||
chip. This connects to U8500 either on the SSP/SPI bus (deprecated
|
||||
|
|
@ -705,16 +785,6 @@ config AB8500_CORE
|
|||
the irq_chip parts for handling the Mixed Signal chip events.
|
||||
This chip embeds various other multimedia funtionalities as well.
|
||||
|
||||
config AB8500_I2C_CORE
|
||||
bool "AB8500 register access via PRCMU I2C"
|
||||
depends on AB8500_CORE && MFD_DB8500_PRCMU
|
||||
default y
|
||||
help
|
||||
This enables register access to the AB8500 chip via PRCMU I2C.
|
||||
The AB8500 chip can be accessed via SPI or I2C. On DB8500 hardware
|
||||
the I2C bus is connected to the Power Reset
|
||||
and Mangagement Unit, PRCMU.
|
||||
|
||||
config AB8500_DEBUG
|
||||
bool "Enable debug info via debugfs"
|
||||
depends on AB8500_CORE && DEBUG_FS
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@
|
|||
|
||||
88pm860x-objs := 88pm860x-core.o 88pm860x-i2c.o
|
||||
obj-$(CONFIG_MFD_88PM860X) += 88pm860x.o
|
||||
obj-$(CONFIG_MFD_88PM800) += 88pm800.o 88pm80x.o
|
||||
obj-$(CONFIG_MFD_88PM805) += 88pm805.o 88pm80x.o
|
||||
obj-$(CONFIG_MFD_SM501) += sm501.o
|
||||
obj-$(CONFIG_MFD_ASIC3) += asic3.o tmio_core.o
|
||||
|
||||
|
|
@ -24,6 +26,16 @@ obj-$(CONFIG_MFD_T7L66XB) += t7l66xb.o tmio_core.o
|
|||
obj-$(CONFIG_MFD_TC6387XB) += tc6387xb.o tmio_core.o
|
||||
obj-$(CONFIG_MFD_TC6393XB) += tc6393xb.o tmio_core.o
|
||||
|
||||
obj-$(CONFIG_MFD_ARIZONA) += arizona-core.o
|
||||
obj-$(CONFIG_MFD_ARIZONA) += arizona-irq.o
|
||||
obj-$(CONFIG_MFD_ARIZONA_I2C) += arizona-i2c.o
|
||||
obj-$(CONFIG_MFD_ARIZONA_SPI) += arizona-spi.o
|
||||
ifneq ($(CONFIG_MFD_WM5102),n)
|
||||
obj-$(CONFIG_MFD_ARIZONA) += wm5102-tables.o
|
||||
endif
|
||||
ifneq ($(CONFIG_MFD_WM5110),n)
|
||||
obj-$(CONFIG_MFD_ARIZONA) += wm5110-tables.o
|
||||
endif
|
||||
obj-$(CONFIG_MFD_WM8400) += wm8400-core.o
|
||||
wm831x-objs := wm831x-core.o wm831x-irq.o wm831x-otp.o
|
||||
wm831x-objs += wm831x-auxadc.o
|
||||
|
|
@ -78,6 +90,7 @@ obj-$(CONFIG_PMIC_DA9052) += da9052-core.o
|
|||
obj-$(CONFIG_MFD_DA9052_SPI) += da9052-spi.o
|
||||
obj-$(CONFIG_MFD_DA9052_I2C) += da9052-i2c.o
|
||||
|
||||
obj-$(CONFIG_MFD_MAX77686) += max77686.o max77686-irq.o
|
||||
obj-$(CONFIG_MFD_MAX77693) += max77693.o max77693-irq.o
|
||||
max8925-objs := max8925-core.o max8925-i2c.o
|
||||
obj-$(CONFIG_MFD_MAX8925) += max8925.o
|
||||
|
|
@ -116,6 +129,6 @@ obj-$(CONFIG_MFD_AAT2870_CORE) += aat2870-core.o
|
|||
obj-$(CONFIG_MFD_INTEL_MSIC) += intel_msic.o
|
||||
obj-$(CONFIG_MFD_PALMAS) += palmas.o
|
||||
obj-$(CONFIG_MFD_RC5T583) += rc5t583.o rc5t583-irq.o
|
||||
obj-$(CONFIG_MFD_S5M_CORE) += s5m-core.o s5m-irq.o
|
||||
obj-$(CONFIG_MFD_SEC_CORE) += sec-core.o sec-irq.o
|
||||
obj-$(CONFIG_MFD_ANATOP) += anatop-mfd.o
|
||||
obj-$(CONFIG_MFD_LM3533) += lm3533-core.o lm3533-ctrlbank.o
|
||||
|
|
|
|||
|
|
@ -867,7 +867,7 @@ static int __devinit ab3100_probe(struct i2c_client *client,
|
|||
int err;
|
||||
int i;
|
||||
|
||||
ab3100 = kzalloc(sizeof(struct ab3100), GFP_KERNEL);
|
||||
ab3100 = devm_kzalloc(&client->dev, sizeof(struct ab3100), GFP_KERNEL);
|
||||
if (!ab3100) {
|
||||
dev_err(&client->dev, "could not allocate AB3100 device\n");
|
||||
return -ENOMEM;
|
||||
|
|
@ -921,7 +921,7 @@ static int __devinit ab3100_probe(struct i2c_client *client,
|
|||
|
||||
/* Attach a second dummy i2c_client to the test register address */
|
||||
ab3100->testreg_client = i2c_new_dummy(client->adapter,
|
||||
client->addr + 1);
|
||||
client->addr + 1);
|
||||
if (!ab3100->testreg_client) {
|
||||
err = -ENOMEM;
|
||||
goto exit_no_testreg_client;
|
||||
|
|
@ -931,13 +931,13 @@ static int __devinit ab3100_probe(struct i2c_client *client,
|
|||
if (err)
|
||||
goto exit_no_setup;
|
||||
|
||||
err = request_threaded_irq(client->irq, NULL, ab3100_irq_handler,
|
||||
IRQF_ONESHOT, "ab3100-core", ab3100);
|
||||
/* This real unpredictable IRQ is of course sampled for entropy */
|
||||
rand_initialize_irq(client->irq);
|
||||
|
||||
err = devm_request_threaded_irq(&client->dev,
|
||||
client->irq, NULL, ab3100_irq_handler,
|
||||
IRQF_ONESHOT, "ab3100-core", ab3100);
|
||||
if (err)
|
||||
goto exit_no_irq;
|
||||
/* This real unpredictable IRQ is of course sampled for entropy */
|
||||
rand_initialize_irq(client->irq);
|
||||
|
||||
err = abx500_register_ops(&client->dev, &ab3100_ops);
|
||||
if (err)
|
||||
|
|
@ -962,7 +962,6 @@ static int __devinit ab3100_probe(struct i2c_client *client,
|
|||
i2c_unregister_device(ab3100->testreg_client);
|
||||
exit_no_testreg_client:
|
||||
exit_no_detect:
|
||||
kfree(ab3100);
|
||||
return err;
|
||||
}
|
||||
|
||||
|
|
@ -972,16 +971,8 @@ static int __devexit ab3100_remove(struct i2c_client *client)
|
|||
|
||||
/* Unregister subdevices */
|
||||
mfd_remove_devices(&client->dev);
|
||||
|
||||
ab3100_remove_debugfs();
|
||||
i2c_unregister_device(ab3100->testreg_client);
|
||||
|
||||
/*
|
||||
* At this point, all subscribers should have unregistered
|
||||
* their notifiers so deactivate IRQ
|
||||
*/
|
||||
free_irq(client->irq, ab3100);
|
||||
kfree(ab3100);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
#include <linux/slab.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/module.h>
|
||||
|
|
@ -140,7 +141,7 @@ static const char ab8500_version_str[][7] = {
|
|||
[AB8500_VERSION_AB8540] = "AB8540",
|
||||
};
|
||||
|
||||
static int ab8500_i2c_write(struct ab8500 *ab8500, u16 addr, u8 data)
|
||||
static int ab8500_prcmu_write(struct ab8500 *ab8500, u16 addr, u8 data)
|
||||
{
|
||||
int ret;
|
||||
|
||||
|
|
@ -150,7 +151,7 @@ static int ab8500_i2c_write(struct ab8500 *ab8500, u16 addr, u8 data)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ab8500_i2c_write_masked(struct ab8500 *ab8500, u16 addr, u8 mask,
|
||||
static int ab8500_prcmu_write_masked(struct ab8500 *ab8500, u16 addr, u8 mask,
|
||||
u8 data)
|
||||
{
|
||||
int ret;
|
||||
|
|
@ -162,7 +163,7 @@ static int ab8500_i2c_write_masked(struct ab8500 *ab8500, u16 addr, u8 mask,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int ab8500_i2c_read(struct ab8500 *ab8500, u16 addr)
|
||||
static int ab8500_prcmu_read(struct ab8500 *ab8500, u16 addr)
|
||||
{
|
||||
int ret;
|
||||
u8 data;
|
||||
|
|
@ -361,7 +362,7 @@ static void ab8500_irq_sync_unlock(struct irq_data *data)
|
|||
static void ab8500_irq_mask(struct irq_data *data)
|
||||
{
|
||||
struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data);
|
||||
int offset = data->irq - ab8500->irq_base;
|
||||
int offset = data->hwirq;
|
||||
int index = offset / 8;
|
||||
int mask = 1 << (offset % 8);
|
||||
|
||||
|
|
@ -371,7 +372,7 @@ static void ab8500_irq_mask(struct irq_data *data)
|
|||
static void ab8500_irq_unmask(struct irq_data *data)
|
||||
{
|
||||
struct ab8500 *ab8500 = irq_data_get_irq_chip_data(data);
|
||||
int offset = data->irq - ab8500->irq_base;
|
||||
int offset = data->hwirq;
|
||||
int index = offset / 8;
|
||||
int mask = 1 << (offset % 8);
|
||||
|
||||
|
|
@ -510,38 +511,51 @@ static irqreturn_t ab8500_irq(int irq, void *dev)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int ab8500_irq_init(struct ab8500 *ab8500)
|
||||
/**
|
||||
* ab8500_irq_get_virq(): Map an interrupt on a chip to a virtual IRQ
|
||||
*
|
||||
* @ab8500: ab8500_irq controller to operate on.
|
||||
* @irq: index of the interrupt requested in the chip IRQs
|
||||
*
|
||||
* Useful for drivers to request their own IRQs.
|
||||
*/
|
||||
int ab8500_irq_get_virq(struct ab8500 *ab8500, int irq)
|
||||
{
|
||||
int base = ab8500->irq_base;
|
||||
int irq;
|
||||
int num_irqs;
|
||||
if (!ab8500)
|
||||
return -EINVAL;
|
||||
|
||||
if (is_ab9540(ab8500))
|
||||
num_irqs = AB9540_NR_IRQS;
|
||||
else if (is_ab8505(ab8500))
|
||||
num_irqs = AB8505_NR_IRQS;
|
||||
else
|
||||
num_irqs = AB8500_NR_IRQS;
|
||||
return irq_create_mapping(ab8500->domain, irq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(ab8500_irq_get_virq);
|
||||
|
||||
for (irq = base; irq < base + num_irqs; irq++) {
|
||||
irq_set_chip_data(irq, ab8500);
|
||||
irq_set_chip_and_handler(irq, &ab8500_irq_chip,
|
||||
handle_simple_irq);
|
||||
irq_set_nested_thread(irq, 1);
|
||||
static int ab8500_irq_map(struct irq_domain *d, unsigned int virq,
|
||||
irq_hw_number_t hwirq)
|
||||
{
|
||||
struct ab8500 *ab8500 = d->host_data;
|
||||
|
||||
if (!ab8500)
|
||||
return -EINVAL;
|
||||
|
||||
irq_set_chip_data(virq, ab8500);
|
||||
irq_set_chip_and_handler(virq, &ab8500_irq_chip,
|
||||
handle_simple_irq);
|
||||
irq_set_nested_thread(virq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(irq, IRQF_VALID);
|
||||
set_irq_flags(virq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(irq);
|
||||
irq_set_noprobe(virq);
|
||||
#endif
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ab8500_irq_remove(struct ab8500 *ab8500)
|
||||
static struct irq_domain_ops ab8500_irq_ops = {
|
||||
.map = ab8500_irq_map,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
static int ab8500_irq_init(struct ab8500 *ab8500, struct device_node *np)
|
||||
{
|
||||
int base = ab8500->irq_base;
|
||||
int irq;
|
||||
int num_irqs;
|
||||
|
||||
if (is_ab9540(ab8500))
|
||||
|
|
@ -551,13 +565,22 @@ static void ab8500_irq_remove(struct ab8500 *ab8500)
|
|||
else
|
||||
num_irqs = AB8500_NR_IRQS;
|
||||
|
||||
for (irq = base; irq < base + num_irqs; irq++) {
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(irq, 0);
|
||||
#endif
|
||||
irq_set_chip_and_handler(irq, NULL, NULL);
|
||||
irq_set_chip_data(irq, NULL);
|
||||
if (ab8500->irq_base) {
|
||||
ab8500->domain = irq_domain_add_legacy(
|
||||
NULL, num_irqs, ab8500->irq_base,
|
||||
0, &ab8500_irq_ops, ab8500);
|
||||
}
|
||||
else {
|
||||
ab8500->domain = irq_domain_add_linear(
|
||||
np, num_irqs, &ab8500_irq_ops, ab8500);
|
||||
}
|
||||
|
||||
if (!ab8500->domain) {
|
||||
dev_err(ab8500->dev, "Failed to create irqdomain\n");
|
||||
return -ENOSYS;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int ab8500_suspend(struct ab8500 *ab8500)
|
||||
|
|
@ -947,54 +970,69 @@ static struct mfd_cell __devinitdata abx500_common_devs[] = {
|
|||
#ifdef CONFIG_DEBUG_FS
|
||||
{
|
||||
.name = "ab8500-debug",
|
||||
.of_compatible = "stericsson,ab8500-debug",
|
||||
.num_resources = ARRAY_SIZE(ab8500_debug_resources),
|
||||
.resources = ab8500_debug_resources,
|
||||
},
|
||||
#endif
|
||||
{
|
||||
.name = "ab8500-sysctrl",
|
||||
.of_compatible = "stericsson,ab8500-sysctrl",
|
||||
},
|
||||
{
|
||||
.name = "ab8500-regulator",
|
||||
.of_compatible = "stericsson,ab8500-regulator",
|
||||
},
|
||||
{
|
||||
.name = "ab8500-gpadc",
|
||||
.of_compatible = "stericsson,ab8500-gpadc",
|
||||
.num_resources = ARRAY_SIZE(ab8500_gpadc_resources),
|
||||
.resources = ab8500_gpadc_resources,
|
||||
},
|
||||
{
|
||||
.name = "ab8500-rtc",
|
||||
.of_compatible = "stericsson,ab8500-rtc",
|
||||
.num_resources = ARRAY_SIZE(ab8500_rtc_resources),
|
||||
.resources = ab8500_rtc_resources,
|
||||
},
|
||||
{
|
||||
.name = "ab8500-acc-det",
|
||||
.of_compatible = "stericsson,ab8500-acc-det",
|
||||
.num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources),
|
||||
.resources = ab8500_av_acc_detect_resources,
|
||||
},
|
||||
{
|
||||
.name = "ab8500-poweron-key",
|
||||
.of_compatible = "stericsson,ab8500-poweron-key",
|
||||
.num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources),
|
||||
.resources = ab8500_poweronkey_db_resources,
|
||||
},
|
||||
{
|
||||
.name = "ab8500-pwm",
|
||||
.of_compatible = "stericsson,ab8500-pwm",
|
||||
.id = 1,
|
||||
},
|
||||
{
|
||||
.name = "ab8500-pwm",
|
||||
.of_compatible = "stericsson,ab8500-pwm",
|
||||
.id = 2,
|
||||
},
|
||||
{
|
||||
.name = "ab8500-pwm",
|
||||
.of_compatible = "stericsson,ab8500-pwm",
|
||||
.id = 3,
|
||||
},
|
||||
{ .name = "ab8500-leds", },
|
||||
{
|
||||
.name = "ab8500-leds",
|
||||
.of_compatible = "stericsson,ab8500-leds",
|
||||
},
|
||||
{
|
||||
.name = "ab8500-denc",
|
||||
.of_compatible = "stericsson,ab8500-denc",
|
||||
},
|
||||
{
|
||||
.name = "ab8500-temp",
|
||||
.of_compatible = "stericsson,ab8500-temp",
|
||||
.num_resources = ARRAY_SIZE(ab8500_temp_resources),
|
||||
.resources = ab8500_temp_resources,
|
||||
},
|
||||
|
|
@ -1026,11 +1064,13 @@ static struct mfd_cell __devinitdata ab8500_bm_devs[] = {
|
|||
static struct mfd_cell __devinitdata ab8500_devs[] = {
|
||||
{
|
||||
.name = "ab8500-gpio",
|
||||
.of_compatible = "stericsson,ab8500-gpio",
|
||||
.num_resources = ARRAY_SIZE(ab8500_gpio_resources),
|
||||
.resources = ab8500_gpio_resources,
|
||||
},
|
||||
{
|
||||
.name = "ab8500-usb",
|
||||
.of_compatible = "stericsson,ab8500-usb",
|
||||
.num_resources = ARRAY_SIZE(ab8500_usb_resources),
|
||||
.resources = ab8500_usb_resources,
|
||||
},
|
||||
|
|
@ -1207,16 +1247,17 @@ static struct attribute_group ab9540_attr_group = {
|
|||
.attrs = ab9540_sysfs_entries,
|
||||
};
|
||||
|
||||
static const struct of_device_id ab8500_match[] = {
|
||||
{
|
||||
.compatible = "stericsson,ab8500",
|
||||
.data = (void *)AB8500_VERSION_AB8500,
|
||||
},
|
||||
{},
|
||||
};
|
||||
|
||||
static int __devinit ab8500_probe(struct platform_device *pdev)
|
||||
{
|
||||
static char *switch_off_status[] = {
|
||||
"Swoff bit programming",
|
||||
"Thermal protection activation",
|
||||
"Vbat lower then BattOk falling threshold",
|
||||
"Watchdog expired",
|
||||
"Non presence of 32kHz clock",
|
||||
"Battery level lower than power on reset threshold",
|
||||
"Power on key 1 pressed longer than 10 seconds",
|
||||
"DB8500 thermal shutdown"};
|
||||
struct ab8500_platform_data *plat = dev_get_platdata(&pdev->dev);
|
||||
const struct platform_device_id *platid = platform_get_device_id(pdev);
|
||||
enum ab8500_version version = AB8500_VERSION_UNDEFINED;
|
||||
|
|
@ -1233,14 +1274,6 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||
|
||||
if (plat)
|
||||
ab8500->irq_base = plat->irq_base;
|
||||
else if (np)
|
||||
ret = of_property_read_u32(np, "stericsson,irq-base", &ab8500->irq_base);
|
||||
|
||||
if (!ab8500->irq_base) {
|
||||
dev_info(&pdev->dev, "couldn't find irq-base\n");
|
||||
ret = -EINVAL;
|
||||
goto out_free_ab8500;
|
||||
}
|
||||
|
||||
ab8500->dev = &pdev->dev;
|
||||
|
||||
|
|
@ -1252,9 +1285,9 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||
|
||||
ab8500->irq = resource->start;
|
||||
|
||||
ab8500->read = ab8500_i2c_read;
|
||||
ab8500->write = ab8500_i2c_write;
|
||||
ab8500->write_masked = ab8500_i2c_write_masked;
|
||||
ab8500->read = ab8500_prcmu_read;
|
||||
ab8500->write = ab8500_prcmu_write;
|
||||
ab8500->write_masked = ab8500_prcmu_write_masked;
|
||||
|
||||
mutex_init(&ab8500->lock);
|
||||
mutex_init(&ab8500->irq_lock);
|
||||
|
|
@ -1264,9 +1297,6 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||
|
||||
if (platid)
|
||||
version = platid->driver_data;
|
||||
else if (np)
|
||||
version = (unsigned int)
|
||||
of_match_device(ab8500_match, &pdev->dev)->data;
|
||||
|
||||
if (version != AB8500_VERSION_UNDEFINED)
|
||||
ab8500->version = version;
|
||||
|
|
@ -1323,7 +1353,20 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||
AB8500_SWITCH_OFF_STATUS, &value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
dev_info(ab8500->dev, "switch off status: %#x", value);
|
||||
dev_info(ab8500->dev, "switch off cause(s) (%#x): ", value);
|
||||
|
||||
if (value) {
|
||||
for (i = 0; i < ARRAY_SIZE(switch_off_status); i++) {
|
||||
if (value & 1)
|
||||
printk(KERN_CONT " \"%s\"",
|
||||
switch_off_status[i]);
|
||||
value = value >> 1;
|
||||
|
||||
}
|
||||
printk(KERN_CONT "\n");
|
||||
} else {
|
||||
printk(KERN_CONT " None\n");
|
||||
}
|
||||
|
||||
if (plat && plat->init)
|
||||
plat->init(ab8500);
|
||||
|
|
@ -1352,53 +1395,50 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||
for (i = 0; i < ab8500->mask_size; i++)
|
||||
ab8500->mask[i] = ab8500->oldmask[i] = 0xff;
|
||||
|
||||
if (ab8500->irq_base) {
|
||||
ret = ab8500_irq_init(ab8500);
|
||||
if (ret)
|
||||
goto out_freeoldmask;
|
||||
ret = ab8500_irq_init(ab8500, np);
|
||||
if (ret)
|
||||
goto out_freeoldmask;
|
||||
|
||||
/* Activate this feature only in ab9540 */
|
||||
/* till tests are done on ab8500 1p2 or later*/
|
||||
if (is_ab9540(ab8500))
|
||||
ret = request_threaded_irq(ab8500->irq, NULL,
|
||||
/* Activate this feature only in ab9540 */
|
||||
/* till tests are done on ab8500 1p2 or later*/
|
||||
if (is_ab9540(ab8500)) {
|
||||
ret = request_threaded_irq(ab8500->irq, NULL,
|
||||
ab8500_hierarchical_irq,
|
||||
IRQF_ONESHOT | IRQF_NO_SUSPEND,
|
||||
"ab8500", ab8500);
|
||||
else
|
||||
ret = request_threaded_irq(ab8500->irq, NULL,
|
||||
}
|
||||
else {
|
||||
ret = request_threaded_irq(ab8500->irq, NULL,
|
||||
ab8500_irq,
|
||||
IRQF_ONESHOT | IRQF_NO_SUSPEND,
|
||||
"ab8500", ab8500);
|
||||
if (ret)
|
||||
goto out_removeirq;
|
||||
goto out_freeoldmask;
|
||||
}
|
||||
|
||||
if (!np) {
|
||||
ret = mfd_add_devices(ab8500->dev, 0, abx500_common_devs,
|
||||
ARRAY_SIZE(abx500_common_devs), NULL,
|
||||
ret = mfd_add_devices(ab8500->dev, 0, abx500_common_devs,
|
||||
ARRAY_SIZE(abx500_common_devs), NULL,
|
||||
ab8500->irq_base);
|
||||
if (ret)
|
||||
goto out_freeirq;
|
||||
|
||||
if (is_ab9540(ab8500))
|
||||
ret = mfd_add_devices(ab8500->dev, 0, ab9540_devs,
|
||||
ARRAY_SIZE(ab9540_devs), NULL,
|
||||
ab8500->irq_base);
|
||||
else
|
||||
ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs,
|
||||
ARRAY_SIZE(ab8500_devs), NULL,
|
||||
ab8500->irq_base);
|
||||
if (ret)
|
||||
goto out_freeirq;
|
||||
|
||||
if (ret)
|
||||
goto out_freeirq;
|
||||
|
||||
if (is_ab9540(ab8500))
|
||||
ret = mfd_add_devices(ab8500->dev, 0, ab9540_devs,
|
||||
ARRAY_SIZE(ab9540_devs), NULL,
|
||||
ab8500->irq_base);
|
||||
else
|
||||
ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs,
|
||||
ARRAY_SIZE(ab8500_devs), NULL,
|
||||
ab8500->irq_base);
|
||||
if (ret)
|
||||
goto out_freeirq;
|
||||
|
||||
if (is_ab9540(ab8500) || is_ab8505(ab8500))
|
||||
ret = mfd_add_devices(ab8500->dev, 0, ab9540_ab8505_devs,
|
||||
ARRAY_SIZE(ab9540_ab8505_devs), NULL,
|
||||
ab8500->irq_base);
|
||||
if (ret)
|
||||
goto out_freeirq;
|
||||
}
|
||||
if (is_ab9540(ab8500) || is_ab8505(ab8500))
|
||||
ret = mfd_add_devices(ab8500->dev, 0, ab9540_ab8505_devs,
|
||||
ARRAY_SIZE(ab9540_ab8505_devs), NULL,
|
||||
ab8500->irq_base);
|
||||
if (ret)
|
||||
goto out_freeirq;
|
||||
|
||||
if (!no_bm) {
|
||||
/* Add battery management devices */
|
||||
|
|
@ -1417,15 +1457,11 @@ static int __devinit ab8500_probe(struct platform_device *pdev)
|
|||
&ab8500_attr_group);
|
||||
if (ret)
|
||||
dev_err(ab8500->dev, "error creating sysfs entries\n");
|
||||
else
|
||||
return ret;
|
||||
|
||||
return ret;
|
||||
|
||||
out_freeirq:
|
||||
if (ab8500->irq_base)
|
||||
free_irq(ab8500->irq, ab8500);
|
||||
out_removeirq:
|
||||
if (ab8500->irq_base)
|
||||
ab8500_irq_remove(ab8500);
|
||||
free_irq(ab8500->irq, ab8500);
|
||||
out_freeoldmask:
|
||||
kfree(ab8500->oldmask);
|
||||
out_freemask:
|
||||
|
|
@ -1444,11 +1480,10 @@ static int __devexit ab8500_remove(struct platform_device *pdev)
|
|||
sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group);
|
||||
else
|
||||
sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group);
|
||||
|
||||
mfd_remove_devices(ab8500->dev);
|
||||
if (ab8500->irq_base) {
|
||||
free_irq(ab8500->irq, ab8500);
|
||||
ab8500_irq_remove(ab8500);
|
||||
}
|
||||
free_irq(ab8500->irq, ab8500);
|
||||
|
||||
kfree(ab8500->oldmask);
|
||||
kfree(ab8500->mask);
|
||||
kfree(ab8500);
|
||||
|
|
@ -1468,7 +1503,6 @@ static struct platform_driver ab8500_core_driver = {
|
|||
.driver = {
|
||||
.name = "ab8500-core",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ab8500_match,
|
||||
},
|
||||
.probe = ab8500_probe,
|
||||
.remove = __devexit_p(ab8500_remove),
|
||||
|
|
@ -1484,7 +1518,7 @@ static void __exit ab8500_core_exit(void)
|
|||
{
|
||||
platform_driver_unregister(&ab8500_core_driver);
|
||||
}
|
||||
arch_initcall(ab8500_core_init);
|
||||
core_initcall(ab8500_core_init);
|
||||
module_exit(ab8500_core_exit);
|
||||
|
||||
MODULE_AUTHOR("Mattias Wallin, Srinidhi Kasagar, Rabin Vincent");
|
||||
|
|
|
|||
|
|
@ -31,12 +31,12 @@ struct ab8500_reg_range {
|
|||
};
|
||||
|
||||
/**
|
||||
* struct ab8500_i2c_ranges
|
||||
* struct ab8500_prcmu_ranges
|
||||
* @num_ranges: the number of ranges in the list
|
||||
* @bankid: bank identifier
|
||||
* @range: the list of register ranges
|
||||
*/
|
||||
struct ab8500_i2c_ranges {
|
||||
struct ab8500_prcmu_ranges {
|
||||
u8 num_ranges;
|
||||
u8 bankid;
|
||||
const struct ab8500_reg_range *range;
|
||||
|
|
@ -47,7 +47,7 @@ struct ab8500_i2c_ranges {
|
|||
|
||||
#define AB8500_REV_REG 0x80
|
||||
|
||||
static struct ab8500_i2c_ranges debug_ranges[AB8500_NUM_BANKS] = {
|
||||
static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = {
|
||||
[0x0] = {
|
||||
.num_ranges = 0,
|
||||
.range = 0,
|
||||
|
|
@ -608,16 +608,10 @@ static int __devexit ab8500_debug_remove(struct platform_device *plf)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ab8500_debug_match[] = {
|
||||
{ .compatible = "stericsson,ab8500-debug", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver ab8500_debug_driver = {
|
||||
.driver = {
|
||||
.name = "ab8500-debug",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ab8500_debug_match,
|
||||
},
|
||||
.probe = ab8500_debug_probe,
|
||||
.remove = __devexit_p(ab8500_debug_remove)
|
||||
|
|
|
|||
|
|
@ -599,7 +599,8 @@ static int __devinit ab8500_gpadc_probe(struct platform_device *pdev)
|
|||
/* Register interrupt - SwAdcComplete */
|
||||
ret = request_threaded_irq(gpadc->irq, NULL,
|
||||
ab8500_bm_gpswadcconvend_handler,
|
||||
IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc", gpadc);
|
||||
IRQF_ONESHOT | IRQF_NO_SUSPEND | IRQF_SHARED,
|
||||
"ab8500-gpadc", gpadc);
|
||||
if (ret < 0) {
|
||||
dev_err(gpadc->dev, "Failed to register interrupt, irq: %d\n",
|
||||
gpadc->irq);
|
||||
|
|
@ -648,18 +649,12 @@ static int __devexit ab8500_gpadc_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ab8500_gpadc_match[] = {
|
||||
{ .compatible = "stericsson,ab8500-gpadc", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver ab8500_gpadc_driver = {
|
||||
.probe = ab8500_gpadc_probe,
|
||||
.remove = __devexit_p(ab8500_gpadc_remove),
|
||||
.driver = {
|
||||
.name = "ab8500-gpadc",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ab8500_gpadc_match,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -61,16 +61,10 @@ static int __devexit ab8500_sysctrl_remove(struct platform_device *pdev)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static const struct of_device_id ab8500_sysctrl_match[] = {
|
||||
{ .compatible = "stericsson,ab8500-sysctrl", },
|
||||
{}
|
||||
};
|
||||
|
||||
static struct platform_driver ab8500_sysctrl_driver = {
|
||||
.driver = {
|
||||
.name = "ab8500-sysctrl",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = ab8500_sysctrl_match,
|
||||
},
|
||||
.probe = ab8500_sysctrl_probe,
|
||||
.remove = __devexit_p(ab8500_sysctrl_remove),
|
||||
|
|
|
|||
|
|
@ -320,7 +320,7 @@ static int __devexit adp5520_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int adp5520_suspend(struct device *dev)
|
||||
{
|
||||
struct i2c_client *client = to_i2c_client(dev);
|
||||
|
|
|
|||
|
|
@ -83,7 +83,7 @@ static int __devinit of_anatop_probe(struct platform_device *pdev)
|
|||
drvdata->ioreg = ioreg;
|
||||
spin_lock_init(&drvdata->reglock);
|
||||
platform_set_drvdata(pdev, drvdata);
|
||||
of_platform_populate(np, of_anatop_match, NULL, dev);
|
||||
of_platform_populate(np, NULL, NULL, dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
566
drivers/mfd/arizona-core.c
Normal file
566
drivers/mfd/arizona-core.c
Normal file
|
|
@ -0,0 +1,566 @@
|
|||
/*
|
||||
* Arizona core driver
|
||||
*
|
||||
* Copyright 2012 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/mfd/arizona/core.h>
|
||||
#include <linux/mfd/arizona/registers.h>
|
||||
|
||||
#include "arizona.h"
|
||||
|
||||
static const char *wm5102_core_supplies[] = {
|
||||
"AVDD",
|
||||
"DBVDD1",
|
||||
};
|
||||
|
||||
int arizona_clk32k_enable(struct arizona *arizona)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&arizona->clk_lock);
|
||||
|
||||
arizona->clk32k_ref++;
|
||||
|
||||
if (arizona->clk32k_ref == 1)
|
||||
ret = regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
|
||||
ARIZONA_CLK_32K_ENA,
|
||||
ARIZONA_CLK_32K_ENA);
|
||||
|
||||
if (ret != 0)
|
||||
arizona->clk32k_ref--;
|
||||
|
||||
mutex_unlock(&arizona->clk_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arizona_clk32k_enable);
|
||||
|
||||
int arizona_clk32k_disable(struct arizona *arizona)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&arizona->clk_lock);
|
||||
|
||||
BUG_ON(arizona->clk32k_ref <= 0);
|
||||
|
||||
arizona->clk32k_ref--;
|
||||
|
||||
if (arizona->clk32k_ref == 0)
|
||||
regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
|
||||
ARIZONA_CLK_32K_ENA, 0);
|
||||
|
||||
mutex_unlock(&arizona->clk_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arizona_clk32k_disable);
|
||||
|
||||
static irqreturn_t arizona_clkgen_err(int irq, void *data)
|
||||
{
|
||||
struct arizona *arizona = data;
|
||||
|
||||
dev_err(arizona->dev, "CLKGEN error\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t arizona_underclocked(int irq, void *data)
|
||||
{
|
||||
struct arizona *arizona = data;
|
||||
unsigned int val;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_8,
|
||||
&val);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to read underclock status: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (val & ARIZONA_AIF3_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "AIF3 underclocked\n");
|
||||
if (val & ARIZONA_AIF3_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "AIF3 underclocked\n");
|
||||
if (val & ARIZONA_AIF2_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "AIF1 underclocked\n");
|
||||
if (val & ARIZONA_ISRC2_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ISRC2 underclocked\n");
|
||||
if (val & ARIZONA_ISRC1_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ISRC1 underclocked\n");
|
||||
if (val & ARIZONA_FX_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "FX underclocked\n");
|
||||
if (val & ARIZONA_ASRC_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ASRC underclocked\n");
|
||||
if (val & ARIZONA_DAC_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "DAC underclocked\n");
|
||||
if (val & ARIZONA_ADC_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ADC underclocked\n");
|
||||
if (val & ARIZONA_MIXER_UNDERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "Mixer underclocked\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t arizona_overclocked(int irq, void *data)
|
||||
{
|
||||
struct arizona *arizona = data;
|
||||
unsigned int val[2];
|
||||
int ret;
|
||||
|
||||
ret = regmap_bulk_read(arizona->regmap, ARIZONA_INTERRUPT_RAW_STATUS_6,
|
||||
&val[0], 2);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to read overclock status: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (val[0] & ARIZONA_PWM_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "PWM overclocked\n");
|
||||
if (val[0] & ARIZONA_FX_CORE_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "FX core overclocked\n");
|
||||
if (val[0] & ARIZONA_DAC_SYS_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "DAC SYS overclocked\n");
|
||||
if (val[0] & ARIZONA_DAC_WARP_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "DAC WARP overclocked\n");
|
||||
if (val[0] & ARIZONA_ADC_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ADC overclocked\n");
|
||||
if (val[0] & ARIZONA_MIXER_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "Mixer overclocked\n");
|
||||
if (val[0] & ARIZONA_AIF3_SYNC_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "AIF3 overclocked\n");
|
||||
if (val[0] & ARIZONA_AIF2_SYNC_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "AIF2 overclocked\n");
|
||||
if (val[0] & ARIZONA_AIF1_SYNC_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "AIF1 overclocked\n");
|
||||
if (val[0] & ARIZONA_PAD_CTRL_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "Pad control overclocked\n");
|
||||
|
||||
if (val[1] & ARIZONA_SLIMBUS_SUBSYS_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "Slimbus subsystem overclocked\n");
|
||||
if (val[1] & ARIZONA_SLIMBUS_ASYNC_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "Slimbus async overclocked\n");
|
||||
if (val[1] & ARIZONA_SLIMBUS_SYNC_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "Slimbus sync overclocked\n");
|
||||
if (val[1] & ARIZONA_ASRC_ASYNC_SYS_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ASRC async system overclocked\n");
|
||||
if (val[1] & ARIZONA_ASRC_ASYNC_WARP_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ASRC async WARP overclocked\n");
|
||||
if (val[1] & ARIZONA_ASRC_SYNC_SYS_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ASRC sync system overclocked\n");
|
||||
if (val[1] & ARIZONA_ASRC_SYNC_WARP_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ASRC sync WARP overclocked\n");
|
||||
if (val[1] & ARIZONA_ADSP2_1_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "DSP1 overclocked\n");
|
||||
if (val[1] & ARIZONA_ISRC2_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ISRC2 overclocked\n");
|
||||
if (val[1] & ARIZONA_ISRC1_OVERCLOCKED_STS)
|
||||
dev_err(arizona->dev, "ISRC1 overclocked\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int arizona_wait_for_boot(struct arizona *arizona)
|
||||
{
|
||||
unsigned int reg;
|
||||
int ret, i;
|
||||
|
||||
/*
|
||||
* We can't use an interrupt as we need to runtime resume to do so,
|
||||
* we won't race with the interrupt handler as it'll be blocked on
|
||||
* runtime resume.
|
||||
*/
|
||||
for (i = 0; i < 5; i++) {
|
||||
msleep(1);
|
||||
|
||||
ret = regmap_read(arizona->regmap,
|
||||
ARIZONA_INTERRUPT_RAW_STATUS_5, ®);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to read boot state: %d\n",
|
||||
ret);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (reg & ARIZONA_BOOT_DONE_STS)
|
||||
break;
|
||||
}
|
||||
|
||||
if (reg & ARIZONA_BOOT_DONE_STS) {
|
||||
regmap_write(arizona->regmap, ARIZONA_INTERRUPT_STATUS_5,
|
||||
ARIZONA_BOOT_DONE_STS);
|
||||
} else {
|
||||
dev_err(arizona->dev, "Device boot timed out: %x\n", reg);
|
||||
return -ETIMEDOUT;
|
||||
}
|
||||
|
||||
pm_runtime_mark_last_busy(arizona->dev);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
static int arizona_runtime_resume(struct device *dev)
|
||||
{
|
||||
struct arizona *arizona = dev_get_drvdata(dev);
|
||||
int ret;
|
||||
|
||||
dev_dbg(arizona->dev, "Leaving AoD mode\n");
|
||||
|
||||
ret = regulator_enable(arizona->dcvdd);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to enable DCVDD: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
regcache_cache_only(arizona->regmap, false);
|
||||
|
||||
ret = arizona_wait_for_boot(arizona);
|
||||
if (ret != 0) {
|
||||
regulator_disable(arizona->dcvdd);
|
||||
return ret;
|
||||
}
|
||||
|
||||
regcache_sync(arizona->regmap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int arizona_runtime_suspend(struct device *dev)
|
||||
{
|
||||
struct arizona *arizona = dev_get_drvdata(dev);
|
||||
|
||||
dev_dbg(arizona->dev, "Entering AoD mode\n");
|
||||
|
||||
regulator_disable(arizona->dcvdd);
|
||||
regcache_cache_only(arizona->regmap, true);
|
||||
regcache_mark_dirty(arizona->regmap);
|
||||
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
const struct dev_pm_ops arizona_pm_ops = {
|
||||
SET_RUNTIME_PM_OPS(arizona_runtime_suspend,
|
||||
arizona_runtime_resume,
|
||||
NULL)
|
||||
};
|
||||
EXPORT_SYMBOL_GPL(arizona_pm_ops);
|
||||
|
||||
static struct mfd_cell early_devs[] = {
|
||||
{ .name = "arizona-ldo1" },
|
||||
};
|
||||
|
||||
static struct mfd_cell wm5102_devs[] = {
|
||||
{ .name = "arizona-extcon" },
|
||||
{ .name = "arizona-gpio" },
|
||||
{ .name = "arizona-micsupp" },
|
||||
{ .name = "arizona-pwm" },
|
||||
{ .name = "wm5102-codec" },
|
||||
};
|
||||
|
||||
static struct mfd_cell wm5110_devs[] = {
|
||||
{ .name = "arizona-extcon" },
|
||||
{ .name = "arizona-gpio" },
|
||||
{ .name = "arizona-micsupp" },
|
||||
{ .name = "arizona-pwm" },
|
||||
{ .name = "wm5110-codec" },
|
||||
};
|
||||
|
||||
int __devinit arizona_dev_init(struct arizona *arizona)
|
||||
{
|
||||
struct device *dev = arizona->dev;
|
||||
const char *type_name;
|
||||
unsigned int reg, val;
|
||||
int ret, i;
|
||||
|
||||
dev_set_drvdata(arizona->dev, arizona);
|
||||
mutex_init(&arizona->clk_lock);
|
||||
|
||||
if (dev_get_platdata(arizona->dev))
|
||||
memcpy(&arizona->pdata, dev_get_platdata(arizona->dev),
|
||||
sizeof(arizona->pdata));
|
||||
|
||||
regcache_cache_only(arizona->regmap, true);
|
||||
|
||||
switch (arizona->type) {
|
||||
case WM5102:
|
||||
case WM5110:
|
||||
for (i = 0; i < ARRAY_SIZE(wm5102_core_supplies); i++)
|
||||
arizona->core_supplies[i].supply
|
||||
= wm5102_core_supplies[i];
|
||||
arizona->num_core_supplies = ARRAY_SIZE(wm5102_core_supplies);
|
||||
break;
|
||||
default:
|
||||
dev_err(arizona->dev, "Unknown device type %d\n",
|
||||
arizona->type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
ret = mfd_add_devices(arizona->dev, -1, early_devs,
|
||||
ARRAY_SIZE(early_devs), NULL, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to add early children: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = devm_regulator_bulk_get(dev, arizona->num_core_supplies,
|
||||
arizona->core_supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to request core supplies: %d\n",
|
||||
ret);
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
arizona->dcvdd = devm_regulator_get(arizona->dev, "DCVDD");
|
||||
if (IS_ERR(arizona->dcvdd)) {
|
||||
ret = PTR_ERR(arizona->dcvdd);
|
||||
dev_err(dev, "Failed to request DCVDD: %d\n", ret);
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
ret = regulator_bulk_enable(arizona->num_core_supplies,
|
||||
arizona->core_supplies);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to enable core supplies: %d\n",
|
||||
ret);
|
||||
goto err_early;
|
||||
}
|
||||
|
||||
ret = regulator_enable(arizona->dcvdd);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to enable DCVDD: %d\n", ret);
|
||||
goto err_enable;
|
||||
}
|
||||
|
||||
if (arizona->pdata.reset) {
|
||||
/* Start out with /RESET low to put the chip into reset */
|
||||
ret = gpio_request_one(arizona->pdata.reset,
|
||||
GPIOF_DIR_OUT | GPIOF_INIT_LOW,
|
||||
"arizona /RESET");
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to request /RESET: %d\n", ret);
|
||||
goto err_dcvdd;
|
||||
}
|
||||
|
||||
gpio_set_value_cansleep(arizona->pdata.reset, 1);
|
||||
}
|
||||
|
||||
regcache_cache_only(arizona->regmap, false);
|
||||
|
||||
ret = regmap_read(arizona->regmap, ARIZONA_SOFTWARE_RESET, ®);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to read ID register: %d\n", ret);
|
||||
goto err_reset;
|
||||
}
|
||||
|
||||
ret = regmap_read(arizona->regmap, ARIZONA_DEVICE_REVISION,
|
||||
&arizona->rev);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to read revision register: %d\n", ret);
|
||||
goto err_reset;
|
||||
}
|
||||
arizona->rev &= ARIZONA_DEVICE_REVISION_MASK;
|
||||
|
||||
switch (reg) {
|
||||
#ifdef CONFIG_MFD_WM5102
|
||||
case 0x5102:
|
||||
type_name = "WM5102";
|
||||
if (arizona->type != WM5102) {
|
||||
dev_err(arizona->dev, "WM5102 registered as %d\n",
|
||||
arizona->type);
|
||||
arizona->type = WM5102;
|
||||
}
|
||||
ret = wm5102_patch(arizona);
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM5110
|
||||
case 0x5110:
|
||||
type_name = "WM5110";
|
||||
if (arizona->type != WM5110) {
|
||||
dev_err(arizona->dev, "WM5110 registered as %d\n",
|
||||
arizona->type);
|
||||
arizona->type = WM5110;
|
||||
}
|
||||
ret = wm5110_patch(arizona);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
dev_err(arizona->dev, "Unknown device ID %x\n", reg);
|
||||
goto err_reset;
|
||||
}
|
||||
|
||||
dev_info(dev, "%s revision %c\n", type_name, arizona->rev + 'A');
|
||||
|
||||
if (ret != 0)
|
||||
dev_err(arizona->dev, "Failed to apply patch: %d\n", ret);
|
||||
|
||||
/* If we have a /RESET GPIO we'll already be reset */
|
||||
if (!arizona->pdata.reset) {
|
||||
ret = regmap_write(arizona->regmap, ARIZONA_SOFTWARE_RESET, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(dev, "Failed to reset device: %d\n", ret);
|
||||
goto err_reset;
|
||||
}
|
||||
}
|
||||
|
||||
ret = arizona_wait_for_boot(arizona);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Device failed initial boot: %d\n", ret);
|
||||
goto err_reset;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(arizona->pdata.gpio_defaults); i++) {
|
||||
if (!arizona->pdata.gpio_defaults[i])
|
||||
continue;
|
||||
|
||||
regmap_write(arizona->regmap, ARIZONA_GPIO1_CTRL + i,
|
||||
arizona->pdata.gpio_defaults[i]);
|
||||
}
|
||||
|
||||
pm_runtime_set_autosuspend_delay(arizona->dev, 100);
|
||||
pm_runtime_use_autosuspend(arizona->dev);
|
||||
pm_runtime_enable(arizona->dev);
|
||||
|
||||
/* Chip default */
|
||||
if (!arizona->pdata.clk32k_src)
|
||||
arizona->pdata.clk32k_src = ARIZONA_32KZ_MCLK2;
|
||||
|
||||
switch (arizona->pdata.clk32k_src) {
|
||||
case ARIZONA_32KZ_MCLK1:
|
||||
case ARIZONA_32KZ_MCLK2:
|
||||
regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
|
||||
ARIZONA_CLK_32K_SRC_MASK,
|
||||
arizona->pdata.clk32k_src - 1);
|
||||
break;
|
||||
case ARIZONA_32KZ_NONE:
|
||||
regmap_update_bits(arizona->regmap, ARIZONA_CLOCK_32K_1,
|
||||
ARIZONA_CLK_32K_SRC_MASK, 2);
|
||||
break;
|
||||
default:
|
||||
dev_err(arizona->dev, "Invalid 32kHz clock source: %d\n",
|
||||
arizona->pdata.clk32k_src);
|
||||
ret = -EINVAL;
|
||||
goto err_reset;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARIZONA_MAX_INPUT; i++) {
|
||||
/* Default for both is 0 so noop with defaults */
|
||||
val = arizona->pdata.dmic_ref[i]
|
||||
<< ARIZONA_IN1_DMIC_SUP_SHIFT;
|
||||
val |= arizona->pdata.inmode[i] << ARIZONA_IN1_MODE_SHIFT;
|
||||
|
||||
regmap_update_bits(arizona->regmap,
|
||||
ARIZONA_IN1L_CONTROL + (i * 8),
|
||||
ARIZONA_IN1_DMIC_SUP_MASK |
|
||||
ARIZONA_IN1_MODE_MASK, val);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARIZONA_MAX_OUTPUT; i++) {
|
||||
/* Default is 0 so noop with defaults */
|
||||
if (arizona->pdata.out_mono[i])
|
||||
val = ARIZONA_OUT1_MONO;
|
||||
else
|
||||
val = 0;
|
||||
|
||||
regmap_update_bits(arizona->regmap,
|
||||
ARIZONA_OUTPUT_PATH_CONFIG_1L + (i * 8),
|
||||
ARIZONA_OUT1_MONO, val);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARIZONA_MAX_PDM_SPK; i++) {
|
||||
if (arizona->pdata.spk_mute[i])
|
||||
regmap_update_bits(arizona->regmap,
|
||||
ARIZONA_PDM_SPK1_CTRL_1 + (i * 2),
|
||||
ARIZONA_SPK1_MUTE_ENDIAN_MASK |
|
||||
ARIZONA_SPK1_MUTE_SEQ1_MASK,
|
||||
arizona->pdata.spk_mute[i]);
|
||||
|
||||
if (arizona->pdata.spk_fmt[i])
|
||||
regmap_update_bits(arizona->regmap,
|
||||
ARIZONA_PDM_SPK1_CTRL_2 + (i * 2),
|
||||
ARIZONA_SPK1_FMT_MASK,
|
||||
arizona->pdata.spk_fmt[i]);
|
||||
}
|
||||
|
||||
/* Set up for interrupts */
|
||||
ret = arizona_irq_init(arizona);
|
||||
if (ret != 0)
|
||||
goto err_reset;
|
||||
|
||||
arizona_request_irq(arizona, ARIZONA_IRQ_CLKGEN_ERR, "CLKGEN error",
|
||||
arizona_clkgen_err, arizona);
|
||||
arizona_request_irq(arizona, ARIZONA_IRQ_OVERCLOCKED, "Overclocked",
|
||||
arizona_overclocked, arizona);
|
||||
arizona_request_irq(arizona, ARIZONA_IRQ_UNDERCLOCKED, "Underclocked",
|
||||
arizona_underclocked, arizona);
|
||||
|
||||
switch (arizona->type) {
|
||||
case WM5102:
|
||||
ret = mfd_add_devices(arizona->dev, -1, wm5102_devs,
|
||||
ARRAY_SIZE(wm5102_devs), NULL, 0);
|
||||
break;
|
||||
case WM5110:
|
||||
ret = mfd_add_devices(arizona->dev, -1, wm5110_devs,
|
||||
ARRAY_SIZE(wm5102_devs), NULL, 0);
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to add subdevices: %d\n", ret);
|
||||
goto err_irq;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM_RUNTIME
|
||||
regulator_disable(arizona->dcvdd);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
err_irq:
|
||||
arizona_irq_exit(arizona);
|
||||
err_reset:
|
||||
if (arizona->pdata.reset) {
|
||||
gpio_set_value_cansleep(arizona->pdata.reset, 1);
|
||||
gpio_free(arizona->pdata.reset);
|
||||
}
|
||||
err_dcvdd:
|
||||
regulator_disable(arizona->dcvdd);
|
||||
err_enable:
|
||||
regulator_bulk_disable(arizona->num_core_supplies,
|
||||
arizona->core_supplies);
|
||||
err_early:
|
||||
mfd_remove_devices(dev);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arizona_dev_init);
|
||||
|
||||
int __devexit arizona_dev_exit(struct arizona *arizona)
|
||||
{
|
||||
mfd_remove_devices(arizona->dev);
|
||||
arizona_free_irq(arizona, ARIZONA_IRQ_UNDERCLOCKED, arizona);
|
||||
arizona_free_irq(arizona, ARIZONA_IRQ_OVERCLOCKED, arizona);
|
||||
arizona_free_irq(arizona, ARIZONA_IRQ_CLKGEN_ERR, arizona);
|
||||
pm_runtime_disable(arizona->dev);
|
||||
arizona_irq_exit(arizona);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arizona_dev_exit);
|
||||
97
drivers/mfd/arizona-i2c.c
Normal file
97
drivers/mfd/arizona-i2c.c
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* Arizona-i2c.c -- Arizona I2C bus interface
|
||||
*
|
||||
* Copyright 2012 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/mfd/arizona/core.h>
|
||||
|
||||
#include "arizona.h"
|
||||
|
||||
static __devinit int arizona_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct arizona *arizona;
|
||||
const struct regmap_config *regmap_config;
|
||||
int ret;
|
||||
|
||||
switch (id->driver_data) {
|
||||
#ifdef CONFIG_MFD_WM5102
|
||||
case WM5102:
|
||||
regmap_config = &wm5102_i2c_regmap;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM5110
|
||||
case WM5110:
|
||||
regmap_config = &wm5110_i2c_regmap;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
dev_err(&i2c->dev, "Unknown device type %ld\n",
|
||||
id->driver_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
arizona = devm_kzalloc(&i2c->dev, sizeof(*arizona), GFP_KERNEL);
|
||||
if (arizona == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
arizona->regmap = devm_regmap_init_i2c(i2c, regmap_config);
|
||||
if (IS_ERR(arizona->regmap)) {
|
||||
ret = PTR_ERR(arizona->regmap);
|
||||
dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
arizona->type = id->driver_data;
|
||||
arizona->dev = &i2c->dev;
|
||||
arizona->irq = i2c->irq;
|
||||
|
||||
return arizona_dev_init(arizona);
|
||||
}
|
||||
|
||||
static int __devexit arizona_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct arizona *arizona = dev_get_drvdata(&i2c->dev);
|
||||
arizona_dev_exit(arizona);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id arizona_i2c_id[] = {
|
||||
{ "wm5102", WM5102 },
|
||||
{ "wm5110", WM5110 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, arizona_i2c_id);
|
||||
|
||||
static struct i2c_driver arizona_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "arizona",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &arizona_pm_ops,
|
||||
},
|
||||
.probe = arizona_i2c_probe,
|
||||
.remove = __devexit_p(arizona_i2c_remove),
|
||||
.id_table = arizona_i2c_id,
|
||||
};
|
||||
|
||||
module_i2c_driver(arizona_i2c_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Arizona I2C bus interface");
|
||||
MODULE_AUTHOR("Mark Brown <[email protected]>");
|
||||
MODULE_LICENSE("GPL");
|
||||
275
drivers/mfd/arizona-irq.c
Normal file
275
drivers/mfd/arizona-irq.c
Normal file
|
|
@ -0,0 +1,275 @@
|
|||
/*
|
||||
* Arizona interrupt support
|
||||
*
|
||||
* Copyright 2012 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/delay.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
#include <linux/mfd/arizona/core.h>
|
||||
#include <linux/mfd/arizona/registers.h>
|
||||
|
||||
#include "arizona.h"
|
||||
|
||||
static int arizona_map_irq(struct arizona *arizona, int irq)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = regmap_irq_get_virq(arizona->aod_irq_chip, irq);
|
||||
if (ret < 0)
|
||||
ret = regmap_irq_get_virq(arizona->irq_chip, irq);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int arizona_request_irq(struct arizona *arizona, int irq, char *name,
|
||||
irq_handler_t handler, void *data)
|
||||
{
|
||||
irq = arizona_map_irq(arizona, irq);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
return request_threaded_irq(irq, NULL, handler, IRQF_ONESHOT,
|
||||
name, data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arizona_request_irq);
|
||||
|
||||
void arizona_free_irq(struct arizona *arizona, int irq, void *data)
|
||||
{
|
||||
irq = arizona_map_irq(arizona, irq);
|
||||
if (irq < 0)
|
||||
return;
|
||||
|
||||
free_irq(irq, data);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arizona_free_irq);
|
||||
|
||||
int arizona_set_irq_wake(struct arizona *arizona, int irq, int on)
|
||||
{
|
||||
irq = arizona_map_irq(arizona, irq);
|
||||
if (irq < 0)
|
||||
return irq;
|
||||
|
||||
return irq_set_irq_wake(irq, on);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(arizona_set_irq_wake);
|
||||
|
||||
static irqreturn_t arizona_boot_done(int irq, void *data)
|
||||
{
|
||||
struct arizona *arizona = data;
|
||||
|
||||
dev_dbg(arizona->dev, "Boot done\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t arizona_ctrlif_err(int irq, void *data)
|
||||
{
|
||||
struct arizona *arizona = data;
|
||||
|
||||
/*
|
||||
* For pretty much all potential sources a register cache sync
|
||||
* won't help, we've just got a software bug somewhere.
|
||||
*/
|
||||
dev_err(arizona->dev, "Control interface error\n");
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t arizona_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct arizona *arizona = data;
|
||||
int i, ret;
|
||||
|
||||
ret = pm_runtime_get_sync(arizona->dev);
|
||||
if (ret < 0) {
|
||||
dev_err(arizona->dev, "Failed to resume device: %d\n", ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
/* Check both domains */
|
||||
for (i = 0; i < 2; i++)
|
||||
handle_nested_irq(irq_find_mapping(arizona->virq, i));
|
||||
|
||||
pm_runtime_mark_last_busy(arizona->dev);
|
||||
pm_runtime_put_autosuspend(arizona->dev);
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static void arizona_irq_enable(struct irq_data *data)
|
||||
{
|
||||
}
|
||||
|
||||
static void arizona_irq_disable(struct irq_data *data)
|
||||
{
|
||||
}
|
||||
|
||||
static struct irq_chip arizona_irq_chip = {
|
||||
.name = "arizona",
|
||||
.irq_disable = arizona_irq_disable,
|
||||
.irq_enable = arizona_irq_enable,
|
||||
};
|
||||
|
||||
static int arizona_irq_map(struct irq_domain *h, unsigned int virq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct regmap_irq_chip_data *data = h->host_data;
|
||||
|
||||
irq_set_chip_data(virq, data);
|
||||
irq_set_chip_and_handler(virq, &arizona_irq_chip, handle_edge_irq);
|
||||
irq_set_nested_thread(virq, 1);
|
||||
|
||||
/* ARM needs us to explicitly flag the IRQ as valid
|
||||
* and will set them noprobe when we do so. */
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(virq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(virq);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_domain_ops arizona_domain_ops = {
|
||||
.map = arizona_irq_map,
|
||||
.xlate = irq_domain_xlate_twocell,
|
||||
};
|
||||
|
||||
int arizona_irq_init(struct arizona *arizona)
|
||||
{
|
||||
int flags = IRQF_ONESHOT;
|
||||
int ret, i;
|
||||
const struct regmap_irq_chip *aod, *irq;
|
||||
|
||||
switch (arizona->type) {
|
||||
#ifdef CONFIG_MFD_WM5102
|
||||
case WM5102:
|
||||
aod = &wm5102_aod;
|
||||
irq = &wm5102_irq;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM5110
|
||||
case WM5110:
|
||||
aod = &wm5110_aod;
|
||||
irq = &wm5110_irq;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
BUG_ON("Unknown Arizona class device" == NULL);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (arizona->pdata.irq_active_high) {
|
||||
ret = regmap_update_bits(arizona->regmap, ARIZONA_IRQ_CTRL_1,
|
||||
ARIZONA_IRQ_POL, 0);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Couldn't set IRQ polarity: %d\n",
|
||||
ret);
|
||||
goto err;
|
||||
}
|
||||
|
||||
flags |= IRQF_TRIGGER_HIGH;
|
||||
} else {
|
||||
flags |= IRQF_TRIGGER_LOW;
|
||||
}
|
||||
|
||||
/* Allocate a virtual IRQ domain to distribute to the regmap domains */
|
||||
arizona->virq = irq_domain_add_linear(NULL, 2, &arizona_domain_ops,
|
||||
arizona);
|
||||
if (!arizona->virq) {
|
||||
ret = -EINVAL;
|
||||
goto err;
|
||||
}
|
||||
|
||||
ret = regmap_add_irq_chip(arizona->regmap,
|
||||
irq_create_mapping(arizona->virq, 0),
|
||||
IRQF_ONESHOT, -1, aod,
|
||||
&arizona->aod_irq_chip);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to add AOD IRQs: %d\n", ret);
|
||||
goto err_domain;
|
||||
}
|
||||
|
||||
ret = regmap_add_irq_chip(arizona->regmap,
|
||||
irq_create_mapping(arizona->virq, 1),
|
||||
IRQF_ONESHOT, -1, irq,
|
||||
&arizona->irq_chip);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to add AOD IRQs: %d\n", ret);
|
||||
goto err_aod;
|
||||
}
|
||||
|
||||
/* Make sure the boot done IRQ is unmasked for resumes */
|
||||
i = arizona_map_irq(arizona, ARIZONA_IRQ_BOOT_DONE);
|
||||
ret = request_threaded_irq(i, NULL, arizona_boot_done, IRQF_ONESHOT,
|
||||
"Boot done", arizona);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to request boot done %d: %d\n",
|
||||
arizona->irq, ret);
|
||||
goto err_boot_done;
|
||||
}
|
||||
|
||||
/* Handle control interface errors in the core */
|
||||
i = arizona_map_irq(arizona, ARIZONA_IRQ_CTRLIF_ERR);
|
||||
ret = request_threaded_irq(i, NULL, arizona_ctrlif_err, IRQF_ONESHOT,
|
||||
"Control interface error", arizona);
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to request boot done %d: %d\n",
|
||||
arizona->irq, ret);
|
||||
goto err_ctrlif;
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(arizona->irq, NULL, arizona_irq_thread,
|
||||
flags, "arizona", arizona);
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(arizona->dev, "Failed to request IRQ %d: %d\n",
|
||||
arizona->irq, ret);
|
||||
goto err_main_irq;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
err_main_irq:
|
||||
free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_CTRLIF_ERR), arizona);
|
||||
err_ctrlif:
|
||||
free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_BOOT_DONE), arizona);
|
||||
err_boot_done:
|
||||
regmap_del_irq_chip(irq_create_mapping(arizona->virq, 1),
|
||||
arizona->irq_chip);
|
||||
err_aod:
|
||||
regmap_del_irq_chip(irq_create_mapping(arizona->virq, 0),
|
||||
arizona->aod_irq_chip);
|
||||
err_domain:
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
||||
int arizona_irq_exit(struct arizona *arizona)
|
||||
{
|
||||
free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_CTRLIF_ERR), arizona);
|
||||
free_irq(arizona_map_irq(arizona, ARIZONA_IRQ_BOOT_DONE), arizona);
|
||||
regmap_del_irq_chip(irq_create_mapping(arizona->virq, 1),
|
||||
arizona->irq_chip);
|
||||
regmap_del_irq_chip(irq_create_mapping(arizona->virq, 0),
|
||||
arizona->aod_irq_chip);
|
||||
free_irq(arizona->irq, arizona);
|
||||
|
||||
return 0;
|
||||
}
|
||||
97
drivers/mfd/arizona-spi.c
Normal file
97
drivers/mfd/arizona-spi.c
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
* arizona-spi.c -- Arizona SPI bus interface
|
||||
*
|
||||
* Copyright 2012 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/consumer.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/spi/spi.h>
|
||||
|
||||
#include <linux/mfd/arizona/core.h>
|
||||
|
||||
#include "arizona.h"
|
||||
|
||||
static int __devinit arizona_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct spi_device_id *id = spi_get_device_id(spi);
|
||||
struct arizona *arizona;
|
||||
const struct regmap_config *regmap_config;
|
||||
int ret;
|
||||
|
||||
switch (id->driver_data) {
|
||||
#ifdef CONFIG_MFD_WM5102
|
||||
case WM5102:
|
||||
regmap_config = &wm5102_spi_regmap;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM5110
|
||||
case WM5110:
|
||||
regmap_config = &wm5110_spi_regmap;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
dev_err(&spi->dev, "Unknown device type %ld\n",
|
||||
id->driver_data);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
arizona = devm_kzalloc(&spi->dev, sizeof(*arizona), GFP_KERNEL);
|
||||
if (arizona == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
arizona->regmap = devm_regmap_init_spi(spi, regmap_config);
|
||||
if (IS_ERR(arizona->regmap)) {
|
||||
ret = PTR_ERR(arizona->regmap);
|
||||
dev_err(&spi->dev, "Failed to allocate register map: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
arizona->type = id->driver_data;
|
||||
arizona->dev = &spi->dev;
|
||||
arizona->irq = spi->irq;
|
||||
|
||||
return arizona_dev_init(arizona);
|
||||
}
|
||||
|
||||
static int __devexit arizona_spi_remove(struct spi_device *spi)
|
||||
{
|
||||
struct arizona *arizona = dev_get_drvdata(&spi->dev);
|
||||
arizona_dev_exit(arizona);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct spi_device_id arizona_spi_ids[] = {
|
||||
{ "wm5102", WM5102 },
|
||||
{ "wm5110", WM5110 },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(spi, arizona_spi_ids);
|
||||
|
||||
static struct spi_driver arizona_spi_driver = {
|
||||
.driver = {
|
||||
.name = "arizona",
|
||||
.owner = THIS_MODULE,
|
||||
.pm = &arizona_pm_ops,
|
||||
},
|
||||
.probe = arizona_spi_probe,
|
||||
.remove = __devexit_p(arizona_spi_remove),
|
||||
.id_table = arizona_spi_ids,
|
||||
};
|
||||
|
||||
module_spi_driver(arizona_spi_driver);
|
||||
|
||||
MODULE_DESCRIPTION("Arizona SPI bus interface");
|
||||
MODULE_AUTHOR("Mark Brown <[email protected]>");
|
||||
MODULE_LICENSE("GPL");
|
||||
40
drivers/mfd/arizona.h
Normal file
40
drivers/mfd/arizona.h
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* wm5102.h -- WM5102 MFD internals
|
||||
*
|
||||
* Copyright 2012 Wolfson Microelectronics plc
|
||||
*
|
||||
* Author: Mark Brown <broonie@opensource.wolfsonmicro.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef _WM5102_H
|
||||
#define _WM5102_H
|
||||
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/pm.h>
|
||||
|
||||
struct wm_arizona;
|
||||
|
||||
extern const struct regmap_config wm5102_i2c_regmap;
|
||||
extern const struct regmap_config wm5102_spi_regmap;
|
||||
|
||||
extern const struct regmap_config wm5110_i2c_regmap;
|
||||
extern const struct regmap_config wm5110_spi_regmap;
|
||||
|
||||
extern const struct dev_pm_ops arizona_pm_ops;
|
||||
|
||||
extern const struct regmap_irq_chip wm5102_aod;
|
||||
extern const struct regmap_irq_chip wm5102_irq;
|
||||
|
||||
extern const struct regmap_irq_chip wm5110_aod;
|
||||
extern const struct regmap_irq_chip wm5110_irq;
|
||||
|
||||
int arizona_dev_init(struct arizona *arizona);
|
||||
int arizona_dev_exit(struct arizona *arizona);
|
||||
int arizona_irq_init(struct arizona *arizona);
|
||||
int arizona_irq_exit(struct arizona *arizona);
|
||||
|
||||
#endif
|
||||
|
|
@ -772,7 +772,6 @@ EXPORT_SYMBOL_GPL(da9052_regmap_config);
|
|||
int __devinit da9052_device_init(struct da9052 *da9052, u8 chip_id)
|
||||
{
|
||||
struct da9052_pdata *pdata = da9052->dev->platform_data;
|
||||
struct irq_desc *desc;
|
||||
int ret;
|
||||
|
||||
mutex_init(&da9052->auxadc_lock);
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@
|
|||
#include <linux/uaccess.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/dbx500-prcmu.h>
|
||||
#include <linux/mfd/abx500/ab8500.h>
|
||||
#include <linux/regulator/db8500-prcmu.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <asm/hardware/gic.h>
|
||||
|
|
@ -2269,10 +2270,10 @@ int prcmu_abb_write(u8 slave, u8 reg, u8 *value, u8 size)
|
|||
/**
|
||||
* prcmu_ac_wake_req - should be called whenever ARM wants to wakeup Modem
|
||||
*/
|
||||
void prcmu_ac_wake_req(void)
|
||||
int prcmu_ac_wake_req(void)
|
||||
{
|
||||
u32 val;
|
||||
u32 status;
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&mb0_transfer.ac_wake_lock);
|
||||
|
||||
|
|
@ -2282,39 +2283,32 @@ void prcmu_ac_wake_req(void)
|
|||
|
||||
atomic_set(&ac_wake_req_state, 1);
|
||||
|
||||
retry:
|
||||
writel((val | PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ), PRCM_HOSTACCESS_REQ);
|
||||
/*
|
||||
* Force Modem Wake-up before hostaccess_req ping-pong.
|
||||
* It prevents Modem to enter in Sleep while acking the hostaccess
|
||||
* request. The 31us delay has been calculated by HWI.
|
||||
*/
|
||||
val |= PRCM_HOSTACCESS_REQ_WAKE_REQ;
|
||||
writel(val, PRCM_HOSTACCESS_REQ);
|
||||
|
||||
udelay(31);
|
||||
|
||||
val |= PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ;
|
||||
writel(val, PRCM_HOSTACCESS_REQ);
|
||||
|
||||
if (!wait_for_completion_timeout(&mb0_transfer.ac_wake_work,
|
||||
msecs_to_jiffies(5000))) {
|
||||
#if defined(CONFIG_DBX500_PRCMU_DEBUG)
|
||||
db8500_prcmu_debug_dump(__func__, true, true);
|
||||
#endif
|
||||
pr_crit("prcmu: %s timed out (5 s) waiting for a reply.\n",
|
||||
__func__);
|
||||
goto unlock_and_return;
|
||||
}
|
||||
|
||||
/*
|
||||
* The modem can generate an AC_WAKE_ACK, and then still go to sleep.
|
||||
* As a workaround, we wait, and then check that the modem is indeed
|
||||
* awake (in terms of the value of the PRCM_MOD_AWAKE_STATUS
|
||||
* register, which may not be the whole truth).
|
||||
*/
|
||||
udelay(400);
|
||||
status = (readl(PRCM_MOD_AWAKE_STATUS) & BITS(0, 2));
|
||||
if (status != (PRCM_MOD_AWAKE_STATUS_PRCM_MOD_AAPD_AWAKE |
|
||||
PRCM_MOD_AWAKE_STATUS_PRCM_MOD_COREPD_AWAKE)) {
|
||||
pr_err("prcmu: %s received ack, but modem not awake (0x%X).\n",
|
||||
__func__, status);
|
||||
udelay(1200);
|
||||
writel(val, PRCM_HOSTACCESS_REQ);
|
||||
if (wait_for_completion_timeout(&mb0_transfer.ac_wake_work,
|
||||
msecs_to_jiffies(5000)))
|
||||
goto retry;
|
||||
pr_crit("prcmu: %s timed out (5 s) waiting for AC_SLEEP_ACK.\n",
|
||||
__func__);
|
||||
ret = -EFAULT;
|
||||
}
|
||||
|
||||
unlock_and_return:
|
||||
mutex_unlock(&mb0_transfer.ac_wake_lock);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -2945,14 +2939,31 @@ static struct regulator_init_data db8500_regulators[DB8500_NUM_REGULATORS] = {
|
|||
},
|
||||
};
|
||||
|
||||
static struct resource ab8500_resources[] = {
|
||||
[0] = {
|
||||
.start = IRQ_DB8500_AB8500,
|
||||
.end = IRQ_DB8500_AB8500,
|
||||
.flags = IORESOURCE_IRQ
|
||||
}
|
||||
};
|
||||
|
||||
static struct mfd_cell db8500_prcmu_devs[] = {
|
||||
{
|
||||
.name = "db8500-prcmu-regulators",
|
||||
.of_compatible = "stericsson,db8500-prcmu-regulator",
|
||||
.platform_data = &db8500_regulators,
|
||||
.pdata_size = sizeof(db8500_regulators),
|
||||
},
|
||||
{
|
||||
.name = "cpufreq-u8500",
|
||||
.of_compatible = "stericsson,cpufreq-u8500",
|
||||
},
|
||||
{
|
||||
.name = "ab8500-core",
|
||||
.of_compatible = "stericsson,ab8500",
|
||||
.num_resources = ARRAY_SIZE(ab8500_resources),
|
||||
.resources = ab8500_resources,
|
||||
.id = AB8500_VERSION_AB8500,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -2962,8 +2973,9 @@ static struct mfd_cell db8500_prcmu_devs[] = {
|
|||
*/
|
||||
static int __devinit db8500_prcmu_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct ab8500_platform_data *ab8500_platdata = pdev->dev.platform_data;
|
||||
struct device_node *np = pdev->dev.of_node;
|
||||
int irq = 0, err = 0;
|
||||
int irq = 0, err = 0, i;
|
||||
|
||||
if (ux500_is_svp())
|
||||
return -ENODEV;
|
||||
|
|
@ -2987,16 +2999,21 @@ static int __devinit db8500_prcmu_probe(struct platform_device *pdev)
|
|||
goto no_irq_return;
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(db8500_prcmu_devs); i++) {
|
||||
if (!strcmp(db8500_prcmu_devs[i].name, "ab8500-core")) {
|
||||
db8500_prcmu_devs[i].platform_data = ab8500_platdata;
|
||||
db8500_prcmu_devs[i].pdata_size = sizeof(struct ab8500_platform_data);
|
||||
}
|
||||
}
|
||||
|
||||
if (cpu_is_u8500v20_or_later())
|
||||
prcmu_config_esram0_deep_sleep(ESRAM0_DEEP_SLEEP_STATE_RET);
|
||||
|
||||
if (!np) {
|
||||
err = mfd_add_devices(&pdev->dev, 0, db8500_prcmu_devs,
|
||||
ARRAY_SIZE(db8500_prcmu_devs), NULL, 0);
|
||||
if (err) {
|
||||
pr_err("prcmu: Failed to add subdevices\n");
|
||||
return err;
|
||||
}
|
||||
err = mfd_add_devices(&pdev->dev, 0, db8500_prcmu_devs,
|
||||
ARRAY_SIZE(db8500_prcmu_devs), NULL, 0);
|
||||
if (err) {
|
||||
pr_err("prcmu: Failed to add subdevices\n");
|
||||
return err;
|
||||
}
|
||||
|
||||
pr_info("DB8500 PRCMU initialized\n");
|
||||
|
|
@ -3004,11 +3021,16 @@ static int __devinit db8500_prcmu_probe(struct platform_device *pdev)
|
|||
no_irq_return:
|
||||
return err;
|
||||
}
|
||||
static const struct of_device_id db8500_prcmu_match[] = {
|
||||
{ .compatible = "stericsson,db8500-prcmu"},
|
||||
{ },
|
||||
};
|
||||
|
||||
static struct platform_driver db8500_prcmu_driver = {
|
||||
.driver = {
|
||||
.name = "db8500-prcmu",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = db8500_prcmu_match,
|
||||
},
|
||||
.probe = db8500_prcmu_probe,
|
||||
};
|
||||
|
|
@ -3018,7 +3040,7 @@ static int __init db8500_prcmu_init(void)
|
|||
return platform_driver_register(&db8500_prcmu_driver);
|
||||
}
|
||||
|
||||
arch_initcall(db8500_prcmu_init);
|
||||
core_initcall(db8500_prcmu_init);
|
||||
|
||||
MODULE_AUTHOR("Mattias Nilsson <[email protected]>");
|
||||
MODULE_DESCRIPTION("DB8500 PRCM Unit driver");
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@
|
|||
|
||||
#define PRCM_HOSTACCESS_REQ (_PRCMU_BASE + 0x334)
|
||||
#define PRCM_HOSTACCESS_REQ_HOSTACCESS_REQ 0x1
|
||||
#define PRCM_HOSTACCESS_REQ_WAKE_REQ BIT(16)
|
||||
#define ARM_WAKEUP_MODEM 0x1
|
||||
|
||||
#define PRCM_ARM_IT1_CLR (_PRCMU_BASE + 0x48C)
|
||||
|
|
|
|||
319
drivers/mfd/max77686-irq.c
Normal file
319
drivers/mfd/max77686-irq.c
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
/*
|
||||
* max77686-irq.c - Interrupt controller support for MAX77686
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics Co.Ltd
|
||||
* Chiwoong Byun <woong.byun@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* This driver is based on max8997-irq.c
|
||||
*/
|
||||
|
||||
#include <linux/err.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/mfd/max77686.h>
|
||||
#include <linux/mfd/max77686-private.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
enum {
|
||||
MAX77686_DEBUG_IRQ_INFO = 1 << 0,
|
||||
MAX77686_DEBUG_IRQ_MASK = 1 << 1,
|
||||
MAX77686_DEBUG_IRQ_INT = 1 << 2,
|
||||
};
|
||||
|
||||
static int debug_mask = 0;
|
||||
module_param(debug_mask, int, 0);
|
||||
MODULE_PARM_DESC(debug_mask, "Set debug_mask : 0x0=off 0x1=IRQ_INFO 0x2=IRQ_MASK 0x4=IRQ_INI)");
|
||||
|
||||
static const u8 max77686_mask_reg[] = {
|
||||
[PMIC_INT1] = MAX77686_REG_INT1MSK,
|
||||
[PMIC_INT2] = MAX77686_REG_INT2MSK,
|
||||
[RTC_INT] = MAX77686_RTC_INTM,
|
||||
};
|
||||
|
||||
static struct regmap *max77686_get_regmap(struct max77686_dev *max77686,
|
||||
enum max77686_irq_source src)
|
||||
{
|
||||
switch (src) {
|
||||
case PMIC_INT1 ... PMIC_INT2:
|
||||
return max77686->regmap;
|
||||
case RTC_INT:
|
||||
return max77686->rtc_regmap;
|
||||
default:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
}
|
||||
|
||||
struct max77686_irq_data {
|
||||
int mask;
|
||||
enum max77686_irq_source group;
|
||||
};
|
||||
|
||||
#define DECLARE_IRQ(idx, _group, _mask) \
|
||||
[(idx)] = { .group = (_group), .mask = (_mask) }
|
||||
static const struct max77686_irq_data max77686_irqs[] = {
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_PWRONF, PMIC_INT1, 1 << 0),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_PWRONR, PMIC_INT1, 1 << 1),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_JIGONBF, PMIC_INT1, 1 << 2),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_JIGONBR, PMIC_INT1, 1 << 3),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_ACOKBF, PMIC_INT1, 1 << 4),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_ACOKBR, PMIC_INT1, 1 << 5),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_ONKEY1S, PMIC_INT1, 1 << 6),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_MRSTB, PMIC_INT1, 1 << 7),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_140C, PMIC_INT2, 1 << 0),
|
||||
DECLARE_IRQ(MAX77686_PMICIRQ_120C, PMIC_INT2, 1 << 1),
|
||||
DECLARE_IRQ(MAX77686_RTCIRQ_RTC60S, RTC_INT, 1 << 0),
|
||||
DECLARE_IRQ(MAX77686_RTCIRQ_RTCA1, RTC_INT, 1 << 1),
|
||||
DECLARE_IRQ(MAX77686_RTCIRQ_RTCA2, RTC_INT, 1 << 2),
|
||||
DECLARE_IRQ(MAX77686_RTCIRQ_SMPL, RTC_INT, 1 << 3),
|
||||
DECLARE_IRQ(MAX77686_RTCIRQ_RTC1S, RTC_INT, 1 << 4),
|
||||
DECLARE_IRQ(MAX77686_RTCIRQ_WTSR, RTC_INT, 1 << 5),
|
||||
};
|
||||
|
||||
static void max77686_irq_lock(struct irq_data *data)
|
||||
{
|
||||
struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
|
||||
pr_info("%s\n", __func__);
|
||||
|
||||
mutex_lock(&max77686->irqlock);
|
||||
}
|
||||
|
||||
static void max77686_irq_sync_unlock(struct irq_data *data)
|
||||
{
|
||||
struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) {
|
||||
u8 mask_reg = max77686_mask_reg[i];
|
||||
struct regmap *map = max77686_get_regmap(max77686, i);
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
|
||||
pr_debug("%s: mask_reg[%d]=0x%x, cur=0x%x\n",
|
||||
__func__, i, mask_reg, max77686->irq_masks_cur[i]);
|
||||
|
||||
if (mask_reg == MAX77686_REG_INVALID ||
|
||||
IS_ERR_OR_NULL(map))
|
||||
continue;
|
||||
|
||||
max77686->irq_masks_cache[i] = max77686->irq_masks_cur[i];
|
||||
|
||||
regmap_write(map, max77686_mask_reg[i],
|
||||
max77686->irq_masks_cur[i]);
|
||||
}
|
||||
|
||||
mutex_unlock(&max77686->irqlock);
|
||||
}
|
||||
|
||||
static const inline struct max77686_irq_data *to_max77686_irq(int irq)
|
||||
{
|
||||
struct irq_data *data = irq_get_irq_data(irq);
|
||||
return &max77686_irqs[data->hwirq];
|
||||
}
|
||||
|
||||
static void max77686_irq_mask(struct irq_data *data)
|
||||
{
|
||||
struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
|
||||
const struct max77686_irq_data *irq_data = to_max77686_irq(data->irq);
|
||||
|
||||
max77686->irq_masks_cur[irq_data->group] |= irq_data->mask;
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
|
||||
pr_info("%s: group=%d, cur=0x%x\n",
|
||||
__func__, irq_data->group,
|
||||
max77686->irq_masks_cur[irq_data->group]);
|
||||
}
|
||||
|
||||
static void max77686_irq_unmask(struct irq_data *data)
|
||||
{
|
||||
struct max77686_dev *max77686 = irq_get_chip_data(data->irq);
|
||||
const struct max77686_irq_data *irq_data = to_max77686_irq(data->irq);
|
||||
|
||||
max77686->irq_masks_cur[irq_data->group] &= ~irq_data->mask;
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_MASK)
|
||||
pr_info("%s: group=%d, cur=0x%x\n",
|
||||
__func__, irq_data->group,
|
||||
max77686->irq_masks_cur[irq_data->group]);
|
||||
}
|
||||
|
||||
static struct irq_chip max77686_irq_chip = {
|
||||
.name = "max77686",
|
||||
.irq_bus_lock = max77686_irq_lock,
|
||||
.irq_bus_sync_unlock = max77686_irq_sync_unlock,
|
||||
.irq_mask = max77686_irq_mask,
|
||||
.irq_unmask = max77686_irq_unmask,
|
||||
};
|
||||
|
||||
static irqreturn_t max77686_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct max77686_dev *max77686 = data;
|
||||
unsigned int irq_reg[MAX77686_IRQ_GROUP_NR] = {};
|
||||
unsigned int irq_src;
|
||||
int ret;
|
||||
int i, cur_irq;
|
||||
|
||||
ret = regmap_read(max77686->regmap, MAX77686_REG_INTSRC, &irq_src);
|
||||
if (ret < 0) {
|
||||
dev_err(max77686->dev, "Failed to read interrupt source: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_INT)
|
||||
pr_info("%s: irq_src=0x%x\n", __func__, irq_src);
|
||||
|
||||
if (irq_src == MAX77686_IRQSRC_PMIC) {
|
||||
ret = regmap_bulk_read(max77686->regmap,
|
||||
MAX77686_REG_INT1, irq_reg, 2);
|
||||
if (ret < 0) {
|
||||
dev_err(max77686->dev, "Failed to read interrupt source: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_INT)
|
||||
pr_info("%s: int1=0x%x, int2=0x%x\n", __func__,
|
||||
irq_reg[PMIC_INT1], irq_reg[PMIC_INT2]);
|
||||
}
|
||||
|
||||
if (irq_src & MAX77686_IRQSRC_RTC) {
|
||||
ret = regmap_read(max77686->rtc_regmap,
|
||||
MAX77686_RTC_INT, &irq_reg[RTC_INT]);
|
||||
if (ret < 0) {
|
||||
dev_err(max77686->dev, "Failed to read interrupt source: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_INT)
|
||||
pr_info("%s: rtc int=0x%x\n", __func__,
|
||||
irq_reg[RTC_INT]);
|
||||
|
||||
}
|
||||
|
||||
for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++)
|
||||
irq_reg[i] &= ~max77686->irq_masks_cur[i];
|
||||
|
||||
for (i = 0; i < MAX77686_IRQ_NR; i++) {
|
||||
if (irq_reg[max77686_irqs[i].group] & max77686_irqs[i].mask) {
|
||||
cur_irq = irq_find_mapping(max77686->irq_domain, i);
|
||||
if (cur_irq)
|
||||
handle_nested_irq(cur_irq);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static int max77686_irq_domain_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct max77686_dev *max77686 = d->host_data;
|
||||
|
||||
irq_set_chip_data(irq, max77686);
|
||||
irq_set_chip_and_handler(irq, &max77686_irq_chip, handle_edge_irq);
|
||||
irq_set_nested_thread(irq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(irq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(irq);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_domain_ops max77686_irq_domain_ops = {
|
||||
.map = max77686_irq_domain_map,
|
||||
};
|
||||
|
||||
int max77686_irq_init(struct max77686_dev *max77686)
|
||||
{
|
||||
struct irq_domain *domain;
|
||||
int i;
|
||||
int ret;
|
||||
int val;
|
||||
struct regmap *map;
|
||||
|
||||
mutex_init(&max77686->irqlock);
|
||||
|
||||
if (max77686->irq_gpio && !max77686->irq) {
|
||||
max77686->irq = gpio_to_irq(max77686->irq_gpio);
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_INT) {
|
||||
ret = gpio_request(max77686->irq_gpio, "pmic_irq");
|
||||
if (ret < 0) {
|
||||
dev_err(max77686->dev,
|
||||
"Failed to request gpio %d with ret:"
|
||||
"%d\n", max77686->irq_gpio, ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
gpio_direction_input(max77686->irq_gpio);
|
||||
val = gpio_get_value(max77686->irq_gpio);
|
||||
gpio_free(max77686->irq_gpio);
|
||||
pr_info("%s: gpio_irq=%x\n", __func__, val);
|
||||
}
|
||||
}
|
||||
|
||||
if (!max77686->irq) {
|
||||
dev_err(max77686->dev, "irq is not specified\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
|
||||
/* Mask individual interrupt sources */
|
||||
for (i = 0; i < MAX77686_IRQ_GROUP_NR; i++) {
|
||||
max77686->irq_masks_cur[i] = 0xff;
|
||||
max77686->irq_masks_cache[i] = 0xff;
|
||||
map = max77686_get_regmap(max77686, i);
|
||||
|
||||
if (IS_ERR_OR_NULL(map))
|
||||
continue;
|
||||
if (max77686_mask_reg[i] == MAX77686_REG_INVALID)
|
||||
continue;
|
||||
|
||||
regmap_write(map, max77686_mask_reg[i], 0xff);
|
||||
}
|
||||
domain = irq_domain_add_linear(NULL, MAX77686_IRQ_NR,
|
||||
&max77686_irq_domain_ops, max77686);
|
||||
if (!domain) {
|
||||
dev_err(max77686->dev, "could not create irq domain\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
max77686->irq_domain = domain;
|
||||
|
||||
ret = request_threaded_irq(max77686->irq, NULL, max77686_irq_thread,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"max77686-irq", max77686);
|
||||
|
||||
if (ret)
|
||||
dev_err(max77686->dev, "Failed to request IRQ %d: %d\n",
|
||||
max77686->irq, ret);
|
||||
|
||||
|
||||
if (debug_mask & MAX77686_DEBUG_IRQ_INFO)
|
||||
pr_info("%s-\n", __func__);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void max77686_irq_exit(struct max77686_dev *max77686)
|
||||
{
|
||||
if (max77686->irq)
|
||||
free_irq(max77686->irq, max77686);
|
||||
}
|
||||
187
drivers/mfd/max77686.c
Normal file
187
drivers/mfd/max77686.c
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* max77686.c - mfd core driver for the Maxim 77686
|
||||
*
|
||||
* Copyright (C) 2012 Samsung Electronics
|
||||
* Chiwoong Byun <woong.byun@smasung.com>
|
||||
* Jonghwa Lee <jonghwa3.lee@samsung.com>
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 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.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, write to the Free Software
|
||||
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
*
|
||||
* This driver is based on max8997.c
|
||||
*/
|
||||
|
||||
#include <linux/export.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/max77686.h>
|
||||
#include <linux/mfd/max77686-private.h>
|
||||
#include <linux/err.h>
|
||||
|
||||
#define I2C_ADDR_RTC (0x0C >> 1)
|
||||
|
||||
static struct mfd_cell max77686_devs[] = {
|
||||
{ .name = "max77686-pmic", },
|
||||
{ .name = "max77686-rtc", },
|
||||
};
|
||||
|
||||
static struct regmap_config max77686_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
#ifdef CONFIG_OF
|
||||
static struct of_device_id __devinitdata max77686_pmic_dt_match[] = {
|
||||
{.compatible = "maxim,max77686", .data = 0},
|
||||
{},
|
||||
};
|
||||
|
||||
static struct max77686_platform_data *max77686_i2c_parse_dt_pdata(struct device
|
||||
*dev)
|
||||
{
|
||||
struct max77686_platform_data *pd;
|
||||
|
||||
pd = devm_kzalloc(dev, sizeof(*pd), GFP_KERNEL);
|
||||
if (!pd) {
|
||||
dev_err(dev, "could not allocate memory for pdata\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
dev->platform_data = pd;
|
||||
return pd;
|
||||
}
|
||||
#else
|
||||
static struct max77686_platform_data *max77686_i2c_parse_dt_pdata(struct device
|
||||
*dev)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int max77686_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct max77686_dev *max77686 = NULL;
|
||||
struct max77686_platform_data *pdata = i2c->dev.platform_data;
|
||||
unsigned int data;
|
||||
int ret = 0;
|
||||
|
||||
if (i2c->dev.of_node)
|
||||
pdata = max77686_i2c_parse_dt_pdata(&i2c->dev);
|
||||
|
||||
if (!pdata) {
|
||||
ret = -EIO;
|
||||
dev_err(&i2c->dev, "No platform data found.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
max77686 = kzalloc(sizeof(struct max77686_dev), GFP_KERNEL);
|
||||
if (max77686 == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
max77686->regmap = regmap_init_i2c(i2c, &max77686_regmap_config);
|
||||
if (IS_ERR(max77686->regmap)) {
|
||||
ret = PTR_ERR(max77686->regmap);
|
||||
dev_err(max77686->dev, "Failed to allocate register map: %d\n",
|
||||
ret);
|
||||
kfree(max77686);
|
||||
return ret;
|
||||
}
|
||||
|
||||
i2c_set_clientdata(i2c, max77686);
|
||||
max77686->dev = &i2c->dev;
|
||||
max77686->i2c = i2c;
|
||||
max77686->type = id->driver_data;
|
||||
|
||||
max77686->wakeup = pdata->wakeup;
|
||||
max77686->irq_gpio = pdata->irq_gpio;
|
||||
max77686->irq = i2c->irq;
|
||||
|
||||
if (regmap_read(max77686->regmap,
|
||||
MAX77686_REG_DEVICE_ID, &data) < 0) {
|
||||
dev_err(max77686->dev,
|
||||
"device not found on this channel (this is not an error)\n");
|
||||
ret = -ENODEV;
|
||||
goto err;
|
||||
} else
|
||||
dev_info(max77686->dev, "device found\n");
|
||||
|
||||
max77686->rtc = i2c_new_dummy(i2c->adapter, I2C_ADDR_RTC);
|
||||
i2c_set_clientdata(max77686->rtc, max77686);
|
||||
|
||||
max77686_irq_init(max77686);
|
||||
|
||||
ret = mfd_add_devices(max77686->dev, -1, max77686_devs,
|
||||
ARRAY_SIZE(max77686_devs), NULL, 0);
|
||||
|
||||
if (ret < 0)
|
||||
goto err_mfd;
|
||||
|
||||
return ret;
|
||||
|
||||
err_mfd:
|
||||
mfd_remove_devices(max77686->dev);
|
||||
i2c_unregister_device(max77686->rtc);
|
||||
err:
|
||||
kfree(max77686);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int max77686_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct max77686_dev *max77686 = i2c_get_clientdata(i2c);
|
||||
|
||||
mfd_remove_devices(max77686->dev);
|
||||
i2c_unregister_device(max77686->rtc);
|
||||
kfree(max77686);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id max77686_i2c_id[] = {
|
||||
{ "max77686", TYPE_MAX77686 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max77686_i2c_id);
|
||||
|
||||
static struct i2c_driver max77686_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "max77686",
|
||||
.owner = THIS_MODULE,
|
||||
.of_match_table = of_match_ptr(max77686_pmic_dt_match),
|
||||
},
|
||||
.probe = max77686_i2c_probe,
|
||||
.remove = max77686_i2c_remove,
|
||||
.id_table = max77686_i2c_id,
|
||||
};
|
||||
|
||||
static int __init max77686_i2c_init(void)
|
||||
{
|
||||
return i2c_add_driver(&max77686_i2c_driver);
|
||||
}
|
||||
/* init early so consumer devices can complete system boot */
|
||||
subsys_initcall(max77686_i2c_init);
|
||||
|
||||
static void __exit max77686_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&max77686_i2c_driver);
|
||||
}
|
||||
module_exit(max77686_i2c_exit);
|
||||
|
||||
MODULE_DESCRIPTION("MAXIM 77686 multi-function core driver");
|
||||
MODULE_AUTHOR("Chiwoong Byun <[email protected]>");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -138,8 +138,6 @@ static int max77693_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
max77693->wakeup = pdata->wakeup;
|
||||
|
||||
mutex_init(&max77693->iolock);
|
||||
|
||||
if (max77693_read_reg(max77693->regmap,
|
||||
MAX77693_PMIC_REG_PMIC_ID2, ®_data) < 0) {
|
||||
dev_err(max77693->dev, "device not found on this channel\n");
|
||||
|
|
@ -156,7 +154,7 @@ static int max77693_i2c_probe(struct i2c_client *i2c,
|
|||
|
||||
ret = max77693_irq_init(max77693);
|
||||
if (ret < 0)
|
||||
goto err_mfd;
|
||||
goto err_irq;
|
||||
|
||||
pm_runtime_set_active(max77693->dev);
|
||||
|
||||
|
|
@ -170,11 +168,11 @@ static int max77693_i2c_probe(struct i2c_client *i2c,
|
|||
return ret;
|
||||
|
||||
err_mfd:
|
||||
max77693_irq_exit(max77693);
|
||||
err_irq:
|
||||
i2c_unregister_device(max77693->muic);
|
||||
i2c_unregister_device(max77693->haptic);
|
||||
err_regmap:
|
||||
kfree(max77693);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
@ -183,6 +181,7 @@ static int max77693_i2c_remove(struct i2c_client *i2c)
|
|||
struct max77693_dev *max77693 = i2c_get_clientdata(i2c);
|
||||
|
||||
mfd_remove_devices(max77693->dev);
|
||||
max77693_irq_exit(max77693);
|
||||
i2c_unregister_device(max77693->muic);
|
||||
i2c_unregister_device(max77693->haptic);
|
||||
|
||||
|
|
@ -215,7 +214,7 @@ static int max77693_resume(struct device *dev)
|
|||
return max77693_irq_resume(max77693);
|
||||
}
|
||||
|
||||
const struct dev_pm_ops max77693_pm = {
|
||||
static const struct dev_pm_ops max77693_pm = {
|
||||
.suspend = max77693_suspend,
|
||||
.resume = max77693_resume,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -75,9 +75,9 @@ static struct mfd_cell power_devs[] = {
|
|||
static struct resource rtc_resources[] = {
|
||||
{
|
||||
.name = "max8925-rtc",
|
||||
.start = MAX8925_RTC_IRQ,
|
||||
.end = MAX8925_RTC_IRQ_MASK,
|
||||
.flags = IORESOURCE_IO,
|
||||
.start = MAX8925_IRQ_RTC_ALARM0,
|
||||
.end = MAX8925_IRQ_RTC_ALARM0,
|
||||
.flags = IORESOURCE_IRQ,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
@ -598,7 +598,7 @@ int __devinit max8925_device_init(struct max8925_chip *chip,
|
|||
|
||||
ret = mfd_add_devices(chip->dev, 0, &rtc_devs[0],
|
||||
ARRAY_SIZE(rtc_devs),
|
||||
&rtc_resources[0], 0);
|
||||
&rtc_resources[0], chip->irq_base);
|
||||
if (ret < 0) {
|
||||
dev_err(chip->dev, "Failed to add rtc subdev\n");
|
||||
goto out;
|
||||
|
|
|
|||
|
|
@ -142,7 +142,8 @@ static void max8997_irq_sync_unlock(struct irq_data *data)
|
|||
static const inline struct max8997_irq_data *
|
||||
irq_to_max8997_irq(struct max8997_dev *max8997, int irq)
|
||||
{
|
||||
return &max8997_irqs[irq - max8997->irq_base];
|
||||
struct irq_data *data = irq_get_irq_data(irq);
|
||||
return &max8997_irqs[data->hwirq];
|
||||
}
|
||||
|
||||
static void max8997_irq_mask(struct irq_data *data)
|
||||
|
|
@ -182,7 +183,7 @@ static irqreturn_t max8997_irq_thread(int irq, void *data)
|
|||
u8 irq_reg[MAX8997_IRQ_GROUP_NR] = {};
|
||||
u8 irq_src;
|
||||
int ret;
|
||||
int i;
|
||||
int i, cur_irq;
|
||||
|
||||
ret = max8997_read_reg(max8997->i2c, MAX8997_REG_INTSRC, &irq_src);
|
||||
if (ret < 0) {
|
||||
|
|
@ -269,8 +270,11 @@ static irqreturn_t max8997_irq_thread(int irq, void *data)
|
|||
|
||||
/* Report */
|
||||
for (i = 0; i < MAX8997_IRQ_NR; i++) {
|
||||
if (irq_reg[max8997_irqs[i].group] & max8997_irqs[i].mask)
|
||||
handle_nested_irq(max8997->irq_base + i);
|
||||
if (irq_reg[max8997_irqs[i].group] & max8997_irqs[i].mask) {
|
||||
cur_irq = irq_find_mapping(max8997->irq_domain, i);
|
||||
if (cur_irq)
|
||||
handle_nested_irq(cur_irq);
|
||||
}
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
|
|
@ -278,26 +282,40 @@ static irqreturn_t max8997_irq_thread(int irq, void *data)
|
|||
|
||||
int max8997_irq_resume(struct max8997_dev *max8997)
|
||||
{
|
||||
if (max8997->irq && max8997->irq_base)
|
||||
max8997_irq_thread(max8997->irq_base, max8997);
|
||||
if (max8997->irq && max8997->irq_domain)
|
||||
max8997_irq_thread(0, max8997);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int max8997_irq_domain_map(struct irq_domain *d, unsigned int irq,
|
||||
irq_hw_number_t hw)
|
||||
{
|
||||
struct max8997_dev *max8997 = d->host_data;
|
||||
|
||||
irq_set_chip_data(irq, max8997);
|
||||
irq_set_chip_and_handler(irq, &max8997_irq_chip, handle_edge_irq);
|
||||
irq_set_nested_thread(irq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(irq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(irq);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct irq_domain_ops max8997_irq_domain_ops = {
|
||||
.map = max8997_irq_domain_map,
|
||||
};
|
||||
|
||||
int max8997_irq_init(struct max8997_dev *max8997)
|
||||
{
|
||||
struct irq_domain *domain;
|
||||
int i;
|
||||
int cur_irq;
|
||||
int ret;
|
||||
u8 val;
|
||||
|
||||
if (!max8997->irq) {
|
||||
dev_warn(max8997->dev, "No interrupt specified.\n");
|
||||
max8997->irq_base = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!max8997->irq_base) {
|
||||
dev_err(max8997->dev, "No interrupt base specified.\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
@ -327,19 +345,13 @@ int max8997_irq_init(struct max8997_dev *max8997)
|
|||
true : false;
|
||||
}
|
||||
|
||||
/* Register with genirq */
|
||||
for (i = 0; i < MAX8997_IRQ_NR; i++) {
|
||||
cur_irq = i + max8997->irq_base;
|
||||
irq_set_chip_data(cur_irq, max8997);
|
||||
irq_set_chip_and_handler(cur_irq, &max8997_irq_chip,
|
||||
handle_edge_irq);
|
||||
irq_set_nested_thread(cur_irq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(cur_irq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(cur_irq);
|
||||
#endif
|
||||
domain = irq_domain_add_linear(NULL, MAX8997_IRQ_NR,
|
||||
&max8997_irq_domain_ops, max8997);
|
||||
if (!domain) {
|
||||
dev_err(max8997->dev, "could not create irq domain\n");
|
||||
return -ENODEV;
|
||||
}
|
||||
max8997->irq_domain = domain;
|
||||
|
||||
ret = request_threaded_irq(max8997->irq, NULL, max8997_irq_thread,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
|
|
|
|||
|
|
@ -143,7 +143,6 @@ static int max8997_i2c_probe(struct i2c_client *i2c,
|
|||
if (!pdata)
|
||||
goto err;
|
||||
|
||||
max8997->irq_base = pdata->irq_base;
|
||||
max8997->ono = pdata->ono;
|
||||
|
||||
mutex_init(&max8997->iolock);
|
||||
|
|
@ -206,7 +205,7 @@ static const struct i2c_device_id max8997_i2c_id[] = {
|
|||
};
|
||||
MODULE_DEVICE_TABLE(i2c, max8998_i2c_id);
|
||||
|
||||
u8 max8997_dumpaddr_pmic[] = {
|
||||
static u8 max8997_dumpaddr_pmic[] = {
|
||||
MAX8997_REG_INT1MSK,
|
||||
MAX8997_REG_INT2MSK,
|
||||
MAX8997_REG_INT3MSK,
|
||||
|
|
@ -331,7 +330,7 @@ u8 max8997_dumpaddr_pmic[] = {
|
|||
MAX8997_REG_DVSOKTIMER5,
|
||||
};
|
||||
|
||||
u8 max8997_dumpaddr_muic[] = {
|
||||
static u8 max8997_dumpaddr_muic[] = {
|
||||
MAX8997_MUIC_REG_INTMASK1,
|
||||
MAX8997_MUIC_REG_INTMASK2,
|
||||
MAX8997_MUIC_REG_INTMASK3,
|
||||
|
|
@ -341,7 +340,7 @@ u8 max8997_dumpaddr_muic[] = {
|
|||
MAX8997_MUIC_REG_CONTROL3,
|
||||
};
|
||||
|
||||
u8 max8997_dumpaddr_haptic[] = {
|
||||
static u8 max8997_dumpaddr_haptic[] = {
|
||||
MAX8997_HAPTIC_REG_CONF1,
|
||||
MAX8997_HAPTIC_REG_CONF2,
|
||||
MAX8997_HAPTIC_REG_DRVCONF,
|
||||
|
|
@ -423,7 +422,7 @@ static int max8997_resume(struct device *dev)
|
|||
return max8997_irq_resume(max8997);
|
||||
}
|
||||
|
||||
const struct dev_pm_ops max8997_pm = {
|
||||
static const struct dev_pm_ops max8997_pm = {
|
||||
.suspend = max8997_suspend,
|
||||
.resume = max8997_resume,
|
||||
.freeze = max8997_freeze,
|
||||
|
|
|
|||
|
|
@ -723,10 +723,6 @@ void mc13xxx_common_cleanup(struct mc13xxx *mc13xxx)
|
|||
free_irq(mc13xxx->irq, mc13xxx);
|
||||
|
||||
mfd_remove_devices(mc13xxx->dev);
|
||||
|
||||
regmap_exit(mc13xxx->regmap);
|
||||
|
||||
kfree(mc13xxx);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(mc13xxx_common_cleanup);
|
||||
|
||||
|
|
|
|||
|
|
@ -53,17 +53,11 @@ static struct regmap_config mc13xxx_regmap_i2c_config = {
|
|||
static int mc13xxx_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
const struct of_device_id *of_id;
|
||||
struct i2c_driver *idrv = to_i2c_driver(client->dev.driver);
|
||||
struct mc13xxx *mc13xxx;
|
||||
struct mc13xxx_platform_data *pdata = dev_get_platdata(&client->dev);
|
||||
int ret;
|
||||
|
||||
of_id = of_match_device(mc13xxx_dt_ids, &client->dev);
|
||||
if (of_id)
|
||||
idrv->id_table = (const struct i2c_device_id*) of_id->data;
|
||||
|
||||
mc13xxx = kzalloc(sizeof(*mc13xxx), GFP_KERNEL);
|
||||
mc13xxx = devm_kzalloc(&client->dev, sizeof(*mc13xxx), GFP_KERNEL);
|
||||
if (!mc13xxx)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -72,13 +66,13 @@ static int mc13xxx_i2c_probe(struct i2c_client *client,
|
|||
mc13xxx->dev = &client->dev;
|
||||
mutex_init(&mc13xxx->lock);
|
||||
|
||||
mc13xxx->regmap = regmap_init_i2c(client, &mc13xxx_regmap_i2c_config);
|
||||
mc13xxx->regmap = devm_regmap_init_i2c(client,
|
||||
&mc13xxx_regmap_i2c_config);
|
||||
if (IS_ERR(mc13xxx->regmap)) {
|
||||
ret = PTR_ERR(mc13xxx->regmap);
|
||||
dev_err(mc13xxx->dev, "Failed to initialize register map: %d\n",
|
||||
ret);
|
||||
dev_set_drvdata(&client->dev, NULL);
|
||||
kfree(mc13xxx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -119,17 +119,11 @@ static struct regmap_bus regmap_mc13xxx_bus = {
|
|||
|
||||
static int mc13xxx_spi_probe(struct spi_device *spi)
|
||||
{
|
||||
const struct of_device_id *of_id;
|
||||
struct spi_driver *sdrv = to_spi_driver(spi->dev.driver);
|
||||
struct mc13xxx *mc13xxx;
|
||||
struct mc13xxx_platform_data *pdata = dev_get_platdata(&spi->dev);
|
||||
int ret;
|
||||
|
||||
of_id = of_match_device(mc13xxx_dt_ids, &spi->dev);
|
||||
if (of_id)
|
||||
sdrv->id_table = &mc13xxx_device_id[(enum mc13xxx_id) of_id->data];
|
||||
|
||||
mc13xxx = kzalloc(sizeof(*mc13xxx), GFP_KERNEL);
|
||||
mc13xxx = devm_kzalloc(&spi->dev, sizeof(*mc13xxx), GFP_KERNEL);
|
||||
if (!mc13xxx)
|
||||
return -ENOMEM;
|
||||
|
||||
|
|
@ -139,15 +133,14 @@ static int mc13xxx_spi_probe(struct spi_device *spi)
|
|||
mc13xxx->dev = &spi->dev;
|
||||
mutex_init(&mc13xxx->lock);
|
||||
|
||||
mc13xxx->regmap = regmap_init(&spi->dev, ®map_mc13xxx_bus, &spi->dev,
|
||||
&mc13xxx_regmap_spi_config);
|
||||
|
||||
mc13xxx->regmap = devm_regmap_init(&spi->dev, ®map_mc13xxx_bus,
|
||||
&spi->dev,
|
||||
&mc13xxx_regmap_spi_config);
|
||||
if (IS_ERR(mc13xxx->regmap)) {
|
||||
ret = PTR_ERR(mc13xxx->regmap);
|
||||
dev_err(mc13xxx->dev, "Failed to initialize register map: %d\n",
|
||||
ret);
|
||||
dev_set_drvdata(&spi->dev, NULL);
|
||||
kfree(mc13xxx);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -18,6 +18,8 @@
|
|||
#include <linux/pm_runtime.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/irqdomain.h>
|
||||
#include <linux/of.h>
|
||||
|
||||
int mfd_cell_enable(struct platform_device *pdev)
|
||||
{
|
||||
|
|
@ -76,6 +78,8 @@ static int mfd_add_device(struct device *parent, int id,
|
|||
{
|
||||
struct resource *res;
|
||||
struct platform_device *pdev;
|
||||
struct device_node *np = NULL;
|
||||
struct irq_domain *domain = NULL;
|
||||
int ret = -ENOMEM;
|
||||
int r;
|
||||
|
||||
|
|
@ -89,6 +93,16 @@ static int mfd_add_device(struct device *parent, int id,
|
|||
|
||||
pdev->dev.parent = parent;
|
||||
|
||||
if (parent->of_node && cell->of_compatible) {
|
||||
for_each_child_of_node(parent->of_node, np) {
|
||||
if (of_device_is_compatible(np, cell->of_compatible)) {
|
||||
pdev->dev.of_node = np;
|
||||
domain = irq_find_host(parent->of_node);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cell->pdata_size) {
|
||||
ret = platform_device_add_data(pdev,
|
||||
cell->platform_data, cell->pdata_size);
|
||||
|
|
@ -112,10 +126,18 @@ static int mfd_add_device(struct device *parent, int id,
|
|||
res[r].end = mem_base->start +
|
||||
cell->resources[r].end;
|
||||
} else if (cell->resources[r].flags & IORESOURCE_IRQ) {
|
||||
res[r].start = irq_base +
|
||||
cell->resources[r].start;
|
||||
res[r].end = irq_base +
|
||||
cell->resources[r].end;
|
||||
if (domain) {
|
||||
/* Unable to create mappings for IRQ ranges. */
|
||||
WARN_ON(cell->resources[r].start !=
|
||||
cell->resources[r].end);
|
||||
res[r].start = res[r].end = irq_create_mapping(
|
||||
domain, cell->resources[r].start);
|
||||
} else {
|
||||
res[r].start = irq_base +
|
||||
cell->resources[r].start;
|
||||
res[r].end = irq_base +
|
||||
cell->resources[r].end;
|
||||
}
|
||||
} else {
|
||||
res[r].parent = cell->resources[r].parent;
|
||||
res[r].start = cell->resources[r].start;
|
||||
|
|
|
|||
|
|
@ -253,8 +253,13 @@ static int __devinit pcf50633_probe(struct i2c_client *client,
|
|||
}
|
||||
|
||||
pdev->dev.parent = pcf->dev;
|
||||
platform_device_add_data(pdev, &pdata->reg_init_data[i],
|
||||
sizeof(pdata->reg_init_data[i]));
|
||||
if (platform_device_add_data(pdev, &pdata->reg_init_data[i],
|
||||
sizeof(pdata->reg_init_data[i])) < 0) {
|
||||
platform_device_put(pdev);
|
||||
dev_err(pcf->dev, "Out of memory for regulator parameters %d\n",
|
||||
i);
|
||||
continue;
|
||||
}
|
||||
pcf->regulator_pdev[i] = pdev;
|
||||
|
||||
platform_device_add(pdev);
|
||||
|
|
|
|||
|
|
@ -1,206 +0,0 @@
|
|||
/*
|
||||
* s5m87xx.c
|
||||
*
|
||||
* Copyright (c) 2011 Samsung Electronics Co., Ltd
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/s5m87xx/s5m-core.h>
|
||||
#include <linux/mfd/s5m87xx/s5m-pmic.h>
|
||||
#include <linux/mfd/s5m87xx/s5m-rtc.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
static struct mfd_cell s5m8751_devs[] = {
|
||||
{
|
||||
.name = "s5m8751-pmic",
|
||||
}, {
|
||||
.name = "s5m-charger",
|
||||
}, {
|
||||
.name = "s5m8751-codec",
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell s5m8763_devs[] = {
|
||||
{
|
||||
.name = "s5m8763-pmic",
|
||||
}, {
|
||||
.name = "s5m-rtc",
|
||||
}, {
|
||||
.name = "s5m-charger",
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell s5m8767_devs[] = {
|
||||
{
|
||||
.name = "s5m8767-pmic",
|
||||
}, {
|
||||
.name = "s5m-rtc",
|
||||
},
|
||||
};
|
||||
|
||||
int s5m_reg_read(struct s5m87xx_dev *s5m87xx, u8 reg, void *dest)
|
||||
{
|
||||
return regmap_read(s5m87xx->regmap, reg, dest);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s5m_reg_read);
|
||||
|
||||
int s5m_bulk_read(struct s5m87xx_dev *s5m87xx, u8 reg, int count, u8 *buf)
|
||||
{
|
||||
return regmap_bulk_read(s5m87xx->regmap, reg, buf, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s5m_bulk_read);
|
||||
|
||||
int s5m_reg_write(struct s5m87xx_dev *s5m87xx, u8 reg, u8 value)
|
||||
{
|
||||
return regmap_write(s5m87xx->regmap, reg, value);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s5m_reg_write);
|
||||
|
||||
int s5m_bulk_write(struct s5m87xx_dev *s5m87xx, u8 reg, int count, u8 *buf)
|
||||
{
|
||||
return regmap_raw_write(s5m87xx->regmap, reg, buf, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s5m_bulk_write);
|
||||
|
||||
int s5m_reg_update(struct s5m87xx_dev *s5m87xx, u8 reg, u8 val, u8 mask)
|
||||
{
|
||||
return regmap_update_bits(s5m87xx->regmap, reg, mask, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(s5m_reg_update);
|
||||
|
||||
static struct regmap_config s5m_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int s5m87xx_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct s5m_platform_data *pdata = i2c->dev.platform_data;
|
||||
struct s5m87xx_dev *s5m87xx;
|
||||
int ret;
|
||||
|
||||
s5m87xx = devm_kzalloc(&i2c->dev, sizeof(struct s5m87xx_dev),
|
||||
GFP_KERNEL);
|
||||
if (s5m87xx == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(i2c, s5m87xx);
|
||||
s5m87xx->dev = &i2c->dev;
|
||||
s5m87xx->i2c = i2c;
|
||||
s5m87xx->irq = i2c->irq;
|
||||
s5m87xx->type = id->driver_data;
|
||||
|
||||
if (pdata) {
|
||||
s5m87xx->device_type = pdata->device_type;
|
||||
s5m87xx->ono = pdata->ono;
|
||||
s5m87xx->irq_base = pdata->irq_base;
|
||||
s5m87xx->wakeup = pdata->wakeup;
|
||||
}
|
||||
|
||||
s5m87xx->regmap = devm_regmap_init_i2c(i2c, &s5m_regmap_config);
|
||||
if (IS_ERR(s5m87xx->regmap)) {
|
||||
ret = PTR_ERR(s5m87xx->regmap);
|
||||
dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
s5m87xx->rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR);
|
||||
i2c_set_clientdata(s5m87xx->rtc, s5m87xx);
|
||||
|
||||
if (pdata && pdata->cfg_pmic_irq)
|
||||
pdata->cfg_pmic_irq();
|
||||
|
||||
s5m_irq_init(s5m87xx);
|
||||
|
||||
pm_runtime_set_active(s5m87xx->dev);
|
||||
|
||||
switch (s5m87xx->device_type) {
|
||||
case S5M8751X:
|
||||
ret = mfd_add_devices(s5m87xx->dev, -1, s5m8751_devs,
|
||||
ARRAY_SIZE(s5m8751_devs), NULL, 0);
|
||||
break;
|
||||
case S5M8763X:
|
||||
ret = mfd_add_devices(s5m87xx->dev, -1, s5m8763_devs,
|
||||
ARRAY_SIZE(s5m8763_devs), NULL, 0);
|
||||
break;
|
||||
case S5M8767X:
|
||||
ret = mfd_add_devices(s5m87xx->dev, -1, s5m8767_devs,
|
||||
ARRAY_SIZE(s5m8767_devs), NULL, 0);
|
||||
break;
|
||||
default:
|
||||
/* If this happens the probe function is problem */
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
mfd_remove_devices(s5m87xx->dev);
|
||||
s5m_irq_exit(s5m87xx);
|
||||
i2c_unregister_device(s5m87xx->rtc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int s5m87xx_i2c_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = i2c_get_clientdata(i2c);
|
||||
|
||||
mfd_remove_devices(s5m87xx->dev);
|
||||
s5m_irq_exit(s5m87xx);
|
||||
i2c_unregister_device(s5m87xx->rtc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id s5m87xx_i2c_id[] = {
|
||||
{ "s5m87xx", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, s5m87xx_i2c_id);
|
||||
|
||||
static struct i2c_driver s5m87xx_i2c_driver = {
|
||||
.driver = {
|
||||
.name = "s5m87xx",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = s5m87xx_i2c_probe,
|
||||
.remove = s5m87xx_i2c_remove,
|
||||
.id_table = s5m87xx_i2c_id,
|
||||
};
|
||||
|
||||
static int __init s5m87xx_i2c_init(void)
|
||||
{
|
||||
return i2c_add_driver(&s5m87xx_i2c_driver);
|
||||
}
|
||||
|
||||
subsys_initcall(s5m87xx_i2c_init);
|
||||
|
||||
static void __exit s5m87xx_i2c_exit(void)
|
||||
{
|
||||
i2c_del_driver(&s5m87xx_i2c_driver);
|
||||
}
|
||||
module_exit(s5m87xx_i2c_exit);
|
||||
|
||||
MODULE_AUTHOR("Sangbeom Kim <[email protected]>");
|
||||
MODULE_DESCRIPTION("Core support for the S5M MFD");
|
||||
MODULE_LICENSE("GPL");
|
||||
|
|
@ -1,495 +0,0 @@
|
|||
/*
|
||||
* s5m-irq.c
|
||||
*
|
||||
* Copyright (c) 2011 Samsung Electronics Co., Ltd
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/mfd/s5m87xx/s5m-core.h>
|
||||
|
||||
struct s5m_irq_data {
|
||||
int reg;
|
||||
int mask;
|
||||
};
|
||||
|
||||
static struct s5m_irq_data s5m8767_irqs[] = {
|
||||
[S5M8767_IRQ_PWRR] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8767_IRQ_PWRR_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_PWRF] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8767_IRQ_PWRF_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_PWR1S] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8767_IRQ_PWR1S_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_JIGR] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8767_IRQ_JIGR_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_JIGF] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8767_IRQ_JIGF_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_LOWBAT2] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8767_IRQ_LOWBAT2_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_LOWBAT1] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8767_IRQ_LOWBAT1_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_MRB] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8767_IRQ_MRB_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_DVSOK2] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8767_IRQ_DVSOK2_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_DVSOK3] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8767_IRQ_DVSOK3_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_DVSOK4] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8767_IRQ_DVSOK4_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTC60S] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8767_IRQ_RTC60S_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTCA1] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8767_IRQ_RTCA1_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTCA2] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8767_IRQ_RTCA2_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_SMPL] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8767_IRQ_SMPL_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTC1S] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8767_IRQ_RTC1S_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_WTSR] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8767_IRQ_WTSR_MASK,
|
||||
},
|
||||
};
|
||||
|
||||
static struct s5m_irq_data s5m8763_irqs[] = {
|
||||
[S5M8763_IRQ_DCINF] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8763_IRQ_DCINF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_DCINR] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8763_IRQ_DCINR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_JIGF] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8763_IRQ_JIGF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_JIGR] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8763_IRQ_JIGR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_PWRONF] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8763_IRQ_PWRONF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_PWRONR] = {
|
||||
.reg = 1,
|
||||
.mask = S5M8763_IRQ_PWRONR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_WTSREVNT] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8763_IRQ_WTSREVNT_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_SMPLEVNT] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8763_IRQ_SMPLEVNT_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_ALARM1] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8763_IRQ_ALARM1_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_ALARM0] = {
|
||||
.reg = 2,
|
||||
.mask = S5M8763_IRQ_ALARM0_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_ONKEY1S] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8763_IRQ_ONKEY1S_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_TOPOFFR] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8763_IRQ_TOPOFFR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_DCINOVPR] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8763_IRQ_DCINOVPR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_CHGRSTF] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8763_IRQ_CHGRSTF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_DONER] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8763_IRQ_DONER_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_CHGFAULT] = {
|
||||
.reg = 3,
|
||||
.mask = S5M8763_IRQ_CHGFAULT_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_LOBAT1] = {
|
||||
.reg = 4,
|
||||
.mask = S5M8763_IRQ_LOBAT1_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_LOBAT2] = {
|
||||
.reg = 4,
|
||||
.mask = S5M8763_IRQ_LOBAT2_MASK,
|
||||
},
|
||||
};
|
||||
|
||||
static inline struct s5m_irq_data *
|
||||
irq_to_s5m8767_irq(struct s5m87xx_dev *s5m87xx, int irq)
|
||||
{
|
||||
return &s5m8767_irqs[irq - s5m87xx->irq_base];
|
||||
}
|
||||
|
||||
static void s5m8767_irq_lock(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
|
||||
mutex_lock(&s5m87xx->irqlock);
|
||||
}
|
||||
|
||||
static void s5m8767_irq_sync_unlock(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(s5m87xx->irq_masks_cur); i++) {
|
||||
if (s5m87xx->irq_masks_cur[i] != s5m87xx->irq_masks_cache[i]) {
|
||||
s5m87xx->irq_masks_cache[i] = s5m87xx->irq_masks_cur[i];
|
||||
s5m_reg_write(s5m87xx, S5M8767_REG_INT1M + i,
|
||||
s5m87xx->irq_masks_cur[i]);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&s5m87xx->irqlock);
|
||||
}
|
||||
|
||||
static void s5m8767_irq_unmask(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
struct s5m_irq_data *irq_data = irq_to_s5m8767_irq(s5m87xx,
|
||||
data->irq);
|
||||
|
||||
s5m87xx->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask;
|
||||
}
|
||||
|
||||
static void s5m8767_irq_mask(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
struct s5m_irq_data *irq_data = irq_to_s5m8767_irq(s5m87xx,
|
||||
data->irq);
|
||||
|
||||
s5m87xx->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask;
|
||||
}
|
||||
|
||||
static struct irq_chip s5m8767_irq_chip = {
|
||||
.name = "s5m8767",
|
||||
.irq_bus_lock = s5m8767_irq_lock,
|
||||
.irq_bus_sync_unlock = s5m8767_irq_sync_unlock,
|
||||
.irq_mask = s5m8767_irq_mask,
|
||||
.irq_unmask = s5m8767_irq_unmask,
|
||||
};
|
||||
|
||||
static inline struct s5m_irq_data *
|
||||
irq_to_s5m8763_irq(struct s5m87xx_dev *s5m87xx, int irq)
|
||||
{
|
||||
return &s5m8763_irqs[irq - s5m87xx->irq_base];
|
||||
}
|
||||
|
||||
static void s5m8763_irq_lock(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
|
||||
mutex_lock(&s5m87xx->irqlock);
|
||||
}
|
||||
|
||||
static void s5m8763_irq_sync_unlock(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(s5m87xx->irq_masks_cur); i++) {
|
||||
if (s5m87xx->irq_masks_cur[i] != s5m87xx->irq_masks_cache[i]) {
|
||||
s5m87xx->irq_masks_cache[i] = s5m87xx->irq_masks_cur[i];
|
||||
s5m_reg_write(s5m87xx, S5M8763_REG_IRQM1 + i,
|
||||
s5m87xx->irq_masks_cur[i]);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&s5m87xx->irqlock);
|
||||
}
|
||||
|
||||
static void s5m8763_irq_unmask(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
struct s5m_irq_data *irq_data = irq_to_s5m8763_irq(s5m87xx,
|
||||
data->irq);
|
||||
|
||||
s5m87xx->irq_masks_cur[irq_data->reg - 1] &= ~irq_data->mask;
|
||||
}
|
||||
|
||||
static void s5m8763_irq_mask(struct irq_data *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = irq_data_get_irq_chip_data(data);
|
||||
struct s5m_irq_data *irq_data = irq_to_s5m8763_irq(s5m87xx,
|
||||
data->irq);
|
||||
|
||||
s5m87xx->irq_masks_cur[irq_data->reg - 1] |= irq_data->mask;
|
||||
}
|
||||
|
||||
static struct irq_chip s5m8763_irq_chip = {
|
||||
.name = "s5m8763",
|
||||
.irq_bus_lock = s5m8763_irq_lock,
|
||||
.irq_bus_sync_unlock = s5m8763_irq_sync_unlock,
|
||||
.irq_mask = s5m8763_irq_mask,
|
||||
.irq_unmask = s5m8763_irq_unmask,
|
||||
};
|
||||
|
||||
|
||||
static irqreturn_t s5m8767_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = data;
|
||||
u8 irq_reg[NUM_IRQ_REGS-1];
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
|
||||
ret = s5m_bulk_read(s5m87xx, S5M8767_REG_INT1,
|
||||
NUM_IRQ_REGS - 1, irq_reg);
|
||||
if (ret < 0) {
|
||||
dev_err(s5m87xx->dev, "Failed to read interrupt register: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_IRQ_REGS - 1; i++)
|
||||
irq_reg[i] &= ~s5m87xx->irq_masks_cur[i];
|
||||
|
||||
for (i = 0; i < S5M8767_IRQ_NR; i++) {
|
||||
if (irq_reg[s5m8767_irqs[i].reg - 1] & s5m8767_irqs[i].mask)
|
||||
handle_nested_irq(s5m87xx->irq_base + i);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
static irqreturn_t s5m8763_irq_thread(int irq, void *data)
|
||||
{
|
||||
struct s5m87xx_dev *s5m87xx = data;
|
||||
u8 irq_reg[NUM_IRQ_REGS];
|
||||
int ret;
|
||||
int i;
|
||||
|
||||
ret = s5m_bulk_read(s5m87xx, S5M8763_REG_IRQ1,
|
||||
NUM_IRQ_REGS, irq_reg);
|
||||
if (ret < 0) {
|
||||
dev_err(s5m87xx->dev, "Failed to read interrupt register: %d\n",
|
||||
ret);
|
||||
return IRQ_NONE;
|
||||
}
|
||||
|
||||
for (i = 0; i < NUM_IRQ_REGS; i++)
|
||||
irq_reg[i] &= ~s5m87xx->irq_masks_cur[i];
|
||||
|
||||
for (i = 0; i < S5M8763_IRQ_NR; i++) {
|
||||
if (irq_reg[s5m8763_irqs[i].reg - 1] & s5m8763_irqs[i].mask)
|
||||
handle_nested_irq(s5m87xx->irq_base + i);
|
||||
}
|
||||
|
||||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
int s5m_irq_resume(struct s5m87xx_dev *s5m87xx)
|
||||
{
|
||||
if (s5m87xx->irq && s5m87xx->irq_base){
|
||||
switch (s5m87xx->device_type) {
|
||||
case S5M8763X:
|
||||
s5m8763_irq_thread(s5m87xx->irq_base, s5m87xx);
|
||||
break;
|
||||
case S5M8767X:
|
||||
s5m8767_irq_thread(s5m87xx->irq_base, s5m87xx);
|
||||
break;
|
||||
default:
|
||||
dev_err(s5m87xx->dev,
|
||||
"Unknown device type %d\n",
|
||||
s5m87xx->device_type);
|
||||
return -EINVAL;
|
||||
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int s5m_irq_init(struct s5m87xx_dev *s5m87xx)
|
||||
{
|
||||
int i;
|
||||
int cur_irq;
|
||||
int ret = 0;
|
||||
int type = s5m87xx->device_type;
|
||||
|
||||
if (!s5m87xx->irq) {
|
||||
dev_warn(s5m87xx->dev,
|
||||
"No interrupt specified, no interrupts\n");
|
||||
s5m87xx->irq_base = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!s5m87xx->irq_base) {
|
||||
dev_err(s5m87xx->dev,
|
||||
"No interrupt base specified, no interrupts\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
mutex_init(&s5m87xx->irqlock);
|
||||
|
||||
switch (type) {
|
||||
case S5M8763X:
|
||||
for (i = 0; i < NUM_IRQ_REGS; i++) {
|
||||
s5m87xx->irq_masks_cur[i] = 0xff;
|
||||
s5m87xx->irq_masks_cache[i] = 0xff;
|
||||
s5m_reg_write(s5m87xx, S5M8763_REG_IRQM1 + i,
|
||||
0xff);
|
||||
}
|
||||
|
||||
s5m_reg_write(s5m87xx, S5M8763_REG_STATUSM1, 0xff);
|
||||
s5m_reg_write(s5m87xx, S5M8763_REG_STATUSM2, 0xff);
|
||||
|
||||
for (i = 0; i < S5M8763_IRQ_NR; i++) {
|
||||
cur_irq = i + s5m87xx->irq_base;
|
||||
irq_set_chip_data(cur_irq, s5m87xx);
|
||||
irq_set_chip_and_handler(cur_irq, &s5m8763_irq_chip,
|
||||
handle_edge_irq);
|
||||
irq_set_nested_thread(cur_irq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(cur_irq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(cur_irq);
|
||||
#endif
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(s5m87xx->irq, NULL,
|
||||
s5m8763_irq_thread,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"s5m87xx-irq", s5m87xx);
|
||||
if (ret) {
|
||||
dev_err(s5m87xx->dev, "Failed to request IRQ %d: %d\n",
|
||||
s5m87xx->irq, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
case S5M8767X:
|
||||
for (i = 0; i < NUM_IRQ_REGS - 1; i++) {
|
||||
s5m87xx->irq_masks_cur[i] = 0xff;
|
||||
s5m87xx->irq_masks_cache[i] = 0xff;
|
||||
s5m_reg_write(s5m87xx, S5M8767_REG_INT1M + i,
|
||||
0xff);
|
||||
}
|
||||
for (i = 0; i < S5M8767_IRQ_NR; i++) {
|
||||
cur_irq = i + s5m87xx->irq_base;
|
||||
irq_set_chip_data(cur_irq, s5m87xx);
|
||||
if (ret) {
|
||||
dev_err(s5m87xx->dev,
|
||||
"Failed to irq_set_chip_data %d: %d\n",
|
||||
s5m87xx->irq, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
irq_set_chip_and_handler(cur_irq, &s5m8767_irq_chip,
|
||||
handle_edge_irq);
|
||||
irq_set_nested_thread(cur_irq, 1);
|
||||
#ifdef CONFIG_ARM
|
||||
set_irq_flags(cur_irq, IRQF_VALID);
|
||||
#else
|
||||
irq_set_noprobe(cur_irq);
|
||||
#endif
|
||||
}
|
||||
|
||||
ret = request_threaded_irq(s5m87xx->irq, NULL,
|
||||
s5m8767_irq_thread,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
"s5m87xx-irq", s5m87xx);
|
||||
if (ret) {
|
||||
dev_err(s5m87xx->dev, "Failed to request IRQ %d: %d\n",
|
||||
s5m87xx->irq, ret);
|
||||
return ret;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
dev_err(s5m87xx->dev,
|
||||
"Unknown device type %d\n", s5m87xx->device_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (!s5m87xx->ono)
|
||||
return 0;
|
||||
|
||||
switch (type) {
|
||||
case S5M8763X:
|
||||
ret = request_threaded_irq(s5m87xx->ono, NULL,
|
||||
s5m8763_irq_thread,
|
||||
IRQF_TRIGGER_FALLING |
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_ONESHOT, "s5m87xx-ono",
|
||||
s5m87xx);
|
||||
break;
|
||||
case S5M8767X:
|
||||
ret = request_threaded_irq(s5m87xx->ono, NULL,
|
||||
s5m8767_irq_thread,
|
||||
IRQF_TRIGGER_FALLING |
|
||||
IRQF_TRIGGER_RISING |
|
||||
IRQF_ONESHOT, "s5m87xx-ono", s5m87xx);
|
||||
break;
|
||||
default:
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
if (ret) {
|
||||
dev_err(s5m87xx->dev, "Failed to request IRQ %d: %d\n",
|
||||
s5m87xx->ono, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void s5m_irq_exit(struct s5m87xx_dev *s5m87xx)
|
||||
{
|
||||
if (s5m87xx->ono)
|
||||
free_irq(s5m87xx->ono, s5m87xx);
|
||||
|
||||
if (s5m87xx->irq)
|
||||
free_irq(s5m87xx->irq, s5m87xx);
|
||||
}
|
||||
216
drivers/mfd/sec-core.c
Normal file
216
drivers/mfd/sec-core.c
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
/*
|
||||
* sec-core.c
|
||||
*
|
||||
* Copyright (c) 2012 Samsung Electronics Co., Ltd
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/moduleparam.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/pm_runtime.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/samsung/core.h>
|
||||
#include <linux/mfd/samsung/irq.h>
|
||||
#include <linux/mfd/samsung/rtc.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
static struct mfd_cell s5m8751_devs[] = {
|
||||
{
|
||||
.name = "s5m8751-pmic",
|
||||
}, {
|
||||
.name = "s5m-charger",
|
||||
}, {
|
||||
.name = "s5m8751-codec",
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell s5m8763_devs[] = {
|
||||
{
|
||||
.name = "s5m8763-pmic",
|
||||
}, {
|
||||
.name = "s5m-rtc",
|
||||
}, {
|
||||
.name = "s5m-charger",
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell s5m8767_devs[] = {
|
||||
{
|
||||
.name = "s5m8767-pmic",
|
||||
}, {
|
||||
.name = "s5m-rtc",
|
||||
},
|
||||
};
|
||||
|
||||
static struct mfd_cell s2mps11_devs[] = {
|
||||
{
|
||||
.name = "s2mps11-pmic",
|
||||
},
|
||||
};
|
||||
|
||||
int sec_reg_read(struct sec_pmic_dev *sec_pmic, u8 reg, void *dest)
|
||||
{
|
||||
return regmap_read(sec_pmic->regmap, reg, dest);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sec_reg_read);
|
||||
|
||||
int sec_bulk_read(struct sec_pmic_dev *sec_pmic, u8 reg, int count, u8 *buf)
|
||||
{
|
||||
return regmap_bulk_read(sec_pmic->regmap, reg, buf, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sec_bulk_read);
|
||||
|
||||
int sec_reg_write(struct sec_pmic_dev *sec_pmic, u8 reg, u8 value)
|
||||
{
|
||||
return regmap_write(sec_pmic->regmap, reg, value);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sec_reg_write);
|
||||
|
||||
int sec_bulk_write(struct sec_pmic_dev *sec_pmic, u8 reg, int count, u8 *buf)
|
||||
{
|
||||
return regmap_raw_write(sec_pmic->regmap, reg, buf, count);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sec_bulk_write);
|
||||
|
||||
int sec_reg_update(struct sec_pmic_dev *sec_pmic, u8 reg, u8 val, u8 mask)
|
||||
{
|
||||
return regmap_update_bits(sec_pmic->regmap, reg, mask, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(sec_reg_update);
|
||||
|
||||
static struct regmap_config sec_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
};
|
||||
|
||||
static int sec_pmic_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
struct sec_platform_data *pdata = i2c->dev.platform_data;
|
||||
struct sec_pmic_dev *sec_pmic;
|
||||
int ret;
|
||||
|
||||
sec_pmic = devm_kzalloc(&i2c->dev, sizeof(struct sec_pmic_dev),
|
||||
GFP_KERNEL);
|
||||
if (sec_pmic == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
i2c_set_clientdata(i2c, sec_pmic);
|
||||
sec_pmic->dev = &i2c->dev;
|
||||
sec_pmic->i2c = i2c;
|
||||
sec_pmic->irq = i2c->irq;
|
||||
sec_pmic->type = id->driver_data;
|
||||
|
||||
if (pdata) {
|
||||
sec_pmic->device_type = pdata->device_type;
|
||||
sec_pmic->ono = pdata->ono;
|
||||
sec_pmic->irq_base = pdata->irq_base;
|
||||
sec_pmic->wakeup = pdata->wakeup;
|
||||
}
|
||||
|
||||
sec_pmic->regmap = devm_regmap_init_i2c(i2c, &sec_regmap_config);
|
||||
if (IS_ERR(sec_pmic->regmap)) {
|
||||
ret = PTR_ERR(sec_pmic->regmap);
|
||||
dev_err(&i2c->dev, "Failed to allocate register map: %d\n",
|
||||
ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
sec_pmic->rtc = i2c_new_dummy(i2c->adapter, RTC_I2C_ADDR);
|
||||
i2c_set_clientdata(sec_pmic->rtc, sec_pmic);
|
||||
|
||||
if (pdata && pdata->cfg_pmic_irq)
|
||||
pdata->cfg_pmic_irq();
|
||||
|
||||
sec_irq_init(sec_pmic);
|
||||
|
||||
pm_runtime_set_active(sec_pmic->dev);
|
||||
|
||||
switch (sec_pmic->device_type) {
|
||||
case S5M8751X:
|
||||
ret = mfd_add_devices(sec_pmic->dev, -1, s5m8751_devs,
|
||||
ARRAY_SIZE(s5m8751_devs), NULL, 0);
|
||||
break;
|
||||
case S5M8763X:
|
||||
ret = mfd_add_devices(sec_pmic->dev, -1, s5m8763_devs,
|
||||
ARRAY_SIZE(s5m8763_devs), NULL, 0);
|
||||
break;
|
||||
case S5M8767X:
|
||||
ret = mfd_add_devices(sec_pmic->dev, -1, s5m8767_devs,
|
||||
ARRAY_SIZE(s5m8767_devs), NULL, 0);
|
||||
break;
|
||||
case S2MPS11X:
|
||||
ret = mfd_add_devices(sec_pmic->dev, -1, s2mps11_devs,
|
||||
ARRAY_SIZE(s2mps11_devs), NULL, 0);
|
||||
break;
|
||||
default:
|
||||
/* If this happens the probe function is problem */
|
||||
BUG();
|
||||
}
|
||||
|
||||
if (ret < 0)
|
||||
goto err;
|
||||
|
||||
return ret;
|
||||
|
||||
err:
|
||||
mfd_remove_devices(sec_pmic->dev);
|
||||
sec_irq_exit(sec_pmic);
|
||||
i2c_unregister_device(sec_pmic->rtc);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int sec_pmic_remove(struct i2c_client *i2c)
|
||||
{
|
||||
struct sec_pmic_dev *sec_pmic = i2c_get_clientdata(i2c);
|
||||
|
||||
mfd_remove_devices(sec_pmic->dev);
|
||||
sec_irq_exit(sec_pmic);
|
||||
i2c_unregister_device(sec_pmic->rtc);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct i2c_device_id sec_pmic_id[] = {
|
||||
{ "sec_pmic", 0 },
|
||||
{ }
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, sec_pmic_id);
|
||||
|
||||
static struct i2c_driver sec_pmic_driver = {
|
||||
.driver = {
|
||||
.name = "sec_pmic",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.probe = sec_pmic_probe,
|
||||
.remove = sec_pmic_remove,
|
||||
.id_table = sec_pmic_id,
|
||||
};
|
||||
|
||||
static int __init sec_pmic_init(void)
|
||||
{
|
||||
return i2c_add_driver(&sec_pmic_driver);
|
||||
}
|
||||
|
||||
subsys_initcall(sec_pmic_init);
|
||||
|
||||
static void __exit sec_pmic_exit(void)
|
||||
{
|
||||
i2c_del_driver(&sec_pmic_driver);
|
||||
}
|
||||
module_exit(sec_pmic_exit);
|
||||
|
||||
MODULE_AUTHOR("Sangbeom Kim <[email protected]>");
|
||||
MODULE_DESCRIPTION("Core support for the S5M MFD");
|
||||
MODULE_LICENSE("GPL");
|
||||
317
drivers/mfd/sec-irq.c
Normal file
317
drivers/mfd/sec-irq.c
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
/*
|
||||
* sec-irq.c
|
||||
*
|
||||
* Copyright (c) 2011 Samsung Electronics Co., Ltd
|
||||
* http://www.samsung.com
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the
|
||||
* Free Software Foundation; either version 2 of the License, or (at your
|
||||
* option) any later version.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/interrupt.h>
|
||||
#include <linux/irq.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/mfd/samsung/core.h>
|
||||
#include <linux/mfd/samsung/irq.h>
|
||||
#include <linux/mfd/samsung/s2mps11.h>
|
||||
#include <linux/mfd/samsung/s5m8763.h>
|
||||
#include <linux/mfd/samsung/s5m8767.h>
|
||||
|
||||
static struct regmap_irq s2mps11_irqs[] = {
|
||||
[S2MPS11_IRQ_PWRONF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_PWRONF_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_PWRONR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_PWRONR_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_JIGONBF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_JIGONBF_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_JIGONBR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_JIGONBR_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_ACOKBF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_ACOKBF_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_ACOKBR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_ACOKBR_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_PWRON1S] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_PWRON1S_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_MRB] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S2MPS11_IRQ_MRB_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_RTC60S] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S2MPS11_IRQ_RTC60S_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_RTCA1] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S2MPS11_IRQ_RTCA1_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_RTCA2] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S2MPS11_IRQ_RTCA2_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_SMPL] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S2MPS11_IRQ_SMPL_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_RTC1S] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S2MPS11_IRQ_RTC1S_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_WTSR] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S2MPS11_IRQ_WTSR_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_INT120C] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S2MPS11_IRQ_INT120C_MASK,
|
||||
},
|
||||
[S2MPS11_IRQ_INT140C] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S2MPS11_IRQ_INT140C_MASK,
|
||||
},
|
||||
};
|
||||
|
||||
|
||||
static struct regmap_irq s5m8767_irqs[] = {
|
||||
[S5M8767_IRQ_PWRR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8767_IRQ_PWRR_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_PWRF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8767_IRQ_PWRF_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_PWR1S] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8767_IRQ_PWR1S_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_JIGR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8767_IRQ_JIGR_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_JIGF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8767_IRQ_JIGF_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_LOWBAT2] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8767_IRQ_LOWBAT2_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_LOWBAT1] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8767_IRQ_LOWBAT1_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_MRB] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8767_IRQ_MRB_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_DVSOK2] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8767_IRQ_DVSOK2_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_DVSOK3] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8767_IRQ_DVSOK3_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_DVSOK4] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8767_IRQ_DVSOK4_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTC60S] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8767_IRQ_RTC60S_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTCA1] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8767_IRQ_RTCA1_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTCA2] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8767_IRQ_RTCA2_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_SMPL] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8767_IRQ_SMPL_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_RTC1S] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8767_IRQ_RTC1S_MASK,
|
||||
},
|
||||
[S5M8767_IRQ_WTSR] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8767_IRQ_WTSR_MASK,
|
||||
},
|
||||
};
|
||||
|
||||
static struct regmap_irq s5m8763_irqs[] = {
|
||||
[S5M8763_IRQ_DCINF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8763_IRQ_DCINF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_DCINR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8763_IRQ_DCINR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_JIGF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8763_IRQ_JIGF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_JIGR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8763_IRQ_JIGR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_PWRONF] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8763_IRQ_PWRONF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_PWRONR] = {
|
||||
.reg_offset = 1,
|
||||
.mask = S5M8763_IRQ_PWRONR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_WTSREVNT] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8763_IRQ_WTSREVNT_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_SMPLEVNT] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8763_IRQ_SMPLEVNT_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_ALARM1] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8763_IRQ_ALARM1_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_ALARM0] = {
|
||||
.reg_offset = 2,
|
||||
.mask = S5M8763_IRQ_ALARM0_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_ONKEY1S] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8763_IRQ_ONKEY1S_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_TOPOFFR] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8763_IRQ_TOPOFFR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_DCINOVPR] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8763_IRQ_DCINOVPR_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_CHGRSTF] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8763_IRQ_CHGRSTF_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_DONER] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8763_IRQ_DONER_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_CHGFAULT] = {
|
||||
.reg_offset = 3,
|
||||
.mask = S5M8763_IRQ_CHGFAULT_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_LOBAT1] = {
|
||||
.reg_offset = 4,
|
||||
.mask = S5M8763_IRQ_LOBAT1_MASK,
|
||||
},
|
||||
[S5M8763_IRQ_LOBAT2] = {
|
||||
.reg_offset = 4,
|
||||
.mask = S5M8763_IRQ_LOBAT2_MASK,
|
||||
},
|
||||
};
|
||||
|
||||
static struct regmap_irq_chip s2mps11_irq_chip = {
|
||||
.name = "s2mps11",
|
||||
.irqs = s2mps11_irqs,
|
||||
.num_irqs = ARRAY_SIZE(s2mps11_irqs),
|
||||
.num_regs = 3,
|
||||
.status_base = S2MPS11_REG_INT1,
|
||||
.mask_base = S2MPS11_REG_INT1M,
|
||||
.ack_base = S2MPS11_REG_INT1,
|
||||
};
|
||||
|
||||
static struct regmap_irq_chip s5m8767_irq_chip = {
|
||||
.name = "s5m8767",
|
||||
.irqs = s5m8767_irqs,
|
||||
.num_irqs = ARRAY_SIZE(s5m8767_irqs),
|
||||
.num_regs = 3,
|
||||
.status_base = S5M8767_REG_INT1,
|
||||
.mask_base = S5M8767_REG_INT1M,
|
||||
.ack_base = S5M8767_REG_INT1,
|
||||
};
|
||||
|
||||
static struct regmap_irq_chip s5m8763_irq_chip = {
|
||||
.name = "s5m8763",
|
||||
.irqs = s5m8763_irqs,
|
||||
.num_irqs = ARRAY_SIZE(s5m8763_irqs),
|
||||
.num_regs = 4,
|
||||
.status_base = S5M8763_REG_IRQ1,
|
||||
.mask_base = S5M8763_REG_IRQM1,
|
||||
.ack_base = S5M8763_REG_IRQ1,
|
||||
};
|
||||
|
||||
int sec_irq_init(struct sec_pmic_dev *sec_pmic)
|
||||
{
|
||||
int ret = 0;
|
||||
int type = sec_pmic->device_type;
|
||||
|
||||
if (!sec_pmic->irq) {
|
||||
dev_warn(sec_pmic->dev,
|
||||
"No interrupt specified, no interrupts\n");
|
||||
sec_pmic->irq_base = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
switch (type) {
|
||||
case S5M8763X:
|
||||
ret = regmap_add_irq_chip(sec_pmic->regmap, sec_pmic->irq,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
sec_pmic->irq_base, &s5m8763_irq_chip,
|
||||
&sec_pmic->irq_data);
|
||||
break;
|
||||
case S5M8767X:
|
||||
ret = regmap_add_irq_chip(sec_pmic->regmap, sec_pmic->irq,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
sec_pmic->irq_base, &s5m8767_irq_chip,
|
||||
&sec_pmic->irq_data);
|
||||
break;
|
||||
case S2MPS11X:
|
||||
ret = regmap_add_irq_chip(sec_pmic->regmap, sec_pmic->irq,
|
||||
IRQF_TRIGGER_FALLING | IRQF_ONESHOT,
|
||||
sec_pmic->irq_base, &s2mps11_irq_chip,
|
||||
&sec_pmic->irq_data);
|
||||
break;
|
||||
default:
|
||||
dev_err(sec_pmic->dev, "Unknown device type %d\n",
|
||||
sec_pmic->device_type);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (ret != 0) {
|
||||
dev_err(sec_pmic->dev, "Failed to register IRQ chip: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
void sec_irq_exit(struct sec_pmic_dev *sec_pmic)
|
||||
{
|
||||
regmap_del_irq_chip(sec_pmic->irq, sec_pmic->irq_data);
|
||||
}
|
||||
|
|
@ -357,7 +357,7 @@ static int __devexit tc3589x_remove(struct i2c_client *client)
|
|||
return 0;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_PM
|
||||
#ifdef CONFIG_PM_SLEEP
|
||||
static int tc3589x_suspend(struct device *dev)
|
||||
{
|
||||
struct tc3589x *tc3589x = dev_get_drvdata(dev);
|
||||
|
|
@ -385,11 +385,10 @@ static int tc3589x_resume(struct device *dev)
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const SIMPLE_DEV_PM_OPS(tc3589x_dev_pm_ops, tc3589x_suspend,
|
||||
tc3589x_resume);
|
||||
#endif
|
||||
|
||||
static SIMPLE_DEV_PM_OPS(tc3589x_dev_pm_ops, tc3589x_suspend, tc3589x_resume);
|
||||
|
||||
static const struct i2c_device_id tc3589x_id[] = {
|
||||
{ "tc3589x", 24 },
|
||||
{ }
|
||||
|
|
@ -399,9 +398,7 @@ MODULE_DEVICE_TABLE(i2c, tc3589x_id);
|
|||
static struct i2c_driver tc3589x_driver = {
|
||||
.driver.name = "tc3589x",
|
||||
.driver.owner = THIS_MODULE,
|
||||
#ifdef CONFIG_PM
|
||||
.driver.pm = &tc3589x_dev_pm_ops,
|
||||
#endif
|
||||
.probe = tc3589x_probe,
|
||||
.remove = __devexit_p(tc3589x_remove),
|
||||
.id_table = tc3589x_id,
|
||||
|
|
|
|||
|
|
@ -71,10 +71,10 @@ static const struct tps65090_irq_data tps65090_irqs[] = {
|
|||
|
||||
static struct mfd_cell tps65090s[] = {
|
||||
{
|
||||
.name = "tps65910-pmic",
|
||||
.name = "tps65090-pmic",
|
||||
},
|
||||
{
|
||||
.name = "tps65910-regulator",
|
||||
.name = "tps65090-regulator",
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -21,17 +21,14 @@
|
|||
#include <linux/module.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/gpio.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/i2c.h>
|
||||
#include <linux/regmap.h>
|
||||
#include <linux/regulator/of_regulator.h>
|
||||
|
||||
#include <linux/mfd/core.h>
|
||||
#include <linux/mfd/tps6586x.h>
|
||||
|
||||
/* GPIO control registers */
|
||||
#define TPS6586X_GPIOSET1 0x5d
|
||||
#define TPS6586X_GPIOSET2 0x5e
|
||||
|
||||
/* interrupt control registers */
|
||||
#define TPS6586X_INT_ACK1 0xb5
|
||||
#define TPS6586X_INT_ACK2 0xb6
|
||||
|
|
@ -48,6 +45,9 @@
|
|||
/* device id */
|
||||
#define TPS6586X_VERSIONCRC 0xcd
|
||||
|
||||
/* Maximum register */
|
||||
#define TPS6586X_MAX_REGISTER (TPS6586X_VERSIONCRC + 1)
|
||||
|
||||
struct tps6586x_irq_data {
|
||||
u8 mask_reg;
|
||||
u8 mask_mask;
|
||||
|
|
@ -89,226 +89,96 @@ static const struct tps6586x_irq_data tps6586x_irqs[] = {
|
|||
[TPS6586X_INT_RTC_ALM2] = TPS6586X_IRQ(TPS6586X_INT_MASK4, 1 << 1),
|
||||
};
|
||||
|
||||
static struct mfd_cell tps6586x_cell[] = {
|
||||
{
|
||||
.name = "tps6586x-gpio",
|
||||
},
|
||||
{
|
||||
.name = "tps6586x-rtc",
|
||||
},
|
||||
{
|
||||
.name = "tps6586x-onkey",
|
||||
},
|
||||
};
|
||||
|
||||
struct tps6586x {
|
||||
struct mutex lock;
|
||||
struct device *dev;
|
||||
struct i2c_client *client;
|
||||
struct regmap *regmap;
|
||||
|
||||
struct gpio_chip gpio;
|
||||
struct irq_chip irq_chip;
|
||||
struct mutex irq_lock;
|
||||
int irq_base;
|
||||
u32 irq_en;
|
||||
u8 mask_cache[5];
|
||||
u8 mask_reg[5];
|
||||
};
|
||||
|
||||
static inline int __tps6586x_read(struct i2c_client *client,
|
||||
int reg, uint8_t *val)
|
||||
static inline struct tps6586x *dev_to_tps6586x(struct device *dev)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_byte_data(client, reg);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "failed reading at 0x%02x\n", reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
*val = (uint8_t)ret;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int __tps6586x_reads(struct i2c_client *client, int reg,
|
||||
int len, uint8_t *val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_read_i2c_block_data(client, reg, len, val);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "failed reading from 0x%02x\n", reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int __tps6586x_write(struct i2c_client *client,
|
||||
int reg, uint8_t val)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = i2c_smbus_write_byte_data(client, reg, val);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "failed writing 0x%02x to 0x%02x\n",
|
||||
val, reg);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline int __tps6586x_writes(struct i2c_client *client, int reg,
|
||||
int len, uint8_t *val)
|
||||
{
|
||||
int ret, i;
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
ret = __tps6586x_write(client, reg + i, *(val + i));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
return i2c_get_clientdata(to_i2c_client(dev));
|
||||
}
|
||||
|
||||
int tps6586x_write(struct device *dev, int reg, uint8_t val)
|
||||
{
|
||||
return __tps6586x_write(to_i2c_client(dev), reg, val);
|
||||
struct tps6586x *tps6586x = dev_to_tps6586x(dev);
|
||||
|
||||
return regmap_write(tps6586x->regmap, reg, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tps6586x_write);
|
||||
|
||||
int tps6586x_writes(struct device *dev, int reg, int len, uint8_t *val)
|
||||
{
|
||||
return __tps6586x_writes(to_i2c_client(dev), reg, len, val);
|
||||
struct tps6586x *tps6586x = dev_to_tps6586x(dev);
|
||||
|
||||
return regmap_bulk_write(tps6586x->regmap, reg, val, len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tps6586x_writes);
|
||||
|
||||
int tps6586x_read(struct device *dev, int reg, uint8_t *val)
|
||||
{
|
||||
return __tps6586x_read(to_i2c_client(dev), reg, val);
|
||||
struct tps6586x *tps6586x = dev_to_tps6586x(dev);
|
||||
unsigned int rval;
|
||||
int ret;
|
||||
|
||||
ret = regmap_read(tps6586x->regmap, reg, &rval);
|
||||
if (!ret)
|
||||
*val = rval;
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tps6586x_read);
|
||||
|
||||
int tps6586x_reads(struct device *dev, int reg, int len, uint8_t *val)
|
||||
{
|
||||
return __tps6586x_reads(to_i2c_client(dev), reg, len, val);
|
||||
struct tps6586x *tps6586x = dev_to_tps6586x(dev);
|
||||
|
||||
return regmap_bulk_read(tps6586x->regmap, reg, val, len);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tps6586x_reads);
|
||||
|
||||
int tps6586x_set_bits(struct device *dev, int reg, uint8_t bit_mask)
|
||||
{
|
||||
struct tps6586x *tps6586x = dev_get_drvdata(dev);
|
||||
uint8_t reg_val;
|
||||
int ret = 0;
|
||||
struct tps6586x *tps6586x = dev_to_tps6586x(dev);
|
||||
|
||||
mutex_lock(&tps6586x->lock);
|
||||
|
||||
ret = __tps6586x_read(to_i2c_client(dev), reg, ®_val);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if ((reg_val & bit_mask) != bit_mask) {
|
||||
reg_val |= bit_mask;
|
||||
ret = __tps6586x_write(to_i2c_client(dev), reg, reg_val);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&tps6586x->lock);
|
||||
return ret;
|
||||
return regmap_update_bits(tps6586x->regmap, reg, bit_mask, bit_mask);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tps6586x_set_bits);
|
||||
|
||||
int tps6586x_clr_bits(struct device *dev, int reg, uint8_t bit_mask)
|
||||
{
|
||||
struct tps6586x *tps6586x = dev_get_drvdata(dev);
|
||||
uint8_t reg_val;
|
||||
int ret = 0;
|
||||
struct tps6586x *tps6586x = dev_to_tps6586x(dev);
|
||||
|
||||
mutex_lock(&tps6586x->lock);
|
||||
|
||||
ret = __tps6586x_read(to_i2c_client(dev), reg, ®_val);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if (reg_val & bit_mask) {
|
||||
reg_val &= ~bit_mask;
|
||||
ret = __tps6586x_write(to_i2c_client(dev), reg, reg_val);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&tps6586x->lock);
|
||||
return ret;
|
||||
return regmap_update_bits(tps6586x->regmap, reg, bit_mask, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tps6586x_clr_bits);
|
||||
|
||||
int tps6586x_update(struct device *dev, int reg, uint8_t val, uint8_t mask)
|
||||
{
|
||||
struct tps6586x *tps6586x = dev_get_drvdata(dev);
|
||||
uint8_t reg_val;
|
||||
int ret = 0;
|
||||
struct tps6586x *tps6586x = dev_to_tps6586x(dev);
|
||||
|
||||
mutex_lock(&tps6586x->lock);
|
||||
|
||||
ret = __tps6586x_read(tps6586x->client, reg, ®_val);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
if ((reg_val & mask) != val) {
|
||||
reg_val = (reg_val & ~mask) | val;
|
||||
ret = __tps6586x_write(tps6586x->client, reg, reg_val);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&tps6586x->lock);
|
||||
return ret;
|
||||
return regmap_update_bits(tps6586x->regmap, reg, mask, val);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(tps6586x_update);
|
||||
|
||||
static int tps6586x_gpio_get(struct gpio_chip *gc, unsigned offset)
|
||||
{
|
||||
struct tps6586x *tps6586x = container_of(gc, struct tps6586x, gpio);
|
||||
uint8_t val;
|
||||
int ret;
|
||||
|
||||
ret = __tps6586x_read(tps6586x->client, TPS6586X_GPIOSET2, &val);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
return !!(val & (1 << offset));
|
||||
}
|
||||
|
||||
|
||||
static void tps6586x_gpio_set(struct gpio_chip *chip, unsigned offset,
|
||||
int value)
|
||||
{
|
||||
struct tps6586x *tps6586x = container_of(chip, struct tps6586x, gpio);
|
||||
|
||||
tps6586x_update(tps6586x->dev, TPS6586X_GPIOSET2,
|
||||
value << offset, 1 << offset);
|
||||
}
|
||||
|
||||
static int tps6586x_gpio_output(struct gpio_chip *gc, unsigned offset,
|
||||
int value)
|
||||
{
|
||||
struct tps6586x *tps6586x = container_of(gc, struct tps6586x, gpio);
|
||||
uint8_t val, mask;
|
||||
|
||||
tps6586x_gpio_set(gc, offset, value);
|
||||
|
||||
val = 0x1 << (offset * 2);
|
||||
mask = 0x3 << (offset * 2);
|
||||
|
||||
return tps6586x_update(tps6586x->dev, TPS6586X_GPIOSET1, val, mask);
|
||||
}
|
||||
|
||||
static int tps6586x_gpio_init(struct tps6586x *tps6586x, int gpio_base)
|
||||
{
|
||||
if (!gpio_base)
|
||||
return 0;
|
||||
|
||||
tps6586x->gpio.owner = THIS_MODULE;
|
||||
tps6586x->gpio.label = tps6586x->client->name;
|
||||
tps6586x->gpio.dev = tps6586x->dev;
|
||||
tps6586x->gpio.base = gpio_base;
|
||||
tps6586x->gpio.ngpio = 4;
|
||||
tps6586x->gpio.can_sleep = 1;
|
||||
|
||||
/* FIXME: add handling of GPIOs as dedicated inputs */
|
||||
tps6586x->gpio.direction_output = tps6586x_gpio_output;
|
||||
tps6586x->gpio.set = tps6586x_gpio_set;
|
||||
tps6586x->gpio.get = tps6586x_gpio_get;
|
||||
|
||||
return gpiochip_add(&tps6586x->gpio);
|
||||
}
|
||||
|
||||
static int __remove_subdev(struct device *dev, void *unused)
|
||||
{
|
||||
platform_device_unregister(to_platform_device(dev));
|
||||
|
|
@ -354,12 +224,11 @@ static void tps6586x_irq_sync_unlock(struct irq_data *data)
|
|||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(tps6586x->mask_reg); i++) {
|
||||
if (tps6586x->mask_reg[i] != tps6586x->mask_cache[i]) {
|
||||
if (!WARN_ON(tps6586x_write(tps6586x->dev,
|
||||
TPS6586X_INT_MASK1 + i,
|
||||
tps6586x->mask_reg[i])))
|
||||
tps6586x->mask_cache[i] = tps6586x->mask_reg[i];
|
||||
}
|
||||
int ret;
|
||||
ret = tps6586x_write(tps6586x->dev,
|
||||
TPS6586X_INT_MASK1 + i,
|
||||
tps6586x->mask_reg[i]);
|
||||
WARN_ON(ret);
|
||||
}
|
||||
|
||||
mutex_unlock(&tps6586x->irq_lock);
|
||||
|
|
@ -406,7 +275,6 @@ static int __devinit tps6586x_irq_init(struct tps6586x *tps6586x, int irq,
|
|||
|
||||
mutex_init(&tps6586x->irq_lock);
|
||||
for (i = 0; i < 5; i++) {
|
||||
tps6586x->mask_cache[i] = 0xff;
|
||||
tps6586x->mask_reg[i] = 0xff;
|
||||
tps6586x_write(tps6586x->dev, TPS6586X_INT_MASK1 + i, 0xff);
|
||||
}
|
||||
|
|
@ -556,6 +424,23 @@ static struct tps6586x_platform_data *tps6586x_parse_dt(struct i2c_client *clien
|
|||
}
|
||||
#endif
|
||||
|
||||
static bool is_volatile_reg(struct device *dev, unsigned int reg)
|
||||
{
|
||||
/* Cache all interrupt mask register */
|
||||
if ((reg >= TPS6586X_INT_MASK1) && (reg <= TPS6586X_INT_MASK5))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static const struct regmap_config tps6586x_regmap_config = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 8,
|
||||
.max_register = TPS6586X_MAX_REGISTER - 1,
|
||||
.volatile_reg = is_volatile_reg,
|
||||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
static int __devinit tps6586x_i2c_probe(struct i2c_client *client,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
|
@ -579,29 +464,39 @@ static int __devinit tps6586x_i2c_probe(struct i2c_client *client,
|
|||
|
||||
dev_info(&client->dev, "VERSIONCRC is %02x\n", ret);
|
||||
|
||||
tps6586x = kzalloc(sizeof(struct tps6586x), GFP_KERNEL);
|
||||
if (tps6586x == NULL)
|
||||
tps6586x = devm_kzalloc(&client->dev, sizeof(*tps6586x), GFP_KERNEL);
|
||||
if (tps6586x == NULL) {
|
||||
dev_err(&client->dev, "memory for tps6586x alloc failed\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
tps6586x->client = client;
|
||||
tps6586x->dev = &client->dev;
|
||||
i2c_set_clientdata(client, tps6586x);
|
||||
|
||||
mutex_init(&tps6586x->lock);
|
||||
tps6586x->regmap = devm_regmap_init_i2c(client,
|
||||
&tps6586x_regmap_config);
|
||||
if (IS_ERR(tps6586x->regmap)) {
|
||||
ret = PTR_ERR(tps6586x->regmap);
|
||||
dev_err(&client->dev, "regmap init failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
if (client->irq) {
|
||||
ret = tps6586x_irq_init(tps6586x, client->irq,
|
||||
pdata->irq_base);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "IRQ init failed: %d\n", ret);
|
||||
goto err_irq_init;
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
||||
ret = tps6586x_gpio_init(tps6586x, pdata->gpio_base);
|
||||
if (ret) {
|
||||
dev_err(&client->dev, "GPIO registration failed: %d\n", ret);
|
||||
goto err_gpio_init;
|
||||
ret = mfd_add_devices(tps6586x->dev, -1,
|
||||
tps6586x_cell, ARRAY_SIZE(tps6586x_cell), NULL, 0);
|
||||
if (ret < 0) {
|
||||
dev_err(&client->dev, "mfd_add_devices failed: %d\n", ret);
|
||||
goto err_mfd_add;
|
||||
}
|
||||
|
||||
ret = tps6586x_add_subdevs(tps6586x, pdata);
|
||||
|
|
@ -613,38 +508,21 @@ static int __devinit tps6586x_i2c_probe(struct i2c_client *client,
|
|||
return 0;
|
||||
|
||||
err_add_devs:
|
||||
if (pdata->gpio_base) {
|
||||
ret = gpiochip_remove(&tps6586x->gpio);
|
||||
if (ret)
|
||||
dev_err(&client->dev, "Can't remove gpio chip: %d\n",
|
||||
ret);
|
||||
}
|
||||
err_gpio_init:
|
||||
mfd_remove_devices(tps6586x->dev);
|
||||
err_mfd_add:
|
||||
if (client->irq)
|
||||
free_irq(client->irq, tps6586x);
|
||||
err_irq_init:
|
||||
kfree(tps6586x);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __devexit tps6586x_i2c_remove(struct i2c_client *client)
|
||||
{
|
||||
struct tps6586x *tps6586x = i2c_get_clientdata(client);
|
||||
struct tps6586x_platform_data *pdata = client->dev.platform_data;
|
||||
int ret;
|
||||
|
||||
if (client->irq)
|
||||
free_irq(client->irq, tps6586x);
|
||||
|
||||
if (pdata->gpio_base) {
|
||||
ret = gpiochip_remove(&tps6586x->gpio);
|
||||
if (ret)
|
||||
dev_err(&client->dev, "Can't remove gpio chip: %d\n",
|
||||
ret);
|
||||
}
|
||||
|
||||
tps6586x_remove_subdevs(tps6586x);
|
||||
kfree(tps6586x);
|
||||
mfd_remove_devices(tps6586x->dev);
|
||||
if (client->irq)
|
||||
free_irq(client->irq, tps6586x);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -68,6 +68,24 @@ static const struct regmap_config tps65910_regmap_config = {
|
|||
.cache_type = REGCACHE_RBTREE,
|
||||
};
|
||||
|
||||
static int __devinit tps65910_ck32k_init(struct tps65910 *tps65910,
|
||||
struct tps65910_board *pmic_pdata)
|
||||
{
|
||||
int ret;
|
||||
|
||||
if (!pmic_pdata->en_ck32k_xtal)
|
||||
return 0;
|
||||
|
||||
ret = tps65910_reg_clear_bits(tps65910, TPS65910_DEVCTRL,
|
||||
DEVCTRL_CK32K_CTRL_MASK);
|
||||
if (ret < 0) {
|
||||
dev_err(tps65910->dev, "clear ck32k_ctrl failed: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __devinit tps65910_sleepinit(struct tps65910 *tps65910,
|
||||
struct tps65910_board *pmic_pdata)
|
||||
{
|
||||
|
|
@ -175,6 +193,9 @@ static struct tps65910_board *tps65910_parse_dt(struct i2c_client *client,
|
|||
else if (*chip_id == TPS65911)
|
||||
dev_warn(&client->dev, "VMBCH2-Threshold not specified");
|
||||
|
||||
prop = of_property_read_bool(np, "ti,en-ck32k-xtal");
|
||||
board_info->en_ck32k_xtal = prop;
|
||||
|
||||
board_info->irq = client->irq;
|
||||
board_info->irq_base = -1;
|
||||
|
||||
|
|
@ -243,7 +264,7 @@ static __devinit int tps65910_i2c_probe(struct i2c_client *i2c,
|
|||
init_data->irq_base = pmic_plat_data->irq_base;
|
||||
|
||||
tps65910_irq_init(tps65910, init_data->irq, init_data);
|
||||
|
||||
tps65910_ck32k_init(tps65910, pmic_plat_data);
|
||||
tps65910_sleepinit(tps65910, pmic_plat_data);
|
||||
|
||||
return ret;
|
||||
|
|
|
|||
|
|
@ -568,7 +568,6 @@ add_numbered_child(unsigned chip, const char *name, int num,
|
|||
goto err;
|
||||
}
|
||||
|
||||
device_init_wakeup(&pdev->dev, can_wakeup);
|
||||
pdev->dev.parent = &twl->client->dev;
|
||||
|
||||
if (pdata) {
|
||||
|
|
@ -593,6 +592,8 @@ add_numbered_child(unsigned chip, const char *name, int num,
|
|||
}
|
||||
|
||||
status = platform_device_add(pdev);
|
||||
if (status == 0)
|
||||
device_init_wakeup(&pdev->dev, can_wakeup);
|
||||
|
||||
err:
|
||||
if (status < 0) {
|
||||
|
|
|
|||
|
|
@ -64,19 +64,15 @@ int twl6040_reg_read(struct twl6040 *twl6040, unsigned int reg)
|
|||
int ret;
|
||||
unsigned int val;
|
||||
|
||||
mutex_lock(&twl6040->io_mutex);
|
||||
/* Vibra control registers from cache */
|
||||
if (unlikely(reg == TWL6040_REG_VIBCTLL ||
|
||||
reg == TWL6040_REG_VIBCTLR)) {
|
||||
val = twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)];
|
||||
} else {
|
||||
ret = regmap_read(twl6040->regmap, reg, &val);
|
||||
if (ret < 0) {
|
||||
mutex_unlock(&twl6040->io_mutex);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
mutex_unlock(&twl6040->io_mutex);
|
||||
|
||||
return val;
|
||||
}
|
||||
|
|
@ -86,12 +82,10 @@ int twl6040_reg_write(struct twl6040 *twl6040, unsigned int reg, u8 val)
|
|||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&twl6040->io_mutex);
|
||||
ret = regmap_write(twl6040->regmap, reg, val);
|
||||
/* Cache the vibra control registers */
|
||||
if (reg == TWL6040_REG_VIBCTLL || reg == TWL6040_REG_VIBCTLR)
|
||||
twl6040->vibra_ctrl_cache[VIBRACTRL_MEMBER(reg)] = val;
|
||||
mutex_unlock(&twl6040->io_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -99,23 +93,13 @@ EXPORT_SYMBOL(twl6040_reg_write);
|
|||
|
||||
int twl6040_set_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&twl6040->io_mutex);
|
||||
ret = regmap_update_bits(twl6040->regmap, reg, mask, mask);
|
||||
mutex_unlock(&twl6040->io_mutex);
|
||||
return ret;
|
||||
return regmap_update_bits(twl6040->regmap, reg, mask, mask);
|
||||
}
|
||||
EXPORT_SYMBOL(twl6040_set_bits);
|
||||
|
||||
int twl6040_clear_bits(struct twl6040 *twl6040, unsigned int reg, u8 mask)
|
||||
{
|
||||
int ret;
|
||||
|
||||
mutex_lock(&twl6040->io_mutex);
|
||||
ret = regmap_update_bits(twl6040->regmap, reg, mask, 0);
|
||||
mutex_unlock(&twl6040->io_mutex);
|
||||
return ret;
|
||||
return regmap_update_bits(twl6040->regmap, reg, mask, 0);
|
||||
}
|
||||
EXPORT_SYMBOL(twl6040_clear_bits);
|
||||
|
||||
|
|
@ -573,7 +557,6 @@ static int __devinit twl6040_probe(struct i2c_client *client,
|
|||
twl6040->irq = client->irq;
|
||||
|
||||
mutex_init(&twl6040->mutex);
|
||||
mutex_init(&twl6040->io_mutex);
|
||||
init_completion(&twl6040->ready);
|
||||
|
||||
twl6040->rev = twl6040_reg_read(twl6040, TWL6040_REG_ASICREV);
|
||||
|
|
@ -696,6 +679,7 @@ static int __devexit twl6040_remove(struct i2c_client *client)
|
|||
|
||||
static const struct i2c_device_id twl6040_i2c_id[] = {
|
||||
{ "twl6040", 0, },
|
||||
{ "twl6041", 0, },
|
||||
{ },
|
||||
};
|
||||
MODULE_DEVICE_TABLE(i2c, twl6040_i2c_id);
|
||||
|
|
|
|||
2399
drivers/mfd/wm5102-tables.c
Normal file
2399
drivers/mfd/wm5102-tables.c
Normal file
File diff suppressed because it is too large
Load diff
2281
drivers/mfd/wm5110-tables.c
Normal file
2281
drivers/mfd/wm5110-tables.c
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -32,9 +32,6 @@
|
|||
#include <linux/mfd/wm8350/supply.h>
|
||||
#include <linux/mfd/wm8350/wdt.h>
|
||||
|
||||
#define WM8350_UNLOCK_KEY 0x0013
|
||||
#define WM8350_LOCK_KEY 0x0000
|
||||
|
||||
#define WM8350_CLOCK_CONTROL_1 0x28
|
||||
#define WM8350_AIF_TEST 0x74
|
||||
|
||||
|
|
@ -63,181 +60,32 @@
|
|||
/*
|
||||
* WM8350 Device IO
|
||||
*/
|
||||
static DEFINE_MUTEX(io_mutex);
|
||||
static DEFINE_MUTEX(reg_lock_mutex);
|
||||
|
||||
/* Perform a physical read from the device.
|
||||
*/
|
||||
static int wm8350_phys_read(struct wm8350 *wm8350, u8 reg, int num_regs,
|
||||
u16 *dest)
|
||||
{
|
||||
int i, ret;
|
||||
int bytes = num_regs * 2;
|
||||
|
||||
dev_dbg(wm8350->dev, "volatile read\n");
|
||||
ret = regmap_raw_read(wm8350->regmap, reg, dest, bytes);
|
||||
|
||||
for (i = reg; i < reg + num_regs; i++) {
|
||||
/* Cache is CPU endian */
|
||||
dest[i - reg] = be16_to_cpu(dest[i - reg]);
|
||||
|
||||
/* Mask out non-readable bits */
|
||||
dest[i - reg] &= wm8350_reg_io_map[i].readable;
|
||||
}
|
||||
|
||||
dump(num_regs, dest);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int wm8350_read(struct wm8350 *wm8350, u8 reg, int num_regs, u16 *dest)
|
||||
{
|
||||
int i;
|
||||
int end = reg + num_regs;
|
||||
int ret = 0;
|
||||
int bytes = num_regs * 2;
|
||||
|
||||
if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) {
|
||||
dev_err(wm8350->dev, "invalid reg %x\n",
|
||||
reg + num_regs - 1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
dev_dbg(wm8350->dev,
|
||||
"%s R%d(0x%2.2x) %d regs\n", __func__, reg, reg, num_regs);
|
||||
|
||||
#if WM8350_BUS_DEBUG
|
||||
/* we can _safely_ read any register, but warn if read not supported */
|
||||
for (i = reg; i < end; i++) {
|
||||
if (!wm8350_reg_io_map[i].readable)
|
||||
dev_warn(wm8350->dev,
|
||||
"reg R%d is not readable\n", i);
|
||||
}
|
||||
#endif
|
||||
|
||||
/* if any volatile registers are required, then read back all */
|
||||
for (i = reg; i < end; i++)
|
||||
if (wm8350_reg_io_map[i].vol)
|
||||
return wm8350_phys_read(wm8350, reg, num_regs, dest);
|
||||
|
||||
/* no volatiles, then cache is good */
|
||||
dev_dbg(wm8350->dev, "cache read\n");
|
||||
memcpy(dest, &wm8350->reg_cache[reg], bytes);
|
||||
dump(num_regs, dest);
|
||||
return ret;
|
||||
}
|
||||
|
||||
static inline int is_reg_locked(struct wm8350 *wm8350, u8 reg)
|
||||
{
|
||||
if (reg == WM8350_SECURITY ||
|
||||
wm8350->reg_cache[WM8350_SECURITY] == WM8350_UNLOCK_KEY)
|
||||
return 0;
|
||||
|
||||
if ((reg >= WM8350_GPIO_FUNCTION_SELECT_1 &&
|
||||
reg <= WM8350_GPIO_FUNCTION_SELECT_4) ||
|
||||
(reg >= WM8350_BATTERY_CHARGER_CONTROL_1 &&
|
||||
reg <= WM8350_BATTERY_CHARGER_CONTROL_3))
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int wm8350_write(struct wm8350 *wm8350, u8 reg, int num_regs, u16 *src)
|
||||
{
|
||||
int i;
|
||||
int end = reg + num_regs;
|
||||
int bytes = num_regs * 2;
|
||||
|
||||
if ((reg + num_regs - 1) > WM8350_MAX_REGISTER) {
|
||||
dev_err(wm8350->dev, "invalid reg %x\n",
|
||||
reg + num_regs - 1);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/* it's generally not a good idea to write to RO or locked registers */
|
||||
for (i = reg; i < end; i++) {
|
||||
if (!wm8350_reg_io_map[i].writable) {
|
||||
dev_err(wm8350->dev,
|
||||
"attempted write to read only reg R%d\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
if (is_reg_locked(wm8350, i)) {
|
||||
dev_err(wm8350->dev,
|
||||
"attempted write to locked reg R%d\n", i);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
src[i - reg] &= wm8350_reg_io_map[i].writable;
|
||||
|
||||
wm8350->reg_cache[i] =
|
||||
(wm8350->reg_cache[i] & ~wm8350_reg_io_map[i].writable)
|
||||
| src[i - reg];
|
||||
|
||||
src[i - reg] = cpu_to_be16(src[i - reg]);
|
||||
}
|
||||
|
||||
/* Actually write it out */
|
||||
return regmap_raw_write(wm8350->regmap, reg, src, bytes);
|
||||
}
|
||||
|
||||
/*
|
||||
* Safe read, modify, write methods
|
||||
*/
|
||||
int wm8350_clear_bits(struct wm8350 *wm8350, u16 reg, u16 mask)
|
||||
{
|
||||
u16 data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&io_mutex);
|
||||
err = wm8350_read(wm8350, reg, 1, &data);
|
||||
if (err) {
|
||||
dev_err(wm8350->dev, "read from reg R%d failed\n", reg);
|
||||
goto out;
|
||||
}
|
||||
|
||||
data &= ~mask;
|
||||
err = wm8350_write(wm8350, reg, 1, &data);
|
||||
if (err)
|
||||
dev_err(wm8350->dev, "write to reg R%d failed\n", reg);
|
||||
out:
|
||||
mutex_unlock(&io_mutex);
|
||||
return err;
|
||||
return regmap_update_bits(wm8350->regmap, reg, mask, 0);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_clear_bits);
|
||||
|
||||
int wm8350_set_bits(struct wm8350 *wm8350, u16 reg, u16 mask)
|
||||
{
|
||||
u16 data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&io_mutex);
|
||||
err = wm8350_read(wm8350, reg, 1, &data);
|
||||
if (err) {
|
||||
dev_err(wm8350->dev, "read from reg R%d failed\n", reg);
|
||||
goto out;
|
||||
}
|
||||
|
||||
data |= mask;
|
||||
err = wm8350_write(wm8350, reg, 1, &data);
|
||||
if (err)
|
||||
dev_err(wm8350->dev, "write to reg R%d failed\n", reg);
|
||||
out:
|
||||
mutex_unlock(&io_mutex);
|
||||
return err;
|
||||
return regmap_update_bits(wm8350->regmap, reg, mask, mask);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_set_bits);
|
||||
|
||||
u16 wm8350_reg_read(struct wm8350 *wm8350, int reg)
|
||||
{
|
||||
u16 data;
|
||||
unsigned int data;
|
||||
int err;
|
||||
|
||||
mutex_lock(&io_mutex);
|
||||
err = wm8350_read(wm8350, reg, 1, &data);
|
||||
err = regmap_read(wm8350->regmap, reg, &data);
|
||||
if (err)
|
||||
dev_err(wm8350->dev, "read from reg R%d failed\n", reg);
|
||||
|
||||
mutex_unlock(&io_mutex);
|
||||
return data;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_reg_read);
|
||||
|
|
@ -245,13 +93,11 @@ EXPORT_SYMBOL_GPL(wm8350_reg_read);
|
|||
int wm8350_reg_write(struct wm8350 *wm8350, int reg, u16 val)
|
||||
{
|
||||
int ret;
|
||||
u16 data = val;
|
||||
|
||||
mutex_lock(&io_mutex);
|
||||
ret = wm8350_write(wm8350, reg, 1, &data);
|
||||
ret = regmap_write(wm8350->regmap, reg, val);
|
||||
|
||||
if (ret)
|
||||
dev_err(wm8350->dev, "write to reg R%d failed\n", reg);
|
||||
mutex_unlock(&io_mutex);
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_reg_write);
|
||||
|
|
@ -261,12 +107,11 @@ int wm8350_block_read(struct wm8350 *wm8350, int start_reg, int regs,
|
|||
{
|
||||
int err = 0;
|
||||
|
||||
mutex_lock(&io_mutex);
|
||||
err = wm8350_read(wm8350, start_reg, regs, dest);
|
||||
err = regmap_bulk_read(wm8350->regmap, start_reg, dest, regs);
|
||||
if (err)
|
||||
dev_err(wm8350->dev, "block read starting from R%d failed\n",
|
||||
start_reg);
|
||||
mutex_unlock(&io_mutex);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_block_read);
|
||||
|
|
@ -276,12 +121,11 @@ int wm8350_block_write(struct wm8350 *wm8350, int start_reg, int regs,
|
|||
{
|
||||
int ret = 0;
|
||||
|
||||
mutex_lock(&io_mutex);
|
||||
ret = wm8350_write(wm8350, start_reg, regs, src);
|
||||
ret = regmap_bulk_write(wm8350->regmap, start_reg, src, regs);
|
||||
if (ret)
|
||||
dev_err(wm8350->dev, "block write starting at R%d failed\n",
|
||||
start_reg);
|
||||
mutex_unlock(&io_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_block_write);
|
||||
|
|
@ -295,15 +139,20 @@ EXPORT_SYMBOL_GPL(wm8350_block_write);
|
|||
*/
|
||||
int wm8350_reg_lock(struct wm8350 *wm8350)
|
||||
{
|
||||
u16 key = WM8350_LOCK_KEY;
|
||||
int ret;
|
||||
|
||||
mutex_lock(®_lock_mutex);
|
||||
|
||||
ldbg(__func__);
|
||||
mutex_lock(&io_mutex);
|
||||
ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key);
|
||||
|
||||
ret = wm8350_reg_write(wm8350, WM8350_SECURITY, WM8350_LOCK_KEY);
|
||||
if (ret)
|
||||
dev_err(wm8350->dev, "lock failed\n");
|
||||
mutex_unlock(&io_mutex);
|
||||
|
||||
wm8350->unlocked = false;
|
||||
|
||||
mutex_unlock(®_lock_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_reg_lock);
|
||||
|
|
@ -319,15 +168,20 @@ EXPORT_SYMBOL_GPL(wm8350_reg_lock);
|
|||
*/
|
||||
int wm8350_reg_unlock(struct wm8350 *wm8350)
|
||||
{
|
||||
u16 key = WM8350_UNLOCK_KEY;
|
||||
int ret;
|
||||
|
||||
mutex_lock(®_lock_mutex);
|
||||
|
||||
ldbg(__func__);
|
||||
mutex_lock(&io_mutex);
|
||||
ret = wm8350_write(wm8350, WM8350_SECURITY, 1, &key);
|
||||
|
||||
ret = wm8350_reg_write(wm8350, WM8350_SECURITY, WM8350_UNLOCK_KEY);
|
||||
if (ret)
|
||||
dev_err(wm8350->dev, "unlock failed\n");
|
||||
mutex_unlock(&io_mutex);
|
||||
|
||||
wm8350->unlocked = true;
|
||||
|
||||
mutex_unlock(®_lock_mutex);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_reg_unlock);
|
||||
|
|
@ -394,146 +248,6 @@ static irqreturn_t wm8350_auxadc_irq(int irq, void *irq_data)
|
|||
return IRQ_HANDLED;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cache is always host endian.
|
||||
*/
|
||||
static int wm8350_create_cache(struct wm8350 *wm8350, int type, int mode)
|
||||
{
|
||||
int i, ret = 0;
|
||||
u16 value;
|
||||
const u16 *reg_map;
|
||||
|
||||
switch (type) {
|
||||
case 0:
|
||||
switch (mode) {
|
||||
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_0
|
||||
case 0:
|
||||
reg_map = wm8350_mode0_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_1
|
||||
case 1:
|
||||
reg_map = wm8350_mode1_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_2
|
||||
case 2:
|
||||
reg_map = wm8350_mode2_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8350_CONFIG_MODE_3
|
||||
case 3:
|
||||
reg_map = wm8350_mode3_defaults;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
dev_err(wm8350->dev,
|
||||
"WM8350 configuration mode %d not supported\n",
|
||||
mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case 1:
|
||||
switch (mode) {
|
||||
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_0
|
||||
case 0:
|
||||
reg_map = wm8351_mode0_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_1
|
||||
case 1:
|
||||
reg_map = wm8351_mode1_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_2
|
||||
case 2:
|
||||
reg_map = wm8351_mode2_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8351_CONFIG_MODE_3
|
||||
case 3:
|
||||
reg_map = wm8351_mode3_defaults;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
dev_err(wm8350->dev,
|
||||
"WM8351 configuration mode %d not supported\n",
|
||||
mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
case 2:
|
||||
switch (mode) {
|
||||
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_0
|
||||
case 0:
|
||||
reg_map = wm8352_mode0_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_1
|
||||
case 1:
|
||||
reg_map = wm8352_mode1_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_2
|
||||
case 2:
|
||||
reg_map = wm8352_mode2_defaults;
|
||||
break;
|
||||
#endif
|
||||
#ifdef CONFIG_MFD_WM8352_CONFIG_MODE_3
|
||||
case 3:
|
||||
reg_map = wm8352_mode3_defaults;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
dev_err(wm8350->dev,
|
||||
"WM8352 configuration mode %d not supported\n",
|
||||
mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
dev_err(wm8350->dev,
|
||||
"WM835x configuration mode %d not supported\n",
|
||||
mode);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
wm8350->reg_cache =
|
||||
kmalloc(sizeof(u16) * (WM8350_MAX_REGISTER + 1), GFP_KERNEL);
|
||||
if (wm8350->reg_cache == NULL)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Read the initial cache state back from the device - this is
|
||||
* a PMIC so the device many not be in a virgin state and we
|
||||
* can't rely on the silicon values.
|
||||
*/
|
||||
ret = regmap_raw_read(wm8350->regmap, 0, wm8350->reg_cache,
|
||||
sizeof(u16) * (WM8350_MAX_REGISTER + 1));
|
||||
if (ret < 0) {
|
||||
dev_err(wm8350->dev,
|
||||
"failed to read initial cache values\n");
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Mask out uncacheable/unreadable bits and the audio. */
|
||||
for (i = 0; i < WM8350_MAX_REGISTER; i++) {
|
||||
if (wm8350_reg_io_map[i].readable &&
|
||||
(i < WM8350_CLOCK_CONTROL_1 || i > WM8350_AIF_TEST)) {
|
||||
value = be16_to_cpu(wm8350->reg_cache[i]);
|
||||
value &= wm8350_reg_io_map[i].readable;
|
||||
wm8350->reg_cache[i] = value;
|
||||
} else
|
||||
wm8350->reg_cache[i] = reg_map[i];
|
||||
}
|
||||
|
||||
out:
|
||||
kfree(wm8350->reg_cache);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Register a client device. This is non-fatal since there is no need to
|
||||
* fail the entire device init due to a single platform device failing.
|
||||
|
|
@ -681,18 +395,12 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
|
|||
goto err;
|
||||
}
|
||||
|
||||
ret = wm8350_create_cache(wm8350, mask_rev, mode);
|
||||
if (ret < 0) {
|
||||
dev_err(wm8350->dev, "Failed to create register cache\n");
|
||||
return ret;
|
||||
}
|
||||
|
||||
mutex_init(&wm8350->auxadc_mutex);
|
||||
init_completion(&wm8350->auxadc_done);
|
||||
|
||||
ret = wm8350_irq_init(wm8350, irq, pdata);
|
||||
if (ret < 0)
|
||||
goto err_free;
|
||||
goto err;
|
||||
|
||||
if (wm8350->irq_base) {
|
||||
ret = request_threaded_irq(wm8350->irq_base +
|
||||
|
|
@ -730,8 +438,6 @@ int wm8350_device_init(struct wm8350 *wm8350, int irq,
|
|||
|
||||
err_irq:
|
||||
wm8350_irq_exit(wm8350);
|
||||
err_free:
|
||||
kfree(wm8350->reg_cache);
|
||||
err:
|
||||
return ret;
|
||||
}
|
||||
|
|
@ -758,8 +464,6 @@ void wm8350_device_exit(struct wm8350 *wm8350)
|
|||
free_irq(wm8350->irq_base + WM8350_IRQ_AUXADC_DATARDY, wm8350);
|
||||
|
||||
wm8350_irq_exit(wm8350);
|
||||
|
||||
kfree(wm8350->reg_cache);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(wm8350_device_exit);
|
||||
|
||||
|
|
|
|||
|
|
@ -23,11 +23,6 @@
|
|||
#include <linux/regmap.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static const struct regmap_config wm8350_regmap = {
|
||||
.reg_bits = 8,
|
||||
.val_bits = 16,
|
||||
};
|
||||
|
||||
static int wm8350_i2c_probe(struct i2c_client *i2c,
|
||||
const struct i2c_device_id *id)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -432,11 +432,9 @@ static void wm8350_irq_sync_unlock(struct irq_data *data)
|
|||
for (i = 0; i < ARRAY_SIZE(wm8350->irq_masks); i++) {
|
||||
/* If there's been a change in the mask write it back
|
||||
* to the hardware. */
|
||||
if (wm8350->irq_masks[i] !=
|
||||
wm8350->reg_cache[WM8350_INT_STATUS_1_MASK + i])
|
||||
WARN_ON(wm8350_reg_write(wm8350,
|
||||
WM8350_INT_STATUS_1_MASK + i,
|
||||
wm8350->irq_masks[i]));
|
||||
WARN_ON(regmap_update_bits(wm8350->regmap,
|
||||
WM8350_INT_STATUS_1_MASK + i,
|
||||
0xffff, wm8350->irq_masks[i]));
|
||||
}
|
||||
|
||||
mutex_unlock(&wm8350->irq_lock);
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -283,9 +283,24 @@ static int wm8994_suspend(struct device *dev)
|
|||
wm8994_reg_write(wm8994, WM8994_SOFTWARE_RESET,
|
||||
wm8994_reg_read(wm8994, WM8994_SOFTWARE_RESET));
|
||||
|
||||
regcache_cache_only(wm8994->regmap, true);
|
||||
regcache_mark_dirty(wm8994->regmap);
|
||||
|
||||
/* Restore GPIO registers to prevent problems with mismatched
|
||||
* pin configurations.
|
||||
*/
|
||||
ret = regcache_sync_region(wm8994->regmap, WM8994_GPIO_1,
|
||||
WM8994_GPIO_11);
|
||||
if (ret != 0)
|
||||
dev_err(dev, "Failed to restore GPIO registers: %d\n", ret);
|
||||
|
||||
/* In case one of the GPIOs is used as a wake input. */
|
||||
ret = regcache_sync_region(wm8994->regmap,
|
||||
WM8994_INTERRUPT_STATUS_1_MASK,
|
||||
WM8994_INTERRUPT_STATUS_1_MASK);
|
||||
if (ret != 0)
|
||||
dev_err(dev, "Failed to restore interrupt mask: %d\n", ret);
|
||||
|
||||
regcache_cache_only(wm8994->regmap, true);
|
||||
wm8994->suspended = true;
|
||||
|
||||
ret = regulator_bulk_disable(wm8994->num_supplies,
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@
|
|||
#include <linux/regmap.h>
|
||||
|
||||
#include <linux/mfd/wm8994/core.h>
|
||||
#include <linux/mfd/wm8994/pdata.h>
|
||||
#include <linux/mfd/wm8994/registers.h>
|
||||
|
||||
#include <linux/delay.h>
|
||||
|
|
@ -139,6 +140,8 @@ static struct regmap_irq_chip wm8994_irq_chip = {
|
|||
int wm8994_irq_init(struct wm8994 *wm8994)
|
||||
{
|
||||
int ret;
|
||||
unsigned long irqflags;
|
||||
struct wm8994_pdata *pdata = wm8994->dev->platform_data;
|
||||
|
||||
if (!wm8994->irq) {
|
||||
dev_warn(wm8994->dev,
|
||||
|
|
@ -147,8 +150,13 @@ int wm8994_irq_init(struct wm8994 *wm8994)
|
|||
return 0;
|
||||
}
|
||||
|
||||
/* select user or default irq flags */
|
||||
irqflags = IRQF_TRIGGER_HIGH | IRQF_ONESHOT;
|
||||
if (pdata->irq_flags)
|
||||
irqflags = pdata->irq_flags;
|
||||
|
||||
ret = regmap_add_irq_chip(wm8994->regmap, wm8994->irq,
|
||||
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
|
||||
irqflags,
|
||||
wm8994->irq_base, &wm8994_irq_chip,
|
||||
&wm8994->irq_data);
|
||||
if (ret != 0) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue