// SPDX-License-Identifier: GPL-2.0-only /* * linux/drivers/mfd/mcp-sa11x0.c * * Copyright (C) 2001-2005 Russell King * * SA11x0 MCP (Multimedia Communications Port) driver. * * MCP read/write timeouts from Jordi Colomer, rehacked by rmk. */ #include #include #include #include #include #include #include #include #include #include #include #include #define DRIVER_NAME "sa11x0-mcp" struct mcp_sa11x0 { void __iomem *base0; void __iomem *base1; u32 mccr0; u32 mccr1; }; /* Register offsets */ #define MCCR0(m) ((m)->base0 + 0x00) #define MCDR0(m) ((m)->base0 + 0x08) #define MCDR1(m) ((m)->base0 + 0x0c) #define MCDR2(m) ((m)->base0 + 0x10) #define MCSR(m) ((m)->base0 + 0x18) #define MCCR1(m) ((m)->base1 + 0x00) #define priv(mcp) ((struct mcp_sa11x0 *)mcp_priv(mcp)) static void mcp_sa11x0_set_telecom_divisor(struct mcp *mcp, unsigned int divisor) { struct mcp_sa11x0 *m = priv(mcp); divisor /= 32; m->mccr0 &= ~0x00007f00; m->mccr0 |= divisor << 8; writel_relaxed(m->mccr0, MCCR0(m)); } static void mcp_sa11x0_set_audio_divisor(struct mcp *mcp, unsigned int divisor) { struct mcp_sa11x0 *m = priv(mcp); divisor /= 32; m->mccr0 &= ~0x0000007f; m->mccr0 |= divisor; writel_relaxed(m->mccr0, MCCR0(m)); } /* * Write data to the device. The bit should be set after 3 subframe * times (each frame is 64 clocks). We wait a maximum of 6 subframes. * We really should try doing something more productive while we * wait. */ static void mcp_sa11x0_write(struct mcp *mcp, unsigned int reg, unsigned int val) { struct mcp_sa11x0 *m = priv(mcp); int ret = -ETIME; int i; writel_relaxed(reg << 17 | MCDR2_Wr | (val & 0xffff), MCDR2(m)); for (i = 0; i < 2; i++) { udelay(mcp->rw_timeout); if (readl_relaxed(MCSR(m)) & MCSR_CWC) { ret = 0; break; } } if (ret < 0) printk(KERN_WARNING "mcp: write timed out\n"); } /* * Read data from the device. The bit should be set after 3 subframe * times (each frame is 64 clocks). We wait a maximum of 6 subframes. * We really should try doing something more productive while we * wait. */ static unsigned int mcp_sa11x0_read(struct mcp *mcp, unsigned int reg) { struct mcp_sa11x0 *m = priv(mcp); int ret = -ETIME; int i; writel_relaxed(reg << 17 | MCDR2_Rd, MCDR2(m)); for (i = 0; i < 2; i++) { udelay(mcp->rw_timeout); if (readl_relaxed(MCSR(m)) & MCSR_CRC) { ret = readl_relaxed(MCDR2(m)) & 0xffff; break; } } if (ret < 0) printk(KERN_WARNING "mcp: read timed out\n"); return ret; } static void mcp_sa11x0_enable(struct mcp *mcp) { struct mcp_sa11x0 *m = priv(mcp); writel(-1, MCSR(m)); m->mccr0 |= MCCR0_MCE; writel_relaxed(m->mccr0, MCCR0(m)); } static void mcp_sa11x0_disable(struct mcp *mcp) { struct mcp_sa11x0 *m = priv(mcp); m->mccr0 &= ~MCCR0_MCE; writel_relaxed(m->mccr0, MCCR0(m)); } /* * Our methods. */ static struct mcp_ops mcp_sa11x0 = { .set_telecom_divisor = mcp_sa11x0_set_telecom_divisor, .set_audio_divisor = mcp_sa11x0_set_audio_divisor, .reg_write = mcp_sa11x0_write, .reg_read = mcp_sa11x0_read, .enable = mcp_sa11x0_enable, .disable = mcp_sa11x0_disable, }; static int mcp_sa11x0_probe(struct platform_device *dev) { struct mcp_plat_data *data = dev_get_platdata(&dev->dev); struct resource *mem0, *mem1; struct mcp_sa11x0 *m; struct mcp *mcp; int ret; if (!data) return -ENODEV; mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0); mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1); if (!mem0 || !mem1) return -ENXIO; if (!request_mem_region(mem0->start, resource_size(mem0), DRIVER_NAME)) { ret = -EBUSY; goto err_mem0; } if (!request_mem_region(mem1->start, resource_size(mem1), DRIVER_NAME)) { ret = -EBUSY; goto err_mem1; } mcp = mcp_host_alloc(&dev->dev, sizeof(struct mcp_sa11x0)); if (!mcp) { ret = -ENOMEM; goto err_alloc; } mcp->owner = THIS_MODULE; mcp->ops = &mcp_sa11x0; mcp->sclk_rate = data->sclk_rate; m = priv(mcp); m->mccr0 = data->mccr0 | 0x7f7f; m->mccr1 = data->mccr1; m->base0 = ioremap(mem0->start, resource_size(mem0)); m->base1 = ioremap(mem1->start, resource_size(mem1)); if (!m->base0 || !m->base1) { ret = -ENOMEM; goto err_ioremap; } platform_set_drvdata(dev, mcp); /* * Initialise device. Note that we initially * set the sampling rate to minimum. */ writel_relaxed(-1, MCSR(m)); writel_relaxed(m->mccr1, MCCR1(m)); writel_relaxed(m->mccr0, MCCR0(m)); /* * Calculate the read/write timeout (us) from the bit clock * rate. This is the period for 3 64-bit frames. Always * round this time up. */ mcp->rw_timeout = DIV_ROUND_UP(64 * 3 * 1000000, mcp->sclk_rate); ret = mcp_host_add(mcp, data->codec_pdata); if (ret == 0) return 0; err_ioremap: iounmap(m->base1); iounmap(m->base0); mcp_host_free(mcp); err_alloc: release_mem_region(mem1->start, resource_size(mem1)); err_mem1: release_mem_region(mem0->start, resource_size(mem0)); err_mem0: return ret; } static void mcp_sa11x0_remove(struct platform_device *dev) { struct mcp *mcp = platform_get_drvdata(dev); struct mcp_sa11x0 *m = priv(mcp); struct resource *mem0, *mem1; if (m->mccr0 & MCCR0_MCE) dev_warn(&dev->dev, "device left active (missing disable call?)\n"); mem0 = platform_get_resource(dev, IORESOURCE_MEM, 0); mem1 = platform_get_resource(dev, IORESOURCE_MEM, 1); mcp_host_del(mcp); iounmap(m->base1); iounmap(m->base0); mcp_host_free(mcp); release_mem_region(mem1->start, resource_size(mem1)); release_mem_region(mem0->start, resource_size(mem0)); } static int mcp_sa11x0_suspend(struct device *dev) { struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev)); if (m->mccr0 & MCCR0_MCE) dev_warn(dev, "device left active (missing disable call?)\n"); writel(m->mccr0 & ~MCCR0_MCE, MCCR0(m)); return 0; } static int mcp_sa11x0_resume(struct device *dev) { struct mcp_sa11x0 *m = priv(dev_get_drvdata(dev)); writel_relaxed(m->mccr1, MCCR1(m)); writel_relaxed(m->mccr0, MCCR0(m)); return 0; } static const struct dev_pm_ops mcp_sa11x0_pm_ops = { .suspend = mcp_sa11x0_suspend, .freeze = mcp_sa11x0_suspend, .poweroff = mcp_sa11x0_suspend, .resume_noirq = mcp_sa11x0_resume, .thaw_noirq = mcp_sa11x0_resume, .restore_noirq = mcp_sa11x0_resume, }; static struct platform_driver mcp_sa11x0_driver = { .probe = mcp_sa11x0_probe, .remove = mcp_sa11x0_remove, .driver = { .name = DRIVER_NAME, .pm = pm_sleep_ptr(&mcp_sa11x0_pm_ops), }, }; /* * This needs re-working */ module_platform_driver(mcp_sa11x0_driver); MODULE_ALIAS("platform:" DRIVER_NAME); MODULE_AUTHOR("Russell King "); MODULE_DESCRIPTION("SA11x0 multimedia communications port driver"); MODULE_LICENSE("GPL");