root/drivers/mfd/mxs-lradc.c
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Freescale MXS Low Resolution Analog-to-Digital Converter driver
 *
 * Copyright (c) 2012 DENX Software Engineering, GmbH.
 * Copyright (c) 2017 Ksenija Stanojevic <ksenija.stanojevic@gmail.com>
 *
 * Authors:
 *  Marek Vasut <marex@denx.de>
 *  Ksenija Stanojevic <ksenija.stanojevic@gmail.com>
 */

#include <linux/clk.h>
#include <linux/device.h>
#include <linux/mfd/core.h>
#include <linux/mfd/mxs-lradc.h>
#include <linux/module.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/property.h>
#include <linux/slab.h>

#define ADC_CELL                0
#define TSC_CELL                1
#define RES_MEM                 0

enum mx23_lradc_irqs {
        MX23_LRADC_TS_IRQ = 0,
        MX23_LRADC_CH0_IRQ,
        MX23_LRADC_CH1_IRQ,
        MX23_LRADC_CH2_IRQ,
        MX23_LRADC_CH3_IRQ,
        MX23_LRADC_CH4_IRQ,
        MX23_LRADC_CH5_IRQ,
        MX23_LRADC_CH6_IRQ,
        MX23_LRADC_CH7_IRQ,
};

enum mx28_lradc_irqs {
        MX28_LRADC_TS_IRQ = 0,
        MX28_LRADC_TRESH0_IRQ,
        MX28_LRADC_TRESH1_IRQ,
        MX28_LRADC_CH0_IRQ,
        MX28_LRADC_CH1_IRQ,
        MX28_LRADC_CH2_IRQ,
        MX28_LRADC_CH3_IRQ,
        MX28_LRADC_CH4_IRQ,
        MX28_LRADC_CH5_IRQ,
        MX28_LRADC_CH6_IRQ,
        MX28_LRADC_CH7_IRQ,
        MX28_LRADC_BUTTON0_IRQ,
        MX28_LRADC_BUTTON1_IRQ,
};

static struct resource mx23_adc_resources[] = {
        DEFINE_RES_MEM(0x0, 0x0),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH0_IRQ, "mxs-lradc-channel0"),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH1_IRQ, "mxs-lradc-channel1"),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH2_IRQ, "mxs-lradc-channel2"),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH3_IRQ, "mxs-lradc-channel3"),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH4_IRQ, "mxs-lradc-channel4"),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH5_IRQ, "mxs-lradc-channel5"),
};

static struct resource mx23_touchscreen_resources[] = {
        DEFINE_RES_MEM(0x0, 0x0),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_TS_IRQ, "mxs-lradc-touchscreen"),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH6_IRQ, "mxs-lradc-channel6"),
        DEFINE_RES_IRQ_NAMED(MX23_LRADC_CH7_IRQ, "mxs-lradc-channel7"),
};

static struct resource mx28_adc_resources[] = {
        DEFINE_RES_MEM(0x0, 0x0),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_TRESH0_IRQ, "mxs-lradc-thresh0"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_TRESH1_IRQ, "mxs-lradc-thresh1"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH0_IRQ, "mxs-lradc-channel0"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH1_IRQ, "mxs-lradc-channel1"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH2_IRQ, "mxs-lradc-channel2"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH3_IRQ, "mxs-lradc-channel3"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH4_IRQ, "mxs-lradc-channel4"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH5_IRQ, "mxs-lradc-channel5"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_BUTTON0_IRQ, "mxs-lradc-button0"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_BUTTON1_IRQ, "mxs-lradc-button1"),
};

static struct resource mx28_touchscreen_resources[] = {
        DEFINE_RES_MEM(0x0, 0x0),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_TS_IRQ, "mxs-lradc-touchscreen"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH6_IRQ, "mxs-lradc-channel6"),
        DEFINE_RES_IRQ_NAMED(MX28_LRADC_CH7_IRQ, "mxs-lradc-channel7"),
};

static struct mfd_cell mx23_cells[] = {
        {
                .name = "mxs-lradc-adc",
                .resources = mx23_adc_resources,
                .num_resources = ARRAY_SIZE(mx23_adc_resources),
        },
        {
                .name = "mxs-lradc-ts",
                .resources = mx23_touchscreen_resources,
                .num_resources = ARRAY_SIZE(mx23_touchscreen_resources),
        },
};

static struct mfd_cell mx28_cells[] = {
        {
                .name = "mxs-lradc-adc",
                .resources = mx28_adc_resources,
                .num_resources = ARRAY_SIZE(mx28_adc_resources),
        },
        {
                .name = "mxs-lradc-ts",
                .resources = mx28_touchscreen_resources,
                .num_resources = ARRAY_SIZE(mx28_touchscreen_resources),
        }
};

