blob: 52dd45ed1af30ae587d8124857823780b7d9b99b [file] [log] [blame]
/**
* @file aai_module.c
* @brief P7 AAI kernel module layer
*
* @author adrien.charruel@parrot.com
* @date 2011-05-17
*
* 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.
*
* Parrot Advanced Audio Interface Driver
*
*/
#include "aai.h"
#include "aai_alsa.h"
#include "aai_hw.h"
static int aai_start(struct card_data_t *aai_data, struct platform_device *pdev)
{
int err;
/* prepare controller clock */
err = clk_prepare_enable(aai_data->clk[ctrl_clk]);
if (err) {
dev_err(&pdev->dev, "failed to enable ctrl clock.\n");
return err;
}
/* AAI hardware init */
err = aai_hw_init(aai_data);
if (err) {
dev_err(&pdev->dev, "AAI hardware init failed\n");
return -EINVAL;
}
/* Register devices */
err = aai_pcms_init(aai_data);
if (err) {
dev_err(&pdev->dev, "aai pcms Init failed\n");
return err;
}
return 0;
}
static int __devinit aai_module_probe(struct platform_device *pdev)
{
int err;
struct resource *res;
struct snd_card *card;
struct card_data_t *aai_data;
struct clk *clk;
/* Map registers */
res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!res) {
dev_err(&pdev->dev, "Can't get IORESOURCE_MEM\n");
err = -ENOENT;
goto no_iores;
}
/* Init driver structures */
card = aai_alsa_probe(pdev);
if (!card) {
dev_err(&pdev->dev, "Can't probe AAI alsa card\n");
err = -ENXIO;
goto no_iores;
}
aai_data = card->private_data;
platform_set_drvdata(pdev, card);
aai_data->pdata = dev_get_platdata(&pdev->dev);
if (!aai_data->pdata->pad) {
dev_err(&pdev->dev, "no pad configuration\n");
err = -EINVAL;
goto no_iores;
}
if (!aai_data->pdata->device_list) {
dev_err(&pdev->dev, "no device list defined\n");
err = -EINVAL;
goto no_iores;
}
/* Init IO */
aai_data->ioarea = request_mem_region(res->start,
res->end - res->start + 1,
pdev->name);
if (!aai_data->ioarea) {
dev_err(&pdev->dev, "Can't reserve IO region\n");
err = -ENXIO;
goto no_iores;
}
aai_data->iobase = ioremap(res->start, res->end - res->start + 1);
if (!aai_data->iobase) {
dev_err(&pdev->dev, "Can't map IO\n");
err = -ENXIO;
goto no_iomap;
}
/* Get controller clock */
clk = clk_get(&pdev->dev, "ctrl_clk");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get aai ctrl clk\n");
err = -EINVAL;
goto no_ctrlclk;
}
aai_data->clk[ctrl_clk] = clk;
clk = clk_get(&pdev->dev, "i2s_clk");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get aai i2s clk\n");
err = -EINVAL;
goto no_i2sclk;
}
aai_data->clk[i2s_clk] = clk;
clk = clk_get(&pdev->dev, "pcm1_clk");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get aai pcm1 clk\n");
err = -EINVAL;
goto no_pcm1clk;
}
aai_data->clk[pcm1_clk] = clk;
clk = clk_get(&pdev->dev, "pcm2_clk");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get aai pcm2 clk\n");
err = -EINVAL;
goto no_pcm2clk;
}
aai_data->clk[pcm2_clk] = clk;
clk = clk_get(&pdev->dev, "spdif_clk");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "unable to get aai spdif clk\n");
err = -EINVAL;
goto no_spdifclk;
}
aai_data->clk[spdif_clk] = clk;
/*
* Get IRQ ressource
*/
aai_data->irq = platform_get_irq(pdev, 0);
if (aai_data->irq < 0) {
dev_err(&pdev->dev, "Can't retrieve IRQ number\n");
err = -ENOENT;
goto no_irq;
}
/*
* reset registers, set right default value
* enable main clk
* set irq
* The aai_start function is also used when
* resume after suspend
*/
err = aai_start(aai_data, pdev);
if (err)
goto no_clk_enable;
/*
* Install IRQ handler
*/
err = request_irq(aai_data->irq, aai_data->irq_handler,
IRQF_DISABLED, pdev->name, aai_data);
if (err) {
dev_err(&pdev->dev, "failed attaching IRQ %d\n", aai_data->irq);
return err;
}
aai_data->pin = pinctrl_get_select_default(&pdev->dev);
if (IS_ERR(aai_data->pin)) {
dev_err(&pdev->dev, "Can't get pin ctrl\n");
goto no_irq;
}
dev_info(&pdev->dev, "P7 AAI module initialized\n");
return 0;
no_irq:
clk_disable_unprepare(aai_data->clk[ctrl_clk]);
no_clk_enable:
if (err)
clk_put(aai_data->clk[spdif_clk]);
no_spdifclk:
if (err)
clk_put(aai_data->clk[pcm2_clk]);
no_pcm2clk:
if (err)
clk_put(aai_data->clk[pcm1_clk]);
no_pcm1clk:
if (err)
clk_put(aai_data->clk[i2s_clk]);
no_i2sclk:
if (err)
clk_put(aai_data->clk[ctrl_clk]);
no_ctrlclk:
if (err)
iounmap(aai_data->iobase);
no_iomap:
if (err) {
release_resource(aai_data->ioarea);
kfree(aai_data->ioarea);
}
no_iores:
if (err)
platform_set_drvdata(pdev, NULL);
return err;
}
static int __devexit aai_module_remove(struct platform_device *pdev)
{
int err;
struct snd_card *card = platform_get_drvdata(pdev);
struct card_data_t *aai_data = card->private_data;
/* reset hardware layer */
err = aai_hw_remove(aai_data);
if (err) {
dev_err(&pdev->dev, "AAI hardware remove failed\n");
err = -EINVAL;
goto exit;
}
free_irq(aai_data->irq, aai_data);
/*
* beware, ths call will free drv_data which is included
* into card structure.
*/
aai_alsa_remove(card);
iounmap(aai_data->iobase);
release_resource(aai_data->ioarea);
kfree(aai_data->ioarea);
platform_set_drvdata(pdev, NULL);
pinctrl_put(aai_data->pin);
exit:
return err;
}
static int aai_module_suspend(struct platform_device *pdev, pm_message_t msg)
{
struct snd_card *card = platform_get_drvdata(pdev);
struct card_data_t *aai = card->private_data;
struct aai_device_t *chan;
struct aai_hw_channel_t *fifo;
struct aai_audio_chan_t *internal_chan;
int ipcm, i;
for (ipcm = 0; ipcm < aai->chans_nb; ipcm++) {
chan = &aai->chans[ipcm];
fifo = &chan->fifo;
for (i = 0; i < fifo->chan_nb; i++) {
internal_chan = fifo->chan_list[i];
internal_chan->used = 0;
}
snd_pcm_suspend_all(aai->pcms[ipcm]);
}
free_irq(aai->irq, aai);
clk_disable(aai->clk[i2s_clk]);
clk_disable(aai->clk[ctrl_clk]);
snd_power_change_state(card, SNDRV_CTL_POWER_D3cold);
return 0;
}
static int aai_module_resume(struct platform_device *pdev)
{
struct snd_card *card = platform_get_drvdata(pdev);
struct card_data_t *aai = card->private_data;
int err;
snd_power_change_state(card, SNDRV_CTL_POWER_D0);
err = aai_start(aai, pdev);
if (err) {
dev_err(&pdev->dev, "failed to resume %d\n", err);
return err;
}
/* Install IRQ handler */
err = request_irq(aai->irq, aai->irq_handler,
IRQF_DISABLED, pdev->name, aai);
if (err) {
dev_err(&pdev->dev, "failed attaching IRQ %d\n", aai->irq);
return err;
}
return 0;
}
static struct platform_driver aai_module_driver = {
.probe = aai_module_probe,
.remove = aai_module_remove,
.suspend = aai_module_suspend,
.resume = aai_module_resume,
.driver = {
.name = "aai",
.owner = THIS_MODULE,
},
};
static int __init aai_module_init(void)
{
return platform_driver_register(&aai_module_driver);
}
static void __exit aai_module_exit(void)
{
platform_driver_unregister(&aai_module_driver);
}
module_init(aai_module_init);
module_exit(aai_module_exit);
MODULE_DESCRIPTION("Parrot7 Advanced Audio Interface driver");
MODULE_AUTHOR("Adrien Charruel, <adrien.charruel@parrot.com>");
MODULE_LICENSE("GPL");
MODULE_SUPPORTED_DEVICE("{{Parrot, P7AAI}}");