| // SPDX-License-Identifier: GPL-2.0+ | 
 | // Copyright 2017-2020 NXP | 
 |  | 
 | #include <linux/module.h> | 
 | #include <linux/rpmsg.h> | 
 | #include "imx-pcm-rpmsg.h" | 
 |  | 
 | /* | 
 |  * struct imx_audio_rpmsg: private data | 
 |  * | 
 |  * @rpmsg_pdev: pointer of platform device | 
 |  */ | 
 | struct imx_audio_rpmsg { | 
 | 	struct platform_device *rpmsg_pdev; | 
 | 	struct platform_device *card_pdev; | 
 | }; | 
 |  | 
 | static int imx_audio_rpmsg_cb(struct rpmsg_device *rpdev, void *data, int len, | 
 | 			      void *priv, u32 src) | 
 | { | 
 | 	struct imx_audio_rpmsg *rpmsg = dev_get_drvdata(&rpdev->dev); | 
 | 	struct rpmsg_r_msg *r_msg = (struct rpmsg_r_msg *)data; | 
 | 	struct rpmsg_info *info; | 
 | 	struct rpmsg_msg *msg; | 
 | 	unsigned long flags; | 
 |  | 
 | 	if (!rpmsg->rpmsg_pdev) | 
 | 		return 0; | 
 |  | 
 | 	info = platform_get_drvdata(rpmsg->rpmsg_pdev); | 
 |  | 
 | 	dev_dbg(&rpdev->dev, "get from%d: cmd:%d. %d\n", | 
 | 		src, r_msg->header.cmd, r_msg->param.resp); | 
 |  | 
 | 	switch (r_msg->header.type) { | 
 | 	case MSG_TYPE_C: | 
 | 		/* TYPE C is notification from M core */ | 
 | 		switch (r_msg->header.cmd) { | 
 | 		case TX_PERIOD_DONE: | 
 | 			spin_lock_irqsave(&info->lock[TX], flags); | 
 | 			msg = &info->msg[TX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
 | 			msg->r_msg.param.buffer_tail = | 
 | 						r_msg->param.buffer_tail; | 
 | 			msg->r_msg.param.buffer_tail %= info->num_period[TX]; | 
 | 			spin_unlock_irqrestore(&info->lock[TX], flags); | 
 | 			info->callback[TX](info->callback_param[TX]); | 
 | 			break; | 
 | 		case RX_PERIOD_DONE: | 
 | 			spin_lock_irqsave(&info->lock[RX], flags); | 
 | 			msg = &info->msg[RX_PERIOD_DONE + MSG_TYPE_A_NUM]; | 
 | 			msg->r_msg.param.buffer_tail = | 
 | 						r_msg->param.buffer_tail; | 
 | 			msg->r_msg.param.buffer_tail %= info->num_period[1]; | 
 | 			spin_unlock_irqrestore(&info->lock[RX], flags); | 
 | 			info->callback[RX](info->callback_param[RX]); | 
 | 			break; | 
 | 		default: | 
 | 			dev_warn(&rpdev->dev, "unknown msg command\n"); | 
 | 			break; | 
 | 		} | 
 | 		break; | 
 | 	case MSG_TYPE_B: | 
 | 		/* TYPE B is response msg */ | 
 | 		memcpy(&info->r_msg, r_msg, sizeof(struct rpmsg_r_msg)); | 
 | 		complete(&info->cmd_complete); | 
 | 		break; | 
 | 	default: | 
 | 		dev_warn(&rpdev->dev, "unknown msg type\n"); | 
 | 		break; | 
 | 	} | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int imx_audio_rpmsg_probe(struct rpmsg_device *rpdev) | 
 | { | 
 | 	struct imx_audio_rpmsg *data; | 
 | 	int ret = 0; | 
 |  | 
 | 	dev_info(&rpdev->dev, "new channel: 0x%x -> 0x%x!\n", | 
 | 		 rpdev->src, rpdev->dst); | 
 |  | 
 | 	data = devm_kzalloc(&rpdev->dev, sizeof(*data), GFP_KERNEL); | 
 | 	if (!data) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	dev_set_drvdata(&rpdev->dev, data); | 
 |  | 
 | 	/* Register platform driver for rpmsg routine */ | 
 | 	data->rpmsg_pdev = platform_device_register_data(&rpdev->dev, | 
 | 							 rpdev->id.name, | 
 | 							 PLATFORM_DEVID_NONE, | 
 | 							 NULL, 0); | 
 | 	if (IS_ERR(data->rpmsg_pdev)) { | 
 | 		dev_err(&rpdev->dev, "failed to register rpmsg platform.\n"); | 
 | 		ret = PTR_ERR(data->rpmsg_pdev); | 
 | 	} | 
 |  | 
 | 	data->card_pdev = platform_device_register_data(&rpdev->dev, | 
 | 							"imx-audio-rpmsg", | 
 | 							PLATFORM_DEVID_AUTO, | 
 | 							rpdev->id.name, | 
 | 							strlen(rpdev->id.name) + 1); | 
 | 	if (IS_ERR(data->card_pdev)) { | 
 | 		dev_err(&rpdev->dev, "failed to register rpmsg card.\n"); | 
 | 		ret = PTR_ERR(data->card_pdev); | 
 | 	} | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void imx_audio_rpmsg_remove(struct rpmsg_device *rpdev) | 
 | { | 
 | 	struct imx_audio_rpmsg *data = dev_get_drvdata(&rpdev->dev); | 
 |  | 
 | 	if (data->rpmsg_pdev) | 
 | 		platform_device_unregister(data->rpmsg_pdev); | 
 |  | 
 | 	if (data->card_pdev) | 
 | 		platform_device_unregister(data->card_pdev); | 
 |  | 
 | 	dev_info(&rpdev->dev, "audio rpmsg driver is removed\n"); | 
 | } | 
 |  | 
 | static struct rpmsg_device_id imx_audio_rpmsg_id_table[] = { | 
 | 	{ .name	= "rpmsg-audio-channel" }, | 
 | 	{ .name = "rpmsg-micfil-channel" }, | 
 | 	{ }, | 
 | }; | 
 | MODULE_DEVICE_TABLE(rpmsg, imx_audio_rpmsg_id_table); | 
 |  | 
 | static struct rpmsg_driver imx_audio_rpmsg_driver = { | 
 | 	.drv.name	= "imx_audio_rpmsg", | 
 | 	.id_table	= imx_audio_rpmsg_id_table, | 
 | 	.probe		= imx_audio_rpmsg_probe, | 
 | 	.callback	= imx_audio_rpmsg_cb, | 
 | 	.remove		= imx_audio_rpmsg_remove, | 
 | }; | 
 |  | 
 | module_rpmsg_driver(imx_audio_rpmsg_driver); | 
 |  | 
 | MODULE_DESCRIPTION("Freescale SoC Audio RPMSG interface"); | 
 | MODULE_AUTHOR("Shengjiu Wang <shengjiu.wang@nxp.com>"); | 
 | MODULE_ALIAS("rpmsg:imx_audio_rpmsg"); | 
 | MODULE_LICENSE("GPL v2"); |