// SPDX-License-Identifier: GPL-2.0-only
/*
 * Copyright (c) 2016-2021, The Linux Foundation. All rights reserved.
 */

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/gpio.h>
#include <linux/debugfs.h>
#include <linux/ratelimit.h>
#include <linux/slab.h>
#include <sound/pcm.h>
#include <sound/pcm_params.h>
#include <sound/soc.h>
#include <sound/soc-dapm.h>
#include <sound/tlv.h>
#include "btfm_slim.h"
#include "btfm_slim_slave.h"
#include <linux/bluetooth-power.h>

#define DELAY_FOR_PORT_OPEN_MS (200)

bool btfm_is_port_opening_delayed = true;

int btfm_slim_write(struct btfmslim *btfmslim,
		uint16_t reg, int bytes, void *src, uint8_t pgd)
{
	int ret, i;
	struct slim_ele_access msg;
	int slim_write_tries = SLIM_SLAVE_RW_MAX_TRIES;

	BTFMSLIM_DBG("Write to %s", pgd?"PGD":"IFD");
	msg.start_offset = SLIM_SLAVE_REG_OFFSET + reg;
	msg.num_bytes = bytes;
	msg.comp = NULL;

	for ( ; slim_write_tries != 0; slim_write_tries--) {
		mutex_lock(&btfmslim->xfer_lock);
		ret = slim_change_val_element(pgd ? btfmslim->slim_pgd :
			&btfmslim->slim_ifd, &msg, src, bytes);
		mutex_unlock(&btfmslim->xfer_lock);
		if (ret == 0)
			break;
		usleep_range(5000, 5100);
	}

	if (ret) {
		BTFMSLIM_ERR("failed (%d)", ret);
		return ret;
	}

	for (i = 0; i < bytes; i++)
		BTFMSLIM_DBG("Write 0x%02x to reg 0x%x", ((uint8_t *)src)[i],
			reg + i);
	return 0;
}

int btfm_slim_write_pgd(struct btfmslim *btfmslim,
		uint16_t reg, int bytes, void *src)
{
	return btfm_slim_write(btfmslim, reg, bytes, src, PGD);
}

int btfm_slim_write_inf(struct btfmslim *btfmslim,
		uint16_t reg, int bytes, void *src)
{
	return btfm_slim_write(btfmslim, reg, bytes, src, IFD);
}

int btfm_slim_read(struct btfmslim *btfmslim, unsigned short reg,
				int bytes, void *dest, uint8_t pgd)
{
	int ret, i;
	struct slim_ele_access msg;
	int slim_read_tries = SLIM_SLAVE_RW_MAX_TRIES;

	BTFMSLIM_DBG("Read from %s", pgd?"PGD":"IFD");
	msg.start_offset = SLIM_SLAVE_REG_OFFSET + reg;
	msg.num_bytes = bytes;
	msg.comp = NULL;

	for ( ; slim_read_tries != 0; slim_read_tries--) {
		mutex_lock(&btfmslim->xfer_lock);
		ret = slim_request_val_element(pgd ? btfmslim->slim_pgd :
			&btfmslim->slim_ifd, &msg, dest, bytes);
		mutex_unlock(&btfmslim->xfer_lock);
		if (ret == 0)
			break;
		usleep_range(5000, 5100);
	}

	if (ret)
		BTFMSLIM_ERR("failed (%d)", ret);

	for (i = 0; i < bytes; i++)
		BTFMSLIM_DBG("Read 0x%02x from reg 0x%x", ((uint8_t *)dest)[i],
			reg + i);

	return 0;
}

int btfm_slim_read_pgd(struct btfmslim *btfmslim,
		uint16_t reg, int bytes, void *dest)
{
	return btfm_slim_read(btfmslim, reg, bytes, dest, PGD);
}

int btfm_slim_read_inf(struct btfmslim *btfmslim,
		uint16_t reg, int bytes, void *dest)
{
	return btfm_slim_read(btfmslim, reg, bytes, dest, IFD);
}

bool btfm_slim_is_sb_reset_needed(int chip_ver)
{
	switch (chip_ver) {
	case QCA_APACHE_SOC_ID_0100:
	case QCA_APACHE_SOC_ID_0110:
	case QCA_APACHE_SOC_ID_0120:
	case QCA_APACHE_SOC_ID_0121:
		return true;
	default:
		return false;
	}
}

