diff options
| author | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2026-08-19 10:09:22 -0700 |
| commit | f4cdf7ca9a1fdcca413157df19753f388a5a224e (patch) | |
| tree | 60a31895b2446cd7fa27514e8c2843534a0de0da /drivers/misc/atmel-ssc.c | |
| download | linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.tar.gz linux-stable-f4cdf7ca9a1fdcca413157df19753f388a5a224e.zip | |
Merge tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-mediagrafted
Pull media updates from Mauro Carvalho Chehab:
- v4l2-core: added ISP statistics support and per-block validation
- v4l2-core: Allow unknown HDR10 white point and luminance
- New camera sensors: Sony IMX678 and IMX471m, Himax HM1092 IR sensor
- New codec: Milos: VPU v2.0 codec support
- isp driver: gained support for Dreamchip RPPX1 ISP framework
- vsp1 driver: gained support for RZ/T2H and RZ/N2H
- Novalake driver: gained CVS support for new NVL hardware
- dvb-core: fix feed leak on failed DMX_ADD_PID
- several driver fixes, cleanups and minor improvements
* tag 'media/v7.3-1' of git://git.kernel.org/pub/scm/linux/kernel/git/mchehab/linux-media: (308 commits)
media: ipu-bridge: check all DMI entries when overriding sensor rotation
media: v4l2-async: avoid deleting unlinked ASC entry on link error
media: rzg2l-cru: Align bytesperline to hardware DMA stride requirement
media: intel/ipu6: fix async notifier cleanup leak on parse error
media: staging/ipu7: fix async notifier UAF on probe error path
media: amd: isp4: fix self-deadlock in isp4sd_pwron_and_init() error path
media: amd: isp4: release partial allocations in isp4if_alloc_fw_gpumem()
media: rcar-isp: Fix VSPX reference leaks
media: rcar-isp: Release ISPCORE resources
media: i2c: imx415: Release runtime PM reference on VBLANK error
media: i2c: imx415: Return test pattern write errors
media: renesas: vsp1: Declare index variables in for loop statement
media: renesas: vsp1: Make reset control optional to support platforms without a reset line
media: dt-bindings: media: renesas,vsp1: Document RZ/T2H and RZ/N2H SoCs
media: dt-bindings: media: renesas,fcp: Document RZ/T2H and RZ/N2H SoCs
media: nxp: imx8-isi: Add additional 32-bit RGB format support
media: nxp: imx8-isi: Add 16-bit raw Bayer format support
media: nxp: imx8-isi: Implement per-stream reference counting for multiplexed streams
media: nxp: imx8-isi: Use BIT_ULL() for 64-bit stream masks
media: nxp: imx8-isi: Correct color map between V4L2 and ISI
...
Diffstat (limited to 'drivers/misc/atmel-ssc.c')
| -rw-r--r-- | drivers/misc/atmel-ssc.c | 279 |
1 files changed, 279 insertions, 0 deletions
diff --git a/drivers/misc/atmel-ssc.c b/drivers/misc/atmel-ssc.c new file mode 100644 index 000000000..35a196341 --- /dev/null +++ b/drivers/misc/atmel-ssc.c @@ -0,0 +1,279 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Atmel SSC driver + * + * Copyright (C) 2007 Atmel Corporation + */ + +#include <linux/platform_device.h> +#include <linux/list.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/mutex.h> +#include <linux/atmel-ssc.h> +#include <linux/slab.h> +#include <linux/module.h> + +#include <linux/of.h> + +#include "../../sound/soc/atmel/atmel_ssc_dai.h" + +/* Serialize access to ssc_list and user count */ +static DEFINE_MUTEX(user_lock); +static LIST_HEAD(ssc_list); + +struct ssc_device *ssc_request(unsigned int ssc_num) +{ + int ssc_valid = 0; + struct ssc_device *ssc; + + mutex_lock(&user_lock); + list_for_each_entry(ssc, &ssc_list, list) { + if (ssc->pdev->dev.of_node) { + if (of_alias_get_id(ssc->pdev->dev.of_node, "ssc") + == ssc_num) { + ssc->pdev->id = ssc_num; + ssc_valid = 1; + break; + } + } else if (ssc->pdev->id == ssc_num) { + ssc_valid = 1; + break; + } + } + + if (!ssc_valid) { + mutex_unlock(&user_lock); + pr_err("ssc: ssc%d platform device is missing\n", ssc_num); + return ERR_PTR(-ENODEV); + } + + if (ssc->user) { + mutex_unlock(&user_lock); + dev_dbg(&ssc->pdev->dev, "module busy\n"); + return ERR_PTR(-EBUSY); + } + ssc->user++; + mutex_unlock(&user_lock); + + clk_prepare(ssc->clk); + + return ssc; +} +EXPORT_SYMBOL(ssc_request); + +void ssc_free(struct ssc_device *ssc) +{ + bool disable_clk = true; + + mutex_lock(&user_lock); + if (ssc->user) + ssc->user--; + else { + disable_clk = false; + dev_dbg(&ssc->pdev->dev, "device already free\n"); + } + mutex_unlock(&user_lock); + + if (disable_clk) + clk_unprepare(ssc->clk); +} +EXPORT_SYMBOL(ssc_free); + +static struct atmel_ssc_platform_data at91rm9200_config = { + .use_dma = 0, + .has_fslen_ext = 0, +}; + +static struct atmel_ssc_platform_data at91sam9rl_config = { + .use_dma = 0, + .has_fslen_ext = 1, +}; + +static struct atmel_ssc_platform_data at91sam9g45_config = { + .use_dma = 1, + .has_fslen_ext = 1, +}; + +static const struct platform_device_id atmel_ssc_devtypes[] = { + { + .name = "at91rm9200_ssc", + .driver_data = (unsigned long) &at91rm9200_config, + }, { + .name = "at91sam9rl_ssc", + .driver_data = (unsigned long) &at91sam9rl_config, + }, { + .name = "at91sam9g45_ssc", + .driver_data = (unsigned long) &at91sam9g45_config, + }, { + /* sentinel */ + } +}; + +#ifdef CONFIG_OF +static const struct of_device_id atmel_ssc_dt_ids[] = { + { + .compatible = "atmel,at91rm9200-ssc", + .data = &at91rm9200_config, + }, { + .compatible = "atmel,at91sam9rl-ssc", + .data = &at91sam9rl_config, + }, { + .compatible = "atmel,at91sam9g45-ssc", + .data = &at91sam9g45_config, + }, { + /* sentinel */ + } +}; +MODULE_DEVICE_TABLE(of, atmel_ssc_dt_ids); +#endif + +static inline const struct atmel_ssc_platform_data * + atmel_ssc_get_driver_data(struct platform_device *pdev) +{ + if (pdev->dev.of_node) { + const struct of_device_id *match; + match = of_match_node(atmel_ssc_dt_ids, pdev->dev.of_node); + if (match == NULL) + return NULL; + return match->data; + } + + return (struct atmel_ssc_platform_data *) + platform_get_device_id(pdev)->driver_data; +} + +#ifdef CONFIG_SND_ATMEL_SOC_SSC +static int ssc_sound_dai_probe(struct ssc_device *ssc) +{ + struct device_node *np = ssc->pdev->dev.of_node; + int ret; + int id; + + ssc->sound_dai = false; + + if (!of_property_present(np, "#sound-dai-cells")) + return 0; + + id = of_alias_get_id(np, "ssc"); + if (id < 0) + return id; + + ret = atmel_ssc_set_audio(id); + ssc->sound_dai = !ret; + + return ret; +} + +static void ssc_sound_dai_remove(struct ssc_device *ssc) +{ + if (!ssc->sound_dai) + return; + + atmel_ssc_put_audio(of_alias_get_id(ssc->pdev->dev.of_node, "ssc")); +} +#else +static inline int ssc_sound_dai_probe(struct ssc_device *ssc) +{ + if (of_property_present(ssc->pdev->dev.of_node, "#sound-dai-cells")) + return -ENOTSUPP; + + return 0; +} + +static inline void ssc_sound_dai_remove(struct ssc_device *ssc) +{ +} +#endif + +static int ssc_probe(struct platform_device *pdev) +{ + struct resource *regs; + struct ssc_device *ssc; + const struct atmel_ssc_platform_data *plat_dat; + + ssc = devm_kzalloc(&pdev->dev, sizeof(struct ssc_device), GFP_KERNEL); + if (!ssc) { + dev_dbg(&pdev->dev, "out of memory\n"); + return -ENOMEM; + } + + ssc->pdev = pdev; + + plat_dat = atmel_ssc_get_driver_data(pdev); + if (!plat_dat) + return -ENODEV; + ssc->pdata = (struct atmel_ssc_platform_data *)plat_dat; + + if (pdev->dev.of_node) { + struct device_node *np = pdev->dev.of_node; + ssc->clk_from_rk_pin = + of_property_read_bool(np, "atmel,clk-from-rk-pin"); + } + + ssc->regs = devm_platform_get_and_ioremap_resource(pdev, 0, ®s); + if (IS_ERR(ssc->regs)) + return PTR_ERR(ssc->regs); + + ssc->phybase = regs->start; + + ssc->clk = devm_clk_get(&pdev->dev, "pclk"); + if (IS_ERR(ssc->clk)) { + dev_dbg(&pdev->dev, "no pclk clock defined\n"); + return -ENXIO; + } + + /* disable all interrupts */ + clk_prepare_enable(ssc->clk); + ssc_writel(ssc->regs, IDR, -1); + ssc_readl(ssc->regs, SR); + clk_disable_unprepare(ssc->clk); + + ssc->irq = platform_get_irq(pdev, 0); + if (ssc->irq < 0) { + dev_dbg(&pdev->dev, "could not get irq\n"); + return ssc->irq; + } + + mutex_lock(&user_lock); + list_add_tail(&ssc->list, &ssc_list); + mutex_unlock(&user_lock); + + platform_set_drvdata(pdev, ssc); + + dev_info(&pdev->dev, "Atmel SSC device at 0x%p (irq %d)\n", + ssc->regs, ssc->irq); + + if (ssc_sound_dai_probe(ssc)) + dev_err(&pdev->dev, "failed to auto-setup ssc for audio\n"); + + return 0; +} + +static void ssc_remove(struct platform_device *pdev) +{ + struct ssc_device *ssc = platform_get_drvdata(pdev); + + ssc_sound_dai_remove(ssc); + + mutex_lock(&user_lock); + list_del(&ssc->list); + mutex_unlock(&user_lock); +} + +static struct platform_driver ssc_driver = { + .driver = { + .name = "ssc", + .of_match_table = of_match_ptr(atmel_ssc_dt_ids), + }, + .id_table = atmel_ssc_devtypes, + .probe = ssc_probe, + .remove = ssc_remove, +}; +module_platform_driver(ssc_driver); + +MODULE_AUTHOR("Hans-Christian Noren Egtvedt <egtvedt@samfundet.no>"); +MODULE_DESCRIPTION("SSC driver for Atmel AT91"); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:ssc"); |
