18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci *  linux/drivers/mfd/mcp-core.c
48c2ecf20Sopenharmony_ci *
58c2ecf20Sopenharmony_ci *  Copyright (C) 2001 Russell King
68c2ecf20Sopenharmony_ci *
78c2ecf20Sopenharmony_ci *  Generic MCP (Multimedia Communications Port) layer.  All MCP locking
88c2ecf20Sopenharmony_ci *  is solely held within this file.
98c2ecf20Sopenharmony_ci */
108c2ecf20Sopenharmony_ci#include <linux/module.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/errno.h>
138c2ecf20Sopenharmony_ci#include <linux/smp.h>
148c2ecf20Sopenharmony_ci#include <linux/device.h>
158c2ecf20Sopenharmony_ci#include <linux/slab.h>
168c2ecf20Sopenharmony_ci#include <linux/string.h>
178c2ecf20Sopenharmony_ci#include <linux/mfd/mcp.h>
188c2ecf20Sopenharmony_ci
198c2ecf20Sopenharmony_ci
208c2ecf20Sopenharmony_ci#define to_mcp(d)		container_of(d, struct mcp, attached_device)
218c2ecf20Sopenharmony_ci#define to_mcp_driver(d)	container_of(d, struct mcp_driver, drv)
228c2ecf20Sopenharmony_ci
238c2ecf20Sopenharmony_cistatic int mcp_bus_match(struct device *dev, struct device_driver *drv)
248c2ecf20Sopenharmony_ci{
258c2ecf20Sopenharmony_ci	return 1;
268c2ecf20Sopenharmony_ci}
278c2ecf20Sopenharmony_ci
288c2ecf20Sopenharmony_cistatic int mcp_bus_probe(struct device *dev)
298c2ecf20Sopenharmony_ci{
308c2ecf20Sopenharmony_ci	struct mcp *mcp = to_mcp(dev);
318c2ecf20Sopenharmony_ci	struct mcp_driver *drv = to_mcp_driver(dev->driver);
328c2ecf20Sopenharmony_ci
338c2ecf20Sopenharmony_ci	return drv->probe(mcp);
348c2ecf20Sopenharmony_ci}
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic int mcp_bus_remove(struct device *dev)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	struct mcp *mcp = to_mcp(dev);
398c2ecf20Sopenharmony_ci	struct mcp_driver *drv = to_mcp_driver(dev->driver);
408c2ecf20Sopenharmony_ci
418c2ecf20Sopenharmony_ci	drv->remove(mcp);
428c2ecf20Sopenharmony_ci	return 0;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cistatic struct bus_type mcp_bus_type = {
468c2ecf20Sopenharmony_ci	.name		= "mcp",
478c2ecf20Sopenharmony_ci	.match		= mcp_bus_match,
488c2ecf20Sopenharmony_ci	.probe		= mcp_bus_probe,
498c2ecf20Sopenharmony_ci	.remove		= mcp_bus_remove,
508c2ecf20Sopenharmony_ci};
518c2ecf20Sopenharmony_ci
528c2ecf20Sopenharmony_ci/**
538c2ecf20Sopenharmony_ci *	mcp_set_telecom_divisor - set the telecom divisor
548c2ecf20Sopenharmony_ci *	@mcp: MCP interface structure
558c2ecf20Sopenharmony_ci *	@div: SIB clock divisor
568c2ecf20Sopenharmony_ci *
578c2ecf20Sopenharmony_ci *	Set the telecom divisor on the MCP interface.  The resulting
588c2ecf20Sopenharmony_ci *	sample rate is SIBCLOCK/div.
598c2ecf20Sopenharmony_ci */
608c2ecf20Sopenharmony_civoid mcp_set_telecom_divisor(struct mcp *mcp, unsigned int div)
618c2ecf20Sopenharmony_ci{
628c2ecf20Sopenharmony_ci	unsigned long flags;
638c2ecf20Sopenharmony_ci
648c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mcp->lock, flags);
658c2ecf20Sopenharmony_ci	mcp->ops->set_telecom_divisor(mcp, div);
668c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mcp->lock, flags);
678c2ecf20Sopenharmony_ci}
688c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_set_telecom_divisor);
698c2ecf20Sopenharmony_ci
708c2ecf20Sopenharmony_ci/**
718c2ecf20Sopenharmony_ci *	mcp_set_audio_divisor - set the audio divisor
728c2ecf20Sopenharmony_ci *	@mcp: MCP interface structure
738c2ecf20Sopenharmony_ci *	@div: SIB clock divisor
748c2ecf20Sopenharmony_ci *
758c2ecf20Sopenharmony_ci *	Set the audio divisor on the MCP interface.
768c2ecf20Sopenharmony_ci */
778c2ecf20Sopenharmony_civoid mcp_set_audio_divisor(struct mcp *mcp, unsigned int div)
788c2ecf20Sopenharmony_ci{
798c2ecf20Sopenharmony_ci	unsigned long flags;
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mcp->lock, flags);
828c2ecf20Sopenharmony_ci	mcp->ops->set_audio_divisor(mcp, div);
838c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mcp->lock, flags);
848c2ecf20Sopenharmony_ci}
858c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_set_audio_divisor);
868c2ecf20Sopenharmony_ci
878c2ecf20Sopenharmony_ci/**
888c2ecf20Sopenharmony_ci *	mcp_reg_write - write a device register
898c2ecf20Sopenharmony_ci *	@mcp: MCP interface structure
908c2ecf20Sopenharmony_ci *	@reg: 4-bit register index
918c2ecf20Sopenharmony_ci *	@val: 16-bit data value
928c2ecf20Sopenharmony_ci *
938c2ecf20Sopenharmony_ci *	Write a device register.  The MCP interface must be enabled
948c2ecf20Sopenharmony_ci *	to prevent this function hanging.
958c2ecf20Sopenharmony_ci */
968c2ecf20Sopenharmony_civoid mcp_reg_write(struct mcp *mcp, unsigned int reg, unsigned int val)
978c2ecf20Sopenharmony_ci{
988c2ecf20Sopenharmony_ci	unsigned long flags;
998c2ecf20Sopenharmony_ci
1008c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mcp->lock, flags);
1018c2ecf20Sopenharmony_ci	mcp->ops->reg_write(mcp, reg, val);
1028c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mcp->lock, flags);
1038c2ecf20Sopenharmony_ci}
1048c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_reg_write);
1058c2ecf20Sopenharmony_ci
1068c2ecf20Sopenharmony_ci/**
1078c2ecf20Sopenharmony_ci *	mcp_reg_read - read a device register
1088c2ecf20Sopenharmony_ci *	@mcp: MCP interface structure
1098c2ecf20Sopenharmony_ci *	@reg: 4-bit register index
1108c2ecf20Sopenharmony_ci *
1118c2ecf20Sopenharmony_ci *	Read a device register and return its value.  The MCP interface
1128c2ecf20Sopenharmony_ci *	must be enabled to prevent this function hanging.
1138c2ecf20Sopenharmony_ci */
1148c2ecf20Sopenharmony_ciunsigned int mcp_reg_read(struct mcp *mcp, unsigned int reg)
1158c2ecf20Sopenharmony_ci{
1168c2ecf20Sopenharmony_ci	unsigned long flags;
1178c2ecf20Sopenharmony_ci	unsigned int val;
1188c2ecf20Sopenharmony_ci
1198c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mcp->lock, flags);
1208c2ecf20Sopenharmony_ci	val = mcp->ops->reg_read(mcp, reg);
1218c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mcp->lock, flags);
1228c2ecf20Sopenharmony_ci
1238c2ecf20Sopenharmony_ci	return val;
1248c2ecf20Sopenharmony_ci}
1258c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_reg_read);
1268c2ecf20Sopenharmony_ci
1278c2ecf20Sopenharmony_ci/**
1288c2ecf20Sopenharmony_ci *	mcp_enable - enable the MCP interface
1298c2ecf20Sopenharmony_ci *	@mcp: MCP interface to enable
1308c2ecf20Sopenharmony_ci *
1318c2ecf20Sopenharmony_ci *	Enable the MCP interface.  Each call to mcp_enable will need
1328c2ecf20Sopenharmony_ci *	a corresponding call to mcp_disable to disable the interface.
1338c2ecf20Sopenharmony_ci */
1348c2ecf20Sopenharmony_civoid mcp_enable(struct mcp *mcp)
1358c2ecf20Sopenharmony_ci{
1368c2ecf20Sopenharmony_ci	unsigned long flags;
1378c2ecf20Sopenharmony_ci
1388c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mcp->lock, flags);
1398c2ecf20Sopenharmony_ci	if (mcp->use_count++ == 0)
1408c2ecf20Sopenharmony_ci		mcp->ops->enable(mcp);
1418c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mcp->lock, flags);
1428c2ecf20Sopenharmony_ci}
1438c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_enable);
1448c2ecf20Sopenharmony_ci
1458c2ecf20Sopenharmony_ci/**
1468c2ecf20Sopenharmony_ci *	mcp_disable - disable the MCP interface
1478c2ecf20Sopenharmony_ci *	@mcp: MCP interface to disable
1488c2ecf20Sopenharmony_ci *
1498c2ecf20Sopenharmony_ci *	Disable the MCP interface.  The MCP interface will only be
1508c2ecf20Sopenharmony_ci *	disabled once the number of calls to mcp_enable matches the
1518c2ecf20Sopenharmony_ci *	number of calls to mcp_disable.
1528c2ecf20Sopenharmony_ci */
1538c2ecf20Sopenharmony_civoid mcp_disable(struct mcp *mcp)
1548c2ecf20Sopenharmony_ci{
1558c2ecf20Sopenharmony_ci	unsigned long flags;
1568c2ecf20Sopenharmony_ci
1578c2ecf20Sopenharmony_ci	spin_lock_irqsave(&mcp->lock, flags);
1588c2ecf20Sopenharmony_ci	if (--mcp->use_count == 0)
1598c2ecf20Sopenharmony_ci		mcp->ops->disable(mcp);
1608c2ecf20Sopenharmony_ci	spin_unlock_irqrestore(&mcp->lock, flags);
1618c2ecf20Sopenharmony_ci}
1628c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_disable);
1638c2ecf20Sopenharmony_ci
1648c2ecf20Sopenharmony_cistatic void mcp_release(struct device *dev)
1658c2ecf20Sopenharmony_ci{
1668c2ecf20Sopenharmony_ci	struct mcp *mcp = container_of(dev, struct mcp, attached_device);
1678c2ecf20Sopenharmony_ci
1688c2ecf20Sopenharmony_ci	kfree(mcp);
1698c2ecf20Sopenharmony_ci}
1708c2ecf20Sopenharmony_ci
1718c2ecf20Sopenharmony_cistruct mcp *mcp_host_alloc(struct device *parent, size_t size)
1728c2ecf20Sopenharmony_ci{
1738c2ecf20Sopenharmony_ci	struct mcp *mcp;
1748c2ecf20Sopenharmony_ci
1758c2ecf20Sopenharmony_ci	mcp = kzalloc(sizeof(struct mcp) + size, GFP_KERNEL);
1768c2ecf20Sopenharmony_ci	if (mcp) {
1778c2ecf20Sopenharmony_ci		spin_lock_init(&mcp->lock);
1788c2ecf20Sopenharmony_ci		device_initialize(&mcp->attached_device);
1798c2ecf20Sopenharmony_ci		mcp->attached_device.parent = parent;
1808c2ecf20Sopenharmony_ci		mcp->attached_device.bus = &mcp_bus_type;
1818c2ecf20Sopenharmony_ci		mcp->attached_device.dma_mask = parent->dma_mask;
1828c2ecf20Sopenharmony_ci		mcp->attached_device.release = mcp_release;
1838c2ecf20Sopenharmony_ci	}
1848c2ecf20Sopenharmony_ci	return mcp;
1858c2ecf20Sopenharmony_ci}
1868c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_host_alloc);
1878c2ecf20Sopenharmony_ci
1888c2ecf20Sopenharmony_ciint mcp_host_add(struct mcp *mcp, void *pdata)
1898c2ecf20Sopenharmony_ci{
1908c2ecf20Sopenharmony_ci	mcp->attached_device.platform_data = pdata;
1918c2ecf20Sopenharmony_ci	dev_set_name(&mcp->attached_device, "mcp0");
1928c2ecf20Sopenharmony_ci	return device_add(&mcp->attached_device);
1938c2ecf20Sopenharmony_ci}
1948c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_host_add);
1958c2ecf20Sopenharmony_ci
1968c2ecf20Sopenharmony_civoid mcp_host_del(struct mcp *mcp)
1978c2ecf20Sopenharmony_ci{
1988c2ecf20Sopenharmony_ci	device_del(&mcp->attached_device);
1998c2ecf20Sopenharmony_ci}
2008c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_host_del);
2018c2ecf20Sopenharmony_ci
2028c2ecf20Sopenharmony_civoid mcp_host_free(struct mcp *mcp)
2038c2ecf20Sopenharmony_ci{
2048c2ecf20Sopenharmony_ci	put_device(&mcp->attached_device);
2058c2ecf20Sopenharmony_ci}
2068c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_host_free);
2078c2ecf20Sopenharmony_ci
2088c2ecf20Sopenharmony_ciint mcp_driver_register(struct mcp_driver *mcpdrv)
2098c2ecf20Sopenharmony_ci{
2108c2ecf20Sopenharmony_ci	mcpdrv->drv.bus = &mcp_bus_type;
2118c2ecf20Sopenharmony_ci	return driver_register(&mcpdrv->drv);
2128c2ecf20Sopenharmony_ci}
2138c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_driver_register);
2148c2ecf20Sopenharmony_ci
2158c2ecf20Sopenharmony_civoid mcp_driver_unregister(struct mcp_driver *mcpdrv)
2168c2ecf20Sopenharmony_ci{
2178c2ecf20Sopenharmony_ci	driver_unregister(&mcpdrv->drv);
2188c2ecf20Sopenharmony_ci}
2198c2ecf20Sopenharmony_ciEXPORT_SYMBOL(mcp_driver_unregister);
2208c2ecf20Sopenharmony_ci
2218c2ecf20Sopenharmony_cistatic int __init mcp_init(void)
2228c2ecf20Sopenharmony_ci{
2238c2ecf20Sopenharmony_ci	return bus_register(&mcp_bus_type);
2248c2ecf20Sopenharmony_ci}
2258c2ecf20Sopenharmony_ci
2268c2ecf20Sopenharmony_cistatic void __exit mcp_exit(void)
2278c2ecf20Sopenharmony_ci{
2288c2ecf20Sopenharmony_ci	bus_unregister(&mcp_bus_type);
2298c2ecf20Sopenharmony_ci}
2308c2ecf20Sopenharmony_ci
2318c2ecf20Sopenharmony_cimodule_init(mcp_init);
2328c2ecf20Sopenharmony_cimodule_exit(mcp_exit);
2338c2ecf20Sopenharmony_ci
2348c2ecf20Sopenharmony_ciMODULE_AUTHOR("Russell King <rmk@arm.linux.org.uk>");
2358c2ecf20Sopenharmony_ciMODULE_DESCRIPTION("Core multimedia communications port driver");
2368c2ecf20Sopenharmony_ciMODULE_LICENSE("GPL");
237