int btfm_slim_enable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
	uint8_t rxport, uint32_t rates, uint8_t grp, uint8_t nchan)
{
	int ret, i;
	struct slim_ch prop;
	struct btfmslim_ch *chan = ch;
	uint16_t ch_h[2];
	int chipset_ver;

	if (!btfmslim || !ch)
		return -EINVAL;

	BTFMSLIM_DBG("port: %d ch: %d", ch->port, ch->ch);

	/* Define the channel with below parameters */
	prop.prot =  ((rates == 44100) || (rates == 88200)) ?
			SLIM_PUSH : SLIM_AUTO_ISO;
	prop.baser = ((rates == 44100) || (rates == 88200)) ?
			SLIM_RATE_11025HZ : SLIM_RATE_4000HZ;
	prop.dataf = SLIM_CH_DATAF_NOT_DEFINED;

	chipset_ver = get_chipset_version();
	BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);

	/* Delay port opening for few chipsets if:
	 *	1. for 8k, feedback channel
	 *	2. 44.1k, 88.2k rxports
	 */
	if (((rates == 8000 && btfm_feedback_ch_setting && rxport == 0) ||
		(rxport == 1 && (rates == 44100 || rates == 88200))) &&
		btfm_slim_is_sb_reset_needed(chipset_ver)) {

		BTFMSLIM_INFO("btfm_is_port_opening_delayed %d",
				btfm_is_port_opening_delayed);

		if (!btfm_is_port_opening_delayed) {
			BTFMSLIM_INFO("SB reset needed, sleeping");
			btfm_is_port_opening_delayed = true;
			msleep(DELAY_FOR_PORT_OPEN_MS);
		}
	}

	/* for feedback channel, PCM bit should not be set */
	if (btfm_feedback_ch_setting) {
		BTFMSLIM_DBG("port open for feedback ch, not setting PCM bit");
		prop.dataf = SLIM_CH_DATAF_NOT_DEFINED;
		/* reset so that next port open sets the data format properly */
		btfm_feedback_ch_setting = 0;
	}
	prop.auxf = SLIM_CH_AUXF_NOT_APPLICABLE;
	prop.ratem = ((rates == 44100) || (rates == 88200)) ?
		(rates/11025) : (rates/4000);
	prop.sampleszbits = 16;

	ch_h[0] = ch->ch_hdl;
	ch_h[1] = (grp) ? (ch+1)->ch_hdl : 0;

	BTFMSLIM_INFO("channel define - prot:%d, dataf:%d, auxf:%d",
			prop.prot, prop.dataf, prop.auxf);
	BTFMSLIM_INFO("channel define - rates:%d, baser:%d, ratem:%d",
			rates, prop.baser, prop.ratem);

	ret = slim_define_ch(btfmslim->slim_pgd, &prop, ch_h, nchan, grp,
			&ch->grph);
	if (ret < 0) {
		BTFMSLIM_ERR("slim_define_ch failed ret[%d]", ret);
		goto error;
	}

	for (i = 0; i < nchan; i++, ch++) {
		/* Enable port through registration setting */
		if (btfmslim->vendor_port_en) {
			ret = btfmslim->vendor_port_en(btfmslim, ch->port,
					rxport, 1);
			if (ret < 0) {
				BTFMSLIM_ERR("vendor_port_en failed ret[%d]",
					ret);
				goto error;
			}
		}

		if (rxport) {
			BTFMSLIM_INFO("slim_connect_sink(port: %d, ch: %d)",
				ch->port, ch->ch);
			/* Connect Port with channel given by Machine driver*/
			ret = slim_connect_sink(btfmslim->slim_pgd,
				&ch->port_hdl, 1, ch->ch_hdl);
			if (ret < 0) {
				BTFMSLIM_ERR("slim_connect_sink failed ret[%d]",
					ret);
				goto remove_channel;
			}

		} else {
			BTFMSLIM_INFO("slim_connect_src(port: %d, ch: %d)",
				ch->port, ch->ch);
			/* Connect Port with channel given by Machine driver*/
			ret = slim_connect_src(btfmslim->slim_pgd, ch->port_hdl,
				ch->ch_hdl);
			if (ret < 0) {
				BTFMSLIM_ERR("slim_connect_src failed ret[%d]",
					ret);
				goto remove_channel;
			}
		}
	}

	/* Activate the channel immediately */
	BTFMSLIM_INFO(
		"port: %d, ch: %d, grp: %d, ch->grph: 0x%x, ch_hdl: 0x%x",
		chan->port, chan->ch, grp, chan->grph, chan->ch_hdl);
	ret = slim_control_ch(btfmslim->slim_pgd, (grp ? chan->grph :
		chan->ch_hdl), SLIM_CH_ACTIVATE, true);
	if (ret < 0) {
		BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret);
		goto remove_channel;
	}

