/* * tlv320aic325x-core.c -- driver for TLV320AIC3XXX * * Author: Mukund Navada * Mehar Bajwa * * 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. * * 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., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * set_aic325x_book: change book which we have to write/read to. * * @aic325x: Device to write/read to. * @book: Book to write/read to. */ int set_aic325x_book(struct aic325x *aic325x, int book) { int ret = 0; u8 page_buf[] = { 0x0, 0x0 }; u8 book_buf[] = { 0x7f, 0x0 }; ret = regmap_write(aic325x->regmap, page_buf[0], page_buf[1]); if (ret < 0) return ret; book_buf[1] = book; ret = regmap_write(aic325x->regmap, book_buf[0], book_buf[1]); if (ret < 0) return ret; aic325x->book_no = book; aic325x->page_no = 0; return ret; } /** * set_aic325x_page: change page which we have to write/read to. * * @aic325x: Device to write/read to. * @page: Book to write/read to. */ int set_aic325x_page(struct aic325x *aic325x, int page) { int ret = 0; u8 page_buf[] = { 0x0, 0x0 }; page_buf[1] = page; ret = regmap_write(aic325x->regmap, page_buf[0], page_buf[1]); if (ret < 0) return ret; aic325x->page_no = page; return ret; } /** * aic325x_reg_read: Read a single TLV320AIC3xxx register. * * @aic325x: Device to read from. * @reg: Register to read. */ int aic325x_reg_read(struct aic325x *aic325x, unsigned int reg) { unsigned int val; int ret; union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) ® u8 book, page, offset; page = aic_reg->aic325x_register.page; book = aic_reg->aic325x_register.book; offset = aic_reg->aic325x_register.offset; mutex_lock(&aic325x->io_lock); if (aic325x->book_no != book) { ret = set_aic325x_book(aic325x, book); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } if (aic325x->page_no != page) { ret = set_aic325x_page(aic325x, page); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } ret = regmap_read(aic325x->regmap, offset, &val); mutex_unlock(&aic325x->io_lock); if (ret < 0) return ret; else return val; } EXPORT_SYMBOL_GPL(aic325x_reg_read); /** * aic325x_bulk_read: Read multiple TLV320AIC3XXX registers * * @aic325x: Device to read from * @reg: First register * @count: Number of registers * @buf: Buffer to fill. The data will be returned big endian. */ int aic325x_bulk_read(struct aic325x *aic325x, unsigned int reg, int count, u8 *buf) { int ret; union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) ® u8 book, page, offset; page = aic_reg->aic325x_register.page; book = aic_reg->aic325x_register.book; offset = aic_reg->aic325x_register.offset; mutex_lock(&aic325x->io_lock); if (aic325x->book_no != book) { ret = set_aic325x_book(aic325x, book); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } if (aic325x->page_no != page) { ret = set_aic325x_page(aic325x, page); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } ret = regmap_bulk_read(aic325x->regmap, offset, buf, count); mutex_unlock(&aic325x->io_lock); return ret; } EXPORT_SYMBOL_GPL(aic325x_bulk_read); /** * aic325x_reg_write: Write a single TLV320AIC3XXX register. * * @aic325x: Device to write to. * @reg: Register to write to. * @val: Value to write. */ int aic325x_reg_write(struct aic325x *aic325x, unsigned int reg, unsigned char val) { union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) ® int ret = 0; u8 page, book, offset; page = aic_reg->aic325x_register.page; book = aic_reg->aic325x_register.book; offset = aic_reg->aic325x_register.offset; mutex_lock(&aic325x->io_lock); if (book != aic325x->book_no) { ret = set_aic325x_book(aic325x, book); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } if (page != aic325x->page_no) { ret = set_aic325x_page(aic325x, page); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } ret = regmap_write(aic325x->regmap, offset, val); mutex_unlock(&aic325x->io_lock); return ret; } EXPORT_SYMBOL_GPL(aic325x_reg_write); /** * aic325x_bulk_write: Write multiple TLV320AIC3XXX registers * * @aic325x: Device to write to * @reg: First register * @count: Number of registers * @buf: Buffer to write from. Data must be big-endian formatted. */ int aic325x_bulk_write(struct aic325x *aic325x, unsigned int reg, int count, const u8 *buf) { union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) ® int ret = 0; u8 page, book, offset; page = aic_reg->aic325x_register.page; book = aic_reg->aic325x_register.book; offset = aic_reg->aic325x_register.offset; mutex_lock(&aic325x->io_lock); if (book != aic325x->book_no) { ret = set_aic325x_book(aic325x, book); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } if (page != aic325x->page_no) { ret = set_aic325x_page(aic325x, page); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } ret = regmap_raw_write(aic325x->regmap, offset, buf, count); mutex_unlock(&aic325x->io_lock); return ret; } EXPORT_SYMBOL_GPL(aic325x_bulk_write); /** * aic325x_set_bits: Set the value of a bitfield in a TLV320AIC3XXX register * * @aic325x: Device to write to. * @reg: Register to write to. * @mask: Mask of bits to set. * @val: Value to set (unshifted) */ int aic325x_set_bits(struct aic325x *aic325x, unsigned int reg, unsigned char mask, unsigned char val) { union aic325x_reg_union *aic_reg = (union aic325x_reg_union *) ® int ret = 0; u8 page, book, offset; page = aic_reg->aic325x_register.page; book = aic_reg->aic325x_register.book; offset = aic_reg->aic325x_register.offset; mutex_lock(&aic325x->io_lock); if (book != aic325x->book_no) { ret = set_aic325x_book(aic325x, book); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } if (page != aic325x->page_no) { ret = set_aic325x_page(aic325x, page); if (ret < 0) { mutex_unlock(&aic325x->io_lock); return ret; } } ret = regmap_update_bits(aic325x->regmap, offset, mask, val); mutex_unlock(&aic325x->io_lock); return ret; } EXPORT_SYMBOL_GPL(aic325x_set_bits); /** * aic325x_wait_bits: wait for a value of a bitfield in a TLV320AIC3XXX register * * @aic325x: Device to write to. * @reg: Register to write to. * @mask: Mask of bits to set. * @val: Value to set (unshifted) * @sleep: delay value in each iteration in micro seconds * @count: iteration count for timeout */ int aic325x_wait_bits(struct aic325x *aic325x, unsigned int reg, unsigned char mask, unsigned char val, int sleep, int counter) { int status; int timeout = sleep * counter; status = aic325x_reg_read(aic325x, reg); while (((status & mask) != val) && counter) { usleep_range(sleep, sleep + 500); status = aic325x_reg_read(aic325x, reg); counter--; }; if (!counter) dev_err(aic325x->dev, "wait_bits timedout (%d millisecs). lastval 0x%x\n", timeout, status); return counter; } EXPORT_SYMBOL_GPL(aic325x_wait_bits); static struct mfd_cell aic3262_devs[] = { { .name = "tlv320aic3262-codec", }, { .name = "tlv320aic3262-gpio", }, }; static struct mfd_cell aic3256_devs[] = { { .name = "tlv320aic325x-codec", }, { .name = "tlv320aic3256-gpio", }, }; /** * Instantiate the generic non-control parts of the device. */ int aic325x_device_init(struct aic325x *aic325x) { const char *devname; int ret, i; u8 reset = 1; dev_info(aic325x->dev, "aic325x_device_init beginning\n"); mutex_init(&aic325x->io_lock); dev_set_drvdata(aic325x->dev, aic325x); if (dev_get_platdata(aic325x->dev)) memcpy(&aic325x->pdata, dev_get_platdata(aic325x->dev), sizeof(aic325x->pdata)); /* GPIO reset for TLV320AIC3xxx codec */ if (aic325x->pdata.gpio_reset) { ret = gpio_request_one(aic325x->pdata.gpio_reset, GPIOF_DIR_OUT | GPIOF_INIT_LOW, "aic325x-reset-pin"); if (ret != 0) { dev_err(aic325x->dev, "not able to acquire gpio\n"); goto err_return; } } /* run the codec through software reset */ ret = aic325x_reg_write(aic325x, AIC3XXX_RESET, reset); if (ret < 0) { dev_err(aic325x->dev, "Could not write to AIC3XXX register\n"); goto err_return; } usleep_range(10000, 10500); ret = aic325x_reg_read(aic325x, AIC3XXX_DEVICE_ID); if (ret < 0) { dev_err(aic325x->dev, "Failed to read ID register\n"); goto err_return; } devname = "TLV320AIC3256"; aic325x->type = TLV320AIC3256; /*If naudint is gpio convert it to irq number */ if (aic325x->pdata.gpio_irq == 1) { aic325x->irq = gpio_to_irq(aic325x->pdata.naudint_irq); gpio_request(aic325x->pdata.naudint_irq, "aic325x-gpio-irq"); gpio_direction_input(aic325x->pdata.naudint_irq); } else { aic325x->irq = aic325x->pdata.naudint_irq; } for (i = 0; i < aic325x->pdata.num_gpios; i++) { aic325x_reg_write(aic325x, aic325x->pdata.gpio_defaults[i].reg, aic325x->pdata.gpio_defaults[i].value); } if (aic325x->irq) { ret = aic325x_irq_init(aic325x); if (ret < 0) goto err_irq; } dev_info(aic325x->dev, "%s revision %c\n", devname, 'D' + ret); switch (aic325x->type) { case TLV320AIC3266: case TLV320AIC3262: ret = mfd_add_devices(aic325x->dev, -1, aic3262_devs, ARRAY_SIZE(aic3262_devs), NULL, 0, NULL); break; case TLV320AIC3256: ret = mfd_add_devices(aic325x->dev, -1, aic3256_devs, ARRAY_SIZE(aic3256_devs), NULL, 0, NULL); break; default: dev_err(aic325x->dev, "unable to recognize codec\n"); break; } if (ret != 0) { dev_err(aic325x->dev, "Failed to add children: %d\n", ret); goto err_mfd; } dev_info(aic325x->dev, "aic325x_device_init added mfd devices\n"); return 0; err_mfd: aic325x_irq_exit(aic325x); err_irq: if (aic325x->pdata.gpio_irq) gpio_free(aic325x->pdata.naudint_irq); err_return: if (aic325x->pdata.gpio_reset) gpio_free(aic325x->pdata.gpio_reset); return ret; } EXPORT_SYMBOL_GPL(aic325x_device_init); void aic325x_device_exit(struct aic325x *aic325x) { mfd_remove_devices(aic325x->dev); aic325x_irq_exit(aic325x); if (aic325x->pdata.gpio_irq) gpio_free(aic325x->pdata.naudint_irq); if (aic325x->pdata.gpio_reset) gpio_free(aic325x->pdata.gpio_reset); } EXPORT_SYMBOL_GPL(aic325x_device_exit); MODULE_AUTHOR("Mukund Navada "); MODULE_AUTHOR("Mehar Bajwa "); MODULE_DESCRIPTION("Core support for the TLV320AIC3XXX audio CODEC"); MODULE_LICENSE("GPL");