/* * as3720-core.c - core driver for AS3720 PMICs * * Copyright (C) 2012 ams AG * * Author: Bernhard Breinbauer * * 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 * */ #include #include #include #include #include #include #include #include #include #include #include #include #define AS3720_DRIVER_VERSION "v0.0.6" #define NUM_INT_REG 3 enum as3720_ids { AS3720_GPIO_ID, AS3720_REGULATOR_ID, AS3720_RTC_ID, }; static const struct regmap_config as3720_regmap_config = { .reg_bits = 8, .val_bits = 8, .max_register = AS3720_REGISTER_COUNT, .num_reg_defaults_raw = AS3720_REGISTER_COUNT, }; static const struct resource as3720_rtc_resource[] = { { .name = "RTC_ALARM", .start = AS3720_IRQ_RTC_ALARM, .end = AS3720_IRQ_RTC_ALARM, .flags = IORESOURCE_IRQ, }, }; static struct mfd_cell as3720_devs[] = { { .name = "as3720-gpio", .id = AS3720_GPIO_ID, }, { .name = "as3720-regulator", .id = AS3720_REGULATOR_ID, }, { .name = "as3720-rtc", .num_resources = ARRAY_SIZE(as3720_rtc_resource), .resources = as3720_rtc_resource, .id = AS3720_RTC_ID, }, }; static const struct regmap_irq as3720_irqs[] = { /* INT1 IRQs */ [AS3720_IRQ_LID] = { .mask = AS3720_IRQ_MASK_LID, }, [AS3720_IRQ_ACOK] = { .mask = AS3720_IRQ_MASK_ACOK, }, [AS3720_IRQ_CORE_PWRREQ] = { .mask = AS3720_IRQ_MASK_CORE_PWRREQ, }, [AS3720_IRQ_SD0] = { .mask = AS3720_IRQ_MASK_SD0, }, [AS3720_IRQ_ONKEY_LONG] = { .mask = AS3720_IRQ_MASK_ONKEY_LONG, }, [AS3720_IRQ_ONKEY] = { .mask = AS3720_IRQ_MASK_ONKEY, }, [AS3720_IRQ_OVTMP] = { .mask = AS3720_IRQ_MASK_OVTMP, }, [AS3720_IRQ_LOWBAT] = { .mask = AS3720_IRQ_MASK_LOWBAT, }, [AS3720_IRQ_RTC_REP] = { .mask = AS3720_IRQ_MASK_RTC_REP, .reg_offset = 1, }, [AS3720_IRQ_RTC_ALARM] = { .mask = AS3720_IRQ_MASK_RTC_ALARM, .reg_offset = 2, }, }; static struct regmap_irq_chip as3720_irq_chip = { .name = "as3720", .irqs = as3720_irqs, .num_irqs = ARRAY_SIZE(as3720_irqs), .num_regs = 3, .status_base = AS3720_INTERRUPTSTATUS1_REG, .mask_base = AS3720_INTERRUPTMASK1_REG, .wake_base = 1, }; static void as3720_reg_init(struct as3720 *as3720, struct as3720_reg_init *reg_data) { int ret; while (reg_data->reg != AS3720_REG_INIT_TERMINATE) { ret = as3720_reg_write(as3720, reg_data->reg, reg_data->val); if (ret) { dev_err(as3720->dev, "reg setup failed: %d\n", ret); return; } reg_data++; } } static int as3720_init(struct as3720 *as3720, struct as3720_platform_data *pdata, int irq) { u32 reg; int ret; /* Check that this is actually a AS3720 */ ret = regmap_read(as3720->regmap, AS3720_ADDR_ASIC_ID1, ®); if (ret != 0) { dev_err(as3720->dev, "Chip ID register read failed\n"); return -EIO; } if (reg != AS3720_DEVICE_ID) { dev_err(as3720->dev, "Device is not an AS3720, ID is 0x%x\n" , reg); return -ENODEV; } ret = regmap_read(as3720->regmap, AS3720_ADDR_ASIC_ID2, ®); if (ret < 0) { dev_err(as3720->dev, "ID2 register read failed: %d\n", ret); return ret; } dev_info(as3720->dev, "AS3720 with revision %x found\n", reg); /* do some initial platform register setup */ if (pdata->core_init_data) as3720_reg_init(as3720, pdata->core_init_data); return 0; } static int as3720_i2c_probe(struct i2c_client *i2c, const struct i2c_device_id *id) { struct as3720 *as3720; struct as3720_platform_data *pdata; int irq_flag; int ret; pdata = dev_get_platdata(&i2c->dev); if (!pdata) { dev_err(&i2c->dev, "as3720 requires platform data\n"); return -EINVAL; } as3720 = devm_kzalloc(&i2c->dev, sizeof(struct as3720), GFP_KERNEL); if (as3720 == NULL) { dev_err(&i2c->dev, "mem alloc for as3720 failed\n"); return -ENOMEM; } as3720->dev = &i2c->dev; as3720->chip_irq = i2c->irq; i2c_set_clientdata(i2c, as3720); as3720->regmap = devm_regmap_init_i2c(i2c, &as3720_regmap_config); if (IS_ERR(as3720->regmap)) { ret = PTR_ERR(as3720->regmap); dev_err(&i2c->dev, "regmap_init failed with err: %d\n", ret); return ret; } irq_flag = pdata->irq_type; irq_flag = IRQF_ONESHOT; ret = regmap_add_irq_chip(as3720->regmap, as3720->chip_irq, irq_flag, pdata->irq_base, &as3720_irq_chip, &as3720->irq_data); if (ret < 0) { dev_err(as3720->dev, "irq allocation failed for as3720 failed\n"); return ret; } ret = as3720_init(as3720, pdata, i2c->irq); if (ret < 0) return ret; ret = mfd_add_devices(&i2c->dev, -1, as3720_devs, ARRAY_SIZE(as3720_devs), NULL, pdata->irq_base, NULL); if (ret) { dev_err(as3720->dev, "add mfd devices failed with err: %d\n", ret); return ret; } dev_info(as3720->dev, "AS3720 core driver %s initialized successfully\n", AS3720_DRIVER_VERSION); return 0; } static int as3720_i2c_remove(struct i2c_client *i2c) { struct as3720 *as3720 = i2c_get_clientdata(i2c); mfd_remove_devices(as3720->dev); regmap_del_irq_chip(as3720->chip_irq, as3720->regmap); return 0; } static const struct i2c_device_id as3720_i2c_id[] = { {"as3720", 0}, {} }; MODULE_DEVICE_TABLE(i2c, as3720_i2c_id); static struct i2c_driver as3720_i2c_driver = { .driver = { .name = "as3720", .owner = THIS_MODULE, }, .probe = as3720_i2c_probe, .remove = as3720_i2c_remove, .id_table = as3720_i2c_id, }; static int __init as3720_i2c_init(void) { return i2c_add_driver(&as3720_i2c_driver); } subsys_initcall(as3720_i2c_init); static void __exit as3720_i2c_exit(void) { i2c_del_driver(&as3720_i2c_driver); } module_exit(as3720_i2c_exit); MODULE_DESCRIPTION("I2C support for AS3720 PMICs"); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Bernhard Breinbauer ");