error:
	return ret;

remove_channel:
	/* Remove the channel immediately*/
	ret = slim_control_ch(btfmslim->slim_pgd, (grp ? ch->grph : ch->ch_hdl),
			SLIM_CH_REMOVE, true);
	if (ret < 0)
		BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret);

	return ret;
}

int btfm_slim_disable_ch(struct btfmslim *btfmslim, struct btfmslim_ch *ch,
	uint8_t rxport, uint8_t grp, uint8_t nchan)
{
	int ret, i;

	if (!btfmslim || !ch)
		return -EINVAL;

	BTFMSLIM_INFO("port:%d, grp: %d, ch->grph:0x%x, ch->ch_hdl:0x%x ",
		ch->port, grp, ch->grph, ch->ch_hdl);

	btfm_is_port_opening_delayed = false;

	/* For 44.1/88.2 Khz A2DP Rx, disconnect the port first */
	if (rxport &&
		(btfmslim->sample_rate == 44100 ||
		 btfmslim->sample_rate == 88200)) {
		BTFMSLIM_DBG("disconnecting the ports, removing the channel");
		ret = slim_disconnect_ports(btfmslim->slim_pgd,
				&ch->port_hdl, 1);
		if (ret < 0) {
			BTFMSLIM_ERR("slim_disconnect_ports failed ret[%d]",
				ret);
		}
	}

	/* Remove the channel immediately*/
	ret = slim_control_ch(btfmslim->slim_pgd, (grp ? ch->grph : ch->ch_hdl),
			SLIM_CH_REMOVE, true);
	if (ret < 0) {
		BTFMSLIM_ERR("slim_control_ch failed ret[%d]", ret);
		if (btfmslim->sample_rate != 44100 &&
			btfmslim->sample_rate != 88200) {
			ret = slim_disconnect_ports(btfmslim->slim_pgd,
				&ch->port_hdl, 1);
			if (ret < 0) {
				BTFMSLIM_ERR("disconnect_ports failed ret[%d]",
					 ret);
				goto error;
			}
		}
	}

	/* Disable port through registration setting */
	for (i = 0; i < nchan; i++, ch++) {
		if (btfmslim->vendor_port_en) {
			ret = btfmslim->vendor_port_en(btfmslim, ch->port,
				rxport, 0);
			if (ret < 0) {
				BTFMSLIM_ERR("vendor_port_en failed ret[%d]",
					ret);
				break;
			}
		}
	}
error:
	return ret;
}
static int btfm_slim_get_logical_addr(struct slim_device *slim)
{
	int ret = 0;
	const unsigned long timeout = jiffies +
			      msecs_to_jiffies(SLIM_SLAVE_PRESENT_TIMEOUT);

	do {
		ret = slim_get_logical_addr(slim, slim->e_addr,
			ARRAY_SIZE(slim->e_addr), &slim->laddr);
		if (!ret)  {
			BTFMSLIM_DBG("Assigned l-addr: 0x%x", slim->laddr);
			break;
		}
		/* Give SLIMBUS time to report present and be ready. */
		usleep_range(1000, 1100);
		BTFMSLIM_DBG("retyring get logical addr");
	} while (time_before(jiffies, timeout));

	return ret;
}