static const struct of_device_id mxs_lradc_dt_ids[] = {
        { .compatible = "fsl,imx23-lradc", .data = (void *)IMX23_LRADC, },
        { .compatible = "fsl,imx28-lradc", .data = (void *)IMX28_LRADC, },
        { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, mxs_lradc_dt_ids);

static int mxs_lradc_probe(struct platform_device *pdev)
{
        struct device *dev = &pdev->dev;
        struct device_node *node = dev->of_node;
        struct mxs_lradc *lradc;
        struct mfd_cell *cells = NULL;
        struct resource *res;
        int ret = 0;
        u32 ts_wires = 0;

        lradc = devm_kzalloc(&pdev->dev, sizeof(*lradc), GFP_KERNEL);
        if (!lradc)
                return -ENOMEM;

        lradc->soc = (kernel_ulong_t)device_get_match_data(&pdev->dev);

        lradc->clk = devm_clk_get(&pdev->dev, NULL);
        if (IS_ERR(lradc->clk)) {
                dev_err(dev, "Failed to get the delay unit clock\n");
                return PTR_ERR(lradc->clk);
        }

        ret = clk_prepare_enable(lradc->clk);
        if (ret) {
                dev_err(dev, "Failed to enable the delay unit clock\n");
                return ret;
        }

        ret = of_property_read_u32(node, "fsl,lradc-touchscreen-wires",
                                         &ts_wires);

        if (!ret) {
                lradc->buffer_vchans = BUFFER_VCHANS_LIMITED;

                switch (ts_wires) {
                case 4:
                        lradc->touchscreen_wire = MXS_LRADC_TOUCHSCREEN_4WIRE;
                        break;
                case 5:
                        if (lradc->soc == IMX28_LRADC) {
                                lradc->touchscreen_wire =
                                        MXS_LRADC_TOUCHSCREEN_5WIRE;
                                break;
                        }
                        fallthrough;    /* to an error message for i.MX23 */
                default:
                        dev_err(&pdev->dev,
                                "Unsupported number of touchscreen wires (%d)\n"
                                , ts_wires);
                        ret = -EINVAL;
                        goto err_clk;
                }
        } else {
                lradc->buffer_vchans = BUFFER_VCHANS_ALL;
        }

        platform_set_drvdata(pdev, lradc);

        res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
        if (!res) {
                ret = -ENOMEM;
                goto err_clk;
        }

        switch (lradc->soc) {
        case IMX23_LRADC:
                mx23_adc_resources[RES_MEM] = *res;
                mx23_touchscreen_resources[RES_MEM] = *res;
                cells = mx23_cells;
                break;
        case IMX28_LRADC:
                mx28_adc_resources[RES_MEM] = *res;
                mx28_touchscreen_resources[RES_MEM] = *res;
                cells = mx28_cells;
                break;
        default:
                dev_err(dev, "Unsupported SoC\n");
                ret = -ENODEV;
                goto err_clk;
        }

        ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE,
                                   &cells[ADC_CELL], 1, NULL, 0, NULL);
        if (ret) {
                dev_err(&pdev->dev, "Failed to add the ADC subdevice\n");
                goto err_clk;
        }

        if (!lradc->touchscreen_wire)
                return 0;

        ret = devm_mfd_add_devices(&pdev->dev, PLATFORM_DEVID_NONE,
                                   &cells[TSC_CELL], 1, NULL, 0, NULL);
        if (ret) {
                dev_err(&pdev->dev,
                        "Failed to add the touchscreen subdevice\n");
                goto err_clk;
        }

        return 0;

err_clk:
        clk_disable_unprepare(lradc->clk);

        return ret;
}

static void mxs_lradc_remove(struct platform_device *pdev)
{
        struct mxs_lradc *lradc = platform_get_drvdata(pdev);

        clk_disable_unprepare(lradc->clk);
}

static struct platform_driver mxs_lradc_driver = {
        .driver = {
                .name = "mxs-lradc",
                .of_match_table = mxs_lradc_dt_ids,
        },
        .probe = mxs_lradc_probe,
        .remove = mxs_lradc_remove,
};
module_platform_driver(mxs_lradc_driver);

MODULE_AUTHOR("Ksenija Stanojevic <ksenija.stanojevic@gmail.com>");
MODULE_DESCRIPTION("Freescale i.MX23/i.MX28 LRADC driver");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:mxs-lradc");