static int btfm_slim_alloc_port(struct btfmslim *btfmslim)
{
	int ret = -EINVAL, i;
	int  chipset_ver;
	struct btfmslim_ch *rx_chs;
	struct btfmslim_ch *tx_chs;

	if (!btfmslim)
		return ret;

	chipset_ver = get_chipset_version();
	BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);

	rx_chs = btfmslim->rx_chs;
	tx_chs = btfmslim->tx_chs;
	if ((chipset_ver >=  QCA_CHEROKEE_SOC_ID_0310) &&
		(chipset_ver <=  QCA_CHEROKEE_SOC_ID_0320_UMC)) {
		for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
		(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, tx_chs++) {
			if (tx_chs->port == SLAVE_SB_PGD_PORT_TX1_FM)
				tx_chs->port = CHRKVER3_SB_PGD_PORT_TX1_FM;
			else if (tx_chs->port == SLAVE_SB_PGD_PORT_TX2_FM)
				tx_chs->port = CHRKVER3_SB_PGD_PORT_TX2_FM;
			BTFMSLIM_INFO("Tx port:%d", tx_chs->port);
		}
		tx_chs = btfmslim->tx_chs;
	}
	if (!rx_chs || !tx_chs)
		return ret;

	BTFMSLIM_DBG("Rx: id\tname\tport\thdl\tch\tch_hdl");
	for (i = 0 ; (rx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
		(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, rx_chs++) {

		/* Get Rx port handler from slimbus driver based
		 * on port number
		 */
		ret = slim_get_slaveport(btfmslim->slim_pgd->laddr,
			rx_chs->port, &rx_chs->port_hdl, SLIM_SINK);
		if (ret < 0) {
			BTFMSLIM_ERR("slave port failure port#%d - ret[%d]",
				rx_chs->port, SLIM_SINK);
			return ret;
		}
		BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", rx_chs->id,
			rx_chs->name, rx_chs->port, rx_chs->port_hdl,
			rx_chs->ch, rx_chs->ch_hdl);
	}

	BTFMSLIM_DBG("Tx: id\tname\tport\thdl\tch\tch_hdl");
	for (i = 0; (tx_chs->port != BTFM_SLIM_PGD_PORT_LAST) &&
		(i < BTFM_SLIM_NUM_CODEC_DAIS); i++, tx_chs++) {

		/* Get Tx port handler from slimbus driver based
		 * on port number
		 */
		ret = slim_get_slaveport(btfmslim->slim_pgd->laddr,
			tx_chs->port, &tx_chs->port_hdl, SLIM_SRC);
		if (ret < 0) {
			BTFMSLIM_ERR("slave port failure port#%d - ret[%d]",
				tx_chs->port, SLIM_SRC);
			return ret;
		}
		BTFMSLIM_DBG("    %d\t%s\t%d\t%x\t%d\t%x", tx_chs->id,
			tx_chs->name, tx_chs->port, tx_chs->port_hdl,
			tx_chs->ch, tx_chs->ch_hdl);
	}
	return ret;
}

int btfm_slim_hw_init(struct btfmslim *btfmslim)
{
	int ret;
	int chipset_ver;
	struct slim_device *slim;
	struct slim_device *slim_ifd;

	BTFMSLIM_DBG("");
	if (!btfmslim)
		return -EINVAL;

	if (btfmslim->enabled) {
		BTFMSLIM_DBG("Already enabled");
		return 0;
	}

	slim = btfmslim->slim_pgd;
	slim_ifd = &btfmslim->slim_ifd;

	mutex_lock(&btfmslim->io_lock);
		BTFMSLIM_INFO(
			"PGD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
			slim->e_addr[0], slim->e_addr[1], slim->e_addr[2],
			slim->e_addr[3], slim->e_addr[4], slim->e_addr[5]);
		BTFMSLIM_INFO(
			"IFD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
			slim_ifd->e_addr[0], slim_ifd->e_addr[1],
			slim_ifd->e_addr[2], slim_ifd->e_addr[3],
			slim_ifd->e_addr[4], slim_ifd->e_addr[5]);

	chipset_ver = get_chipset_version();
	BTFMSLIM_INFO("chipset soc version:%x", chipset_ver);

	if (chipset_ver == QCA_HSP_SOC_ID_0100 ||
		chipset_ver == QCA_HSP_SOC_ID_0110 ||
		chipset_ver == QCA_HSP_SOC_ID_0210 ||
		chipset_ver == QCA_HSP_SOC_ID_1211 ||
		chipset_ver == QCA_HSP_SOC_ID_0200) {
		BTFMSLIM_INFO("chipset is hastings prime, overwriting EA");
		slim->e_addr[0] = 0x00;
		slim->e_addr[1] = 0x01;
		slim->e_addr[2] = 0x21;
		slim->e_addr[3] = 0x02;
		slim->e_addr[4] = 0x17;
		slim->e_addr[5] = 0x02;

		slim_ifd->e_addr[0] = 0x00;
		slim_ifd->e_addr[1] = 0x00;
		slim_ifd->e_addr[2] = 0x21;
		slim_ifd->e_addr[3] = 0x02;
		slim_ifd->e_addr[4] = 0x17;
		slim_ifd->e_addr[5] = 0x02;
	} else if (chipset_ver == QCA_HASTINGS_SOC_ID_0200) {
		BTFMSLIM_INFO("chipset is hastings 2.0, overwriting EA");
		slim->e_addr[0] = 0x00;
		slim->e_addr[1] = 0x01;
		slim->e_addr[2] = 0x20;
		slim->e_addr[3] = 0x02;
		slim->e_addr[4] = 0x17;
		slim->e_addr[5] = 0x02;

		slim_ifd->e_addr[0] = 0x00;
		slim_ifd->e_addr[1] = 0x00;
		slim_ifd->e_addr[2] = 0x20;
		slim_ifd->e_addr[3] = 0x02;
		slim_ifd->e_addr[4] = 0x17;
		slim_ifd->e_addr[5] = 0x02;
	}
		BTFMSLIM_INFO(
			"PGD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
			slim->e_addr[0], slim->e_addr[1], slim->e_addr[2],
			slim->e_addr[3], slim->e_addr[4], slim->e_addr[5]);
		BTFMSLIM_INFO(
			"IFD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
			slim_ifd->e_addr[0], slim_ifd->e_addr[1],
			slim_ifd->e_addr[2], slim_ifd->e_addr[3],
			slim_ifd->e_addr[4], slim_ifd->e_addr[5]);

	/* Assign Logical Address for PGD (Ported Generic Device)
	 * enumeration address
	 */
	ret = btfm_slim_get_logical_addr(btfmslim->slim_pgd);
	if (ret) {
		BTFMSLIM_ERR("failed to get slimbus %s logical address: %d",
		       btfmslim->slim_pgd->name, ret);
		goto error;
	}

	/* Assign Logical Address for Ported Generic Device
	 * enumeration address
	 */
	ret = btfm_slim_get_logical_addr(&btfmslim->slim_ifd);
	if (ret) {
		BTFMSLIM_ERR("failed to get slimbus %s logical address: %d",
		       btfmslim->slim_ifd.name, ret);
		goto error;
	}

	/* Allocate ports with logical address to get port handler from
	 * slimbus driver
	 */
	ret = btfm_slim_alloc_port(btfmslim);
	if (ret)
		goto error;

	/* Start vendor specific initialization and get port information */
	if (btfmslim->vendor_init)
		ret = btfmslim->vendor_init(btfmslim);

	/* Only when all registers read/write successfully, it set to
	 * enabled status
	 */
	btfmslim->enabled = 1;
error:
	mutex_unlock(&btfmslim->io_lock);
	return ret;
}


int btfm_slim_hw_deinit(struct btfmslim *btfmslim)
{
	int ret = 0;

	if (!btfmslim)
		return -EINVAL;

	if (!btfmslim->enabled) {
		BTFMSLIM_DBG("Already disabled");
		return 0;
	}
	mutex_lock(&btfmslim->io_lock);
	btfmslim->enabled = 0;
	mutex_unlock(&btfmslim->io_lock);
	return ret;
}

static int btfm_slim_get_dt_info(struct btfmslim *btfmslim)
{
	int ret = 0;
	struct slim_device *slim = btfmslim->slim_pgd;
	struct slim_device *slim_ifd = &btfmslim->slim_ifd;
	struct property *prop;

	if (!slim || !slim_ifd)
		return -EINVAL;

	if (slim->dev.of_node) {
		BTFMSLIM_DBG("Platform data from device tree (%s)",
			slim->name);
		ret = of_property_read_string(slim->dev.of_node,
			"qcom,btfm-slim-ifd", &slim_ifd->name);
		if (ret) {
			BTFMSLIM_ERR("Looking up %s property in node %s failed",
				"qcom,btfm-slim-ifd",
				 slim->dev.of_node->full_name);
			return -ENODEV;
		}
		BTFMSLIM_DBG("qcom,btfm-slim-ifd (%s)", slim_ifd->name);

		prop = of_find_property(slim->dev.of_node,
				"qcom,btfm-slim-ifd-elemental-addr", NULL);
		if (!prop) {
			BTFMSLIM_ERR("Looking up %s property in node %s failed",
				"qcom,btfm-slim-ifd-elemental-addr",
				slim->dev.of_node->full_name);
			return -ENODEV;
		} else if (prop->length != 6) {
			BTFMSLIM_ERR(
				"invalid codec slim ifd addr. addr length= %d",
				prop->length);
			return -ENODEV;
		}
		memcpy(slim_ifd->e_addr, prop->value, 6);
		BTFMSLIM_DBG(
			"PGD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
			slim->e_addr[0], slim->e_addr[1], slim->e_addr[2],
			slim->e_addr[3], slim->e_addr[4], slim->e_addr[5]);
		BTFMSLIM_DBG(
			"IFD Enum Addr: %.02x:%.02x:%.02x:%.02x:%.02x: %.02x",
			slim_ifd->e_addr[0], slim_ifd->e_addr[1],
			slim_ifd->e_addr[2], slim_ifd->e_addr[3],
			slim_ifd->e_addr[4], slim_ifd->e_addr[5]);
	} else {
		BTFMSLIM_ERR("Platform data is not valid");
	}

	return ret;
}

static int btfm_slim_probe(struct slim_device *slim)
{
	int ret = 0;
	struct btfmslim *btfm_slim;

	BTFMSLIM_DBG("");
	if (!slim->ctrl)
		return -EINVAL;

	/* Allocation btfmslim data pointer */
	btfm_slim = kzalloc(sizeof(struct btfmslim), GFP_KERNEL);
	if (btfm_slim == NULL) {
		BTFMSLIM_ERR("error, allocation failed");
		return -ENOMEM;
	}
	/* BTFM Slimbus driver control data configuration */
	btfm_slim->slim_pgd = slim;

	/* Assign vendor specific function */
	btfm_slim->rx_chs = SLIM_SLAVE_RXPORT;
	btfm_slim->tx_chs = SLIM_SLAVE_TXPORT;
	btfm_slim->vendor_init = SLIM_SLAVE_INIT;
	btfm_slim->vendor_port_en = SLIM_SLAVE_PORT_EN;

	/* Created Mutex for slimbus data transfer */
	mutex_init(&btfm_slim->io_lock);
	mutex_init(&btfm_slim->xfer_lock);

	/* Get Device tree node for Interface Device enumeration address */
	ret = btfm_slim_get_dt_info(btfm_slim);
	if (ret)
		goto dealloc;

	/* Add Interface Device for slimbus driver */
	ret = slim_add_device(btfm_slim->slim_pgd->ctrl, &btfm_slim->slim_ifd);
	if (ret) {
		BTFMSLIM_ERR("error, adding SLIMBUS device failed");
		goto dealloc;
	}

	/* Platform driver data allocation */
	slim->dev.platform_data = btfm_slim;

	/* Driver specific data allocation */
	btfm_slim->dev = &slim->dev;
	ret = btfm_slim_register_codec(&slim->dev);
	if (ret) {
		BTFMSLIM_ERR("error, registering slimbus codec failed");
		goto free;
	}
	ret = bt_register_slimdev(&slim->dev);
	if (ret < 0) {
		btfm_slim_unregister_codec(&slim->dev);
		goto free;
	}
	return ret;
free:
	slim_remove_device(&btfm_slim->slim_ifd);
dealloc:
	mutex_destroy(&btfm_slim->io_lock);
	mutex_destroy(&btfm_slim->xfer_lock);
	kfree(btfm_slim);
	return ret;
}
static int btfm_slim_remove(struct slim_device *slim)
{
	struct btfmslim *btfm_slim = slim->dev.platform_data;

	BTFMSLIM_DBG("");
	mutex_destroy(&btfm_slim->io_lock);
	mutex_destroy(&btfm_slim->xfer_lock);
	snd_soc_unregister_component(&slim->dev);

	BTFMSLIM_DBG("slim_remove_device() - btfm_slim->slim_ifd");
	slim_remove_device(&btfm_slim->slim_ifd);

	kfree(btfm_slim);

	BTFMSLIM_DBG("slim_remove_device() - btfm_slim->slim_pgd");
	slim_remove_device(slim);
	return 0;
}

static const struct slim_device_id btfm_slim_id[] = {
	{SLIM_SLAVE_COMPATIBLE_STR, 0},
	{}
};

static struct slim_driver btfm_slim_driver = {
	.driver = {
		.name = "btfmslim-driver",
		.owner = THIS_MODULE,
	},
	.probe = btfm_slim_probe,
	.remove = btfm_slim_remove,
	.id_table = btfm_slim_id
};

static int __init btfm_slim_init(void)
{
	int ret;

	BTFMSLIM_DBG("");
	ret = slim_driver_register(&btfm_slim_driver);
	if (ret)
		BTFMSLIM_ERR("Failed to register slimbus driver: %d", ret);
	return ret;
}

static void __exit btfm_slim_exit(void)
{
	BTFMSLIM_DBG("");
	slim_driver_unregister(&btfm_slim_driver);
}

module_init(btfm_slim_init);
module_exit(btfm_slim_exit);

MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("BTFM Slimbus Slave driver");
