linuxOS_D21X/source/linux-5.10/drivers/iio/adc/artinchip_adc.c
2024-11-29 16:33:21 +08:00

763 lines
19 KiB
C

// SPDX-License-Identifier: GPL-2.0-or-later
/*
* GPAI driver of ArtInChip SoC
*
* Copyright (C) 2020-2024 ArtInChip Technology Co., Ltd.
* Authors: Matteo <duanmt@artinchip.com>
*/
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/interrupt.h>
#include <linux/io.h>
#include <linux/of.h>
#include <linux/of_device.h>
#include <linux/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/reset.h>
#include <linux/iio/iio.h>
#include <linux/iio/events.h>
#define AIC_GPAI_NAME "aic-gpai"
#define AIC_GPAI_MAX_CH 8
#define AIC_GPAI_TIMEOUT msecs_to_jiffies(1000)
#define GPAI_FIFO_MAX_DEPTH 32
enum aic_gpai_mode {
AIC_GPAI_MODE_SINGLE = 0,
AIC_GPAI_MODE_PERIOD = 1
};
/* Register definition of GPAI Controller */
#define GPAI_MCR 0x000
#define GPAI_INTR 0x004
#define GPAI_CHnCR(n) (0x100 + (((n) & 0x7) << 6) + 0x00)
#define GPAI_CHnINT(n) (0x100 + (((n) & 0x7) << 6) + 0x04)
#define GPAI_CHnPSI(n) (0x100 + (((n) & 0x7) << 6) + 0x08)
#define GPAI_CHnHLAT(n) (0x100 + (((n) & 0x7) << 6) + 0x10)
#define GPAI_CHnLLAT(n) (0x100 + (((n) & 0x7) << 6) + 0x14)
#define GPAI_CHnACR(n) (0x100 + (((n) & 0x7) << 6) + 0x18)
#define GPAI_CHnFCR(n) (0x100 + (((n) & 0x7) << 6) + 0x20)
#define GPAI_CHnDATA(n) (0x100 + (((n) & 0x7) << 6) + 0x24)
#define GPAI_VERSION 0xFFC
#define GPAI_MCR_CH0_EN BIT(8)
#define GPAI_MCR_CH_EN(n) (GPAI_MCR_CH0_EN << (n))
#define GPAI_MCR_EN BIT(0)
#define GPAI_INTR_CH0_INT_FLAG BIT(16)
#define GPAI_INTR_CH_INT_FLAG(n) (GPAI_INTR_CH0_INT_FLAG << (n))
#define GPAI_INTR_CH0_INT_EN BIT(0)
#define GPAI_INTR_CH_INT_EN(n) (GPAI_INTR_CH0_INT_EN << (n))
#define GPAI_CHnCR_SBC_SHIFT 24
#define GPAI_CHnCR_SBC_2_POINTS 1
#define GPAI_CHnCR_SBC_4_POINTS 2
#define GPAI_CHnCR_SBC_8_POINTS 3
#define GPAI_CHnCR_SBC_SHIFT 24
#define GPAI_CHnCR_SBC_MASK GENMASK(25, 24)
#define GPAI_CHnCR_HIGH_ADC_PRIORITY BIT(4)
#define GPAI_CHnCR_PERIOD_SAMPLE_EN BIT(1)
#define GPAI_CHnCR_SINGLE_SAMPLE_EN BIT(0)
#define GPAI_CHnINT_LLA_RM_FLAG BIT(23)
#define GPAI_CHnINT_LLA_VALID_FLAG BIT(22)
#define GPAI_CHnINT_HLA_RM_FLAG BIT(21)
#define GPAI_CHnINT_HLA_VALID_FLAG BIT(20)
#define GPAI_CHnINT_FIFO_ERR_FLAG BIT(17)
#define GPAI_CHnINT_DRDY_FLG BIT(16)
#define GPAI_CHnINT_LLA_RM_IE BIT(7)
#define GPAI_CHnINT_LLA_VALID_IE BIT(6)
#define GPAI_CHnINT_HLA_RM_IE BIT(5)
#define GPAI_CHnINT_HLA_VALID_IE BIT(4)
#define GPAI_CHnINT_FIFO_ERR_IE BIT(1)
#define GPAI_CHnINT_DAT_RDY_IE BIT(0)
#define GPAI_CHnLAT_HLLA_RM_THD_SHIFT 16
#define GPAI_CHnLAT_HLLA_RM_THD_MASK GENMASK(27, 16)
#define GPAI_CHnLAT_HLLA_THD_MASK GENMASK(11, 0)
#define GPAI_CHnLAT_HLA_RM_THD(n) ((n) - 30)
#define GPAI_CHnLAT_LLA_RM_THD(n) ((n) + 30)
#define GPAI_CHnACR_DISCARD_NOR_DAT BIT(6)
#define GPAI_CHnACR_DISCARD_LL_DAT BIT(5)
#define GPAI_CHnACR_DISCARD_HL_DAT BIT(4)
#define GPAI_CHnACR_LLA_EN BIT(1)
#define GPAI_CHnACR_HLA_EN BIT(0)
#define GPAI_CHnFCR_DAT_CNT_MAX(ch) ((ch) > 1 ? 0x8 : 0x40)
#define GPAI_CHnFCR_DAT_CNT_SHIFT 24
#define GPAI_CHnFCR_DAT_CNT_MASK GENMASK(30, 24)
#define GPAI_CHnFCR_UF_STS BIT(18)
#define GPAI_CHnFCR_OF_STS BIT(17)
#define GPAI_CHnFCR_DAT_RDY_THD_SHIFT 8
#define GPAI_CHnFCR_DAT_RDY_THD_MASK GENMASK(15, 8)
#define GPAI_CHnFCR_FLUSH BIT(0)
#define GPAI_INVALID_DATA 0xFFF
#define GPAI_ADC_DATA_MIN 0x0
#define GPAI_ADC_DATA_STEP 0x1
struct aic_gpai_ch {
u32 id;
bool available;
enum aic_gpai_mode mode;
u16 latest_data;
u16 fifo_data[GPAI_FIFO_MAX_DEPTH];
u8 fifo_valid_cnt;
u16 fifo_thd;
u32 smp_period;
bool hla_enable; // high-level alarm
u16 hla_thd;
u16 hla_rm_thd;
bool lla_enable; // low-level alarm
u16 lla_thd;
u16 lla_rm_thd;
struct completion complete;
};
struct aic_gpai_data {
int num_bits;
const struct iio_chan_spec *channels;
int num_channels;
u32 fifo_depth[AIC_GPAI_MAX_CH];
};
struct aic_gpai_dev {
struct platform_device *pdev;
void __iomem *regs;
struct clk *clk;
struct reset_control *rst;
u32 irq;
u32 pclk_rate;
struct aic_gpai_ch chan[AIC_GPAI_MAX_CH];
const struct aic_gpai_data *data;
};
static DEFINE_SPINLOCK(user_lock);
static const int aic_gpai_adc_raw_available[] = {
GPAI_ADC_DATA_MIN, GPAI_ADC_DATA_STEP, GPAI_INVALID_DATA,
};
extern u16 adcim_auto_calibration(u16 *adc_val, struct device *dev);
// TODO: Add the transform algorithm, offered by SD later
static s32 gpai_data2vol(u16 data)
{
return data;
}
static u16 gpai_vol2data(s32 vol)
{
return vol;
}
static u32 gpai_ms2itv(struct aic_gpai_dev *gpai, u32 ms)
{
u32 tmp = 0;
if (of_device_is_compatible(gpai->pdev->dev.of_node,
"artinchip,aic-gpai-v0.1")) {
dev_info(&gpai->pdev->dev, "Use a max period");
/* BUG: Only the low 16bit is valid, so set a max number */
return 0xFFFF;
}
tmp = gpai->pclk_rate / 1000;
tmp *= ms;
return tmp;
}
static void gpai_reg_enable(void __iomem *base, int offset, int bit, int enable)
{
int tmp = readl(base + offset);
if (enable)
tmp |= bit;
else
tmp &= ~bit;
writel(tmp, base + offset);
}
static void gpai_enable(void __iomem *regs, int enable)
{
spin_lock(&user_lock);
gpai_reg_enable(regs, GPAI_MCR, GPAI_MCR_EN, enable);
spin_unlock(&user_lock);
}
static void gpai_ch_enable(void __iomem *regs, u32 ch, int enable)
{
spin_lock(&user_lock);
gpai_reg_enable(regs, GPAI_MCR, GPAI_MCR_CH_EN(ch), enable);
spin_unlock(&user_lock);
}
static void gpai_int_enable(void __iomem *regs, u32 ch, u32 enable, u32 detail)
{
u32 val = 0;
val = readl(regs + GPAI_INTR);
if (enable) {
val |= GPAI_INTR_CH_INT_EN(ch);
writel(detail, regs + GPAI_CHnINT(ch));
} else {
val &= ~GPAI_INTR_CH_INT_EN(ch);
writel(0, regs + GPAI_CHnINT(ch));
}
writel(val, regs + GPAI_INTR);
}
static void gpai_fifo_init(struct aic_gpai_dev *gpai, u32 ch)
{
u32 val = 0;
val = 1 << GPAI_CHnFCR_DAT_RDY_THD_SHIFT;
writel(val, gpai->regs + GPAI_CHnFCR(ch));
}
static void gpai_fifo_flush(struct aic_gpai_dev *gpai, u32 ch)
{
struct device *dev = &gpai->pdev->dev;
void __iomem *regs = gpai->regs;
u32 val = readl(regs + GPAI_CHnFCR(ch));
if (val & GPAI_CHnFCR_UF_STS)
dev_err(dev, "ch%d FIFO is Underflow!%#x\n", ch, val);
if (val & GPAI_CHnFCR_OF_STS)
dev_err(dev, "ch%d FIFO is Overflow!%#x\n", ch, val);
writel(val | GPAI_CHnFCR_FLUSH, regs + GPAI_CHnFCR(ch));
}
static void gpai_single_mode(void __iomem *regs, u32 ch)
{
u32 val = 0;
spin_lock(&user_lock);
val = readl(regs + GPAI_CHnCR(ch));
val |= GPAI_CHnCR_SBC_8_POINTS << GPAI_CHnCR_SBC_SHIFT
| GPAI_CHnCR_SINGLE_SAMPLE_EN;
writel(val, regs + GPAI_CHnCR(ch));
gpai_int_enable(regs, ch, 1,
GPAI_CHnINT_DAT_RDY_IE | GPAI_CHnINT_FIFO_ERR_IE);
spin_unlock(&user_lock);
}
/* Only in period mode, HLA and LLA are available */
static void gpai_period_mode(struct aic_gpai_dev *gpai, u32 ch)
{
u32 val, acr = 0;
u32 detail = GPAI_CHnINT_DAT_RDY_IE | GPAI_CHnINT_FIFO_ERR_IE;
void __iomem *regs = gpai->regs;
struct aic_gpai_ch *chan = &gpai->chan[ch];
spin_lock(&user_lock);
if (chan->hla_enable) {
detail |= GPAI_CHnINT_HLA_RM_IE | GPAI_CHnINT_HLA_VALID_IE;
val = ((chan->hla_rm_thd << GPAI_CHnLAT_HLLA_RM_THD_SHIFT)
& GPAI_CHnLAT_HLLA_RM_THD_MASK)
| (chan->hla_thd & GPAI_CHnLAT_HLLA_THD_MASK);
writel(val, regs + GPAI_CHnHLAT(ch));
acr |= GPAI_CHnACR_HLA_EN;
}
if (chan->lla_enable) {
detail |= GPAI_CHnINT_LLA_VALID_IE | GPAI_CHnINT_LLA_RM_IE;
val = ((chan->lla_rm_thd << GPAI_CHnLAT_HLLA_RM_THD_SHIFT)
& GPAI_CHnLAT_HLLA_RM_THD_MASK)
| (chan->lla_thd & GPAI_CHnLAT_HLLA_THD_MASK);
writel(val, regs + GPAI_CHnLLAT(ch));
acr |= GPAI_CHnACR_LLA_EN;
}
gpai_int_enable(regs, ch, 1, detail);
writel(acr, regs + GPAI_CHnACR(ch));
writel(chan->smp_period, regs + GPAI_CHnPSI(ch));
val = readl(regs + GPAI_CHnCR(ch));
val |= GPAI_CHnCR_SBC_8_POINTS << GPAI_CHnCR_SBC_SHIFT
| GPAI_CHnCR_PERIOD_SAMPLE_EN;
writel(val, regs + GPAI_CHnCR(ch));
spin_unlock(&user_lock);
}
static int gpai_ch_init(struct aic_gpai_dev *gpai, u32 ch)
{
struct aic_gpai_ch *chan = &gpai->chan[ch];
init_completion(&chan->complete);
gpai_ch_enable(gpai->regs, ch, 1);
gpai_fifo_init(gpai, ch);
if (chan->mode == AIC_GPAI_MODE_PERIOD)
gpai_period_mode(gpai, ch);
/* For single mode, should init the channel in .read_raw() */
return 0;
}
static int aic_gpai_read_dat(struct aic_gpai_dev *gpai, u32 ch)
{
u32 i;
void __iomem *regs = gpai->regs;
struct device *dev = &gpai->pdev->dev;
struct aic_gpai_ch *chan = &gpai->chan[ch];
u32 cnt = (readl(regs + GPAI_CHnFCR(ch)) & GPAI_CHnFCR_DAT_CNT_MASK)
>> GPAI_CHnFCR_DAT_CNT_SHIFT;
chan->latest_data = 0;
if (unlikely(cnt == 0 || cnt > GPAI_CHnFCR_DAT_CNT_MAX(ch))) {
dev_err(dev, "ch%d invalid data count %d\n", ch, cnt);
return -1;
}
for (i = 0; i < cnt; i++) {
chan->fifo_data[i] = readl(regs + GPAI_CHnDATA(ch));
adcim_auto_calibration(&chan->fifo_data[i], dev);
chan->latest_data += chan->fifo_data[i];
}
chan->fifo_valid_cnt = cnt;
chan->latest_data /= cnt;
dev_dbg(dev, "There are %d data ready in ch%d, last %d\n", cnt,
ch, chan->latest_data);
return 0;
}
static int aic_gpai_read_raw(struct iio_dev *iodev,
struct iio_chan_spec const *chan,
int *val, int *val2, long mask)
{
struct aic_gpai_dev *gpai = iio_priv(iodev);
struct device *dev = &gpai->pdev->dev;
struct aic_gpai_ch *gpai_ch = NULL;
u32 ch = chan->channel;
if (unlikely(chan->channel < 0 || chan->channel >= AIC_GPAI_MAX_CH)) {
dev_err(dev, "Invalid channel No.%d", chan->channel);
return -ENODEV;
}
gpai_ch = &gpai->chan[ch];
if (!gpai_ch->available) {
dev_warn(dev, "Channel %d is unavailable", ch);
return -ENODEV;
}
dev_dbg(&gpai->pdev->dev, "ch %d, mask %#lx\n", ch, mask);
switch (mask) {
case IIO_CHAN_INFO_RAW:
#ifndef CONFIG_ARTINCHIP_ADCIM_DM
if (gpai_ch->mode == AIC_GPAI_MODE_PERIOD) {
*val = gpai_data2vol(gpai_ch->latest_data);
return IIO_VAL_INT;
}
#endif
spin_lock(&user_lock);
reinit_completion(&gpai_ch->complete);
spin_unlock(&user_lock);
gpai_ch_enable(gpai->regs, ch, 1);
gpai_single_mode(gpai->regs, ch);
if (!wait_for_completion_timeout(&gpai_ch->complete,
AIC_GPAI_TIMEOUT)) {
dev_err(dev, "Ch%d read timeout!\n", ch);
gpai_ch_enable(gpai->regs, ch, 0);
return -ETIMEDOUT;
}
gpai_ch_enable(gpai->regs, ch, 0);
*val = gpai_data2vol(gpai_ch->latest_data);
return IIO_VAL_INT;
case IIO_CHAN_INFO_SCALE:
default:
return -EINVAL;
}
}
static const struct iio_event_spec aic_gpai_event[] = {
{ .type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_RISING,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_ENABLE),
},
{
.type = IIO_EV_TYPE_THRESH,
.dir = IIO_EV_DIR_FALLING,
.mask_separate = BIT(IIO_EV_INFO_VALUE) |
BIT(IIO_EV_INFO_ENABLE),
}
};
static irqreturn_t aic_gpai_isr(int irq, void *dev_id)
{
struct aic_gpai_dev *gpai = dev_id;
struct device *dev = &gpai->pdev->dev;
struct iio_dev *iodev = dev_get_drvdata(dev);
void __iomem *regs = gpai->regs;
u32 ch_flag = 0, ch_int = 0;
int i;
struct aic_gpai_ch *chan = NULL;
unsigned long flags;
spin_lock_irqsave(&user_lock, flags);
ch_flag = readl(regs + GPAI_INTR);
for (i = 0; i < AIC_GPAI_MAX_CH; i++) {
if (!(ch_flag & GPAI_INTR_CH_INT_FLAG(i)))
continue;
chan = &gpai->chan[i];
ch_int = readl(regs + GPAI_CHnINT(i));
writel(ch_int, regs + GPAI_CHnINT(i));
if (ch_int & GPAI_CHnINT_DRDY_FLG) {
aic_gpai_read_dat(gpai, i);
if (chan->mode == AIC_GPAI_MODE_SINGLE)
complete(&chan->complete);
if (chan->latest_data == GPAI_INVALID_DATA)
continue;
}
if (ch_int & GPAI_CHnINT_LLA_VALID_FLAG) {
iio_push_event(iodev,
IIO_UNMOD_EVENT_CODE(IIO_ANGL, i,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_FALLING),
iio_get_time_ns(iodev));
dev_warn(dev, "LLA: ch%d %d!\n", i, chan->latest_data);
}
if (ch_int & GPAI_CHnINT_LLA_RM_FLAG) {
dev_warn(dev, "LLA removed: ch%d %d\n", i,
chan->latest_data);
}
if (ch_int & GPAI_CHnINT_HLA_VALID_FLAG) {
iio_push_event(iodev,
IIO_UNMOD_EVENT_CODE(IIO_ANGL, i,
IIO_EV_TYPE_THRESH,
IIO_EV_DIR_RISING),
iio_get_time_ns(iodev));
dev_warn(dev, "HLA: ch%d %d!\n", i, chan->latest_data);
}
if (ch_int & GPAI_CHnINT_HLA_RM_FLAG) {
dev_warn(dev, "HLA removed: ch%d %d\n", i,
chan->latest_data);
}
if (ch_int & GPAI_CHnINT_FIFO_ERR_FLAG)
gpai_fifo_flush(gpai, i);
}
dev_dbg(dev, "IRQ flag %#x, detail %#x\n", ch_flag, ch_int);
spin_unlock_irqrestore(&user_lock, flags);
return IRQ_HANDLED;
}
static int aic_gpai_read_avail(struct iio_dev *iodev,
struct iio_chan_spec const *chan,
const int **vals, int *type, int *length,
long mask)
{
switch (mask) {
case IIO_CHAN_INFO_RAW:
*vals = aic_gpai_adc_raw_available;
*type = IIO_VAL_INT;
return IIO_AVAIL_RANGE;
default:
return -EINVAL;
}
}
static const struct iio_info aic_gpai_iio_info = {
.read_raw = aic_gpai_read_raw,
.read_avail = aic_gpai_read_avail,
};
#define GPAI_CHANNEL(_index, _id) { \
.type = IIO_VOLTAGE, \
.indexed = 1, \
.channel = _index, \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_shared_by_type = BIT(IIO_CHAN_INFO_SCALE), \
.info_mask_separate = BIT(IIO_CHAN_INFO_RAW), \
.info_mask_separate_available = BIT(IIO_CHAN_INFO_RAW), \
.datasheet_name = _id, \
}
static const struct iio_chan_spec aic_gpai_v1_iio_channels[] = {
GPAI_CHANNEL(0, "adc0"),
GPAI_CHANNEL(1, "adc1"),
GPAI_CHANNEL(2, "adc2"),
GPAI_CHANNEL(3, "adc3"),
GPAI_CHANNEL(4, "adc4"),
GPAI_CHANNEL(5, "adc5"),
GPAI_CHANNEL(6, "adc6"),
GPAI_CHANNEL(7, "adc7"),
{
.event_spec = aic_gpai_event,
.num_event_specs = ARRAY_SIZE(aic_gpai_event),
}
};
static const struct aic_gpai_data aic_gpai_v1_data = {
.num_bits = 12,
.channels = aic_gpai_v1_iio_channels,
.num_channels = ARRAY_SIZE(aic_gpai_v1_iio_channels),
.fifo_depth = {64, 64, 8, 8, 8, 8, 8, 8},
};
static const struct of_device_id aic_gpai_match[] = {
{
.compatible = "artinchip,aic-gpai-v0.1",
.data = &aic_gpai_v1_data,
},
{
.compatible = "artinchip,aic-gpai-v1.0",
.data = &aic_gpai_v1_data,
},
{},
};
MODULE_DEVICE_TABLE(of, aic_gpai_match);
#ifdef CONFIG_PM_SLEEP
static int aic_gpai_suspend(struct device *dev)
{
struct iio_dev *iodev = dev_get_drvdata(dev);
struct aic_gpai_dev *gpai = iio_priv(iodev);
gpai_enable(gpai->regs, 0);
reset_control_assert(gpai->rst);
clk_disable_unprepare(gpai->clk);
return 0;
}
static int aic_gpai_resume(struct device *dev)
{
struct iio_dev *iodev = dev_get_drvdata(dev);
struct aic_gpai_dev *gpai = iio_priv(iodev);
int ret;
ret = clk_prepare_enable(gpai->clk);
if (ret)
return ret;
ret = reset_control_deassert(gpai->rst);
if (ret)
return ret;
gpai_enable(gpai->regs, 1);
return ret;
}
#endif
static SIMPLE_DEV_PM_OPS(aic_gpai_pm_ops,
aic_gpai_suspend, aic_gpai_resume);
static int gpai_parse_dt(struct aic_gpai_dev *gpai)
{
struct device *dev = &gpai->pdev->dev;
struct device_node *child, *np = dev->of_node;
s32 ret = 0, hla = 0;
u32 val = 0, i = 0;
for_each_child_of_node(np, child) {
struct aic_gpai_ch *chan = &gpai->chan[i];
chan->id = i;
chan->available = of_device_is_available(child);
if (!chan->available) {
dev_dbg(dev, "ch%d is unavailable.\n", i);
i++;
continue;
}
dev_dbg(dev, "ch%d is available\n", i);
ret = of_property_read_u32(child, "aic,sample-period-ms", &val);
if (ret || val == 0) {
dev_info(dev, "ch%d is single sample mode\n", i);
chan->mode = AIC_GPAI_MODE_SINGLE;
i++;
continue;
}
chan->mode = AIC_GPAI_MODE_PERIOD;
chan->smp_period = gpai_ms2itv(gpai, val);
ret = of_property_read_u32(child, "aic,high-level-thd", &hla);
if (ret || hla == 0) {
dev_warn(dev, "Invalid ch%d HLA thd, return %d\n",
i, ret ? ret : hla);
chan->hla_enable = false;
} else {
chan->hla_enable = true;
chan->hla_thd = gpai_vol2data(hla);
chan->hla_rm_thd =
gpai_vol2data(GPAI_CHnLAT_HLA_RM_THD(hla));
}
ret = of_property_read_u32(child, "aic,low-level-thd", &val);
if (ret || val == 0 || val >= hla) {
dev_warn(dev, "Invalid ch%d LLA thd, return %d\n",
i, ret ? ret : val);
chan->lla_enable = false;
} else {
chan->lla_enable = true;
chan->lla_thd = gpai_vol2data(val);
chan->lla_rm_thd =
gpai_vol2data(GPAI_CHnLAT_LLA_RM_THD(val));
}
chan->fifo_thd = 1;
i++;
}
return 0;
}
static int aic_gpai_probe(struct platform_device *pdev)
{
struct aic_gpai_dev *gpai = NULL;
struct iio_dev *iodev = NULL;
const struct of_device_id *match;
struct clk *clk;
int ret, irq, i;
if (!pdev->dev.of_node)
return -ENODEV;
iodev = devm_iio_device_alloc(&pdev->dev, sizeof(*gpai));
if (!iodev)
return -ENOMEM;
gpai = iio_priv(iodev);
match = of_match_device(aic_gpai_match, &pdev->dev);
if (!match) {
dev_err(&pdev->dev, "failed to match device\n");
return -ENODEV;
}
gpai->data = match->data;
gpai->regs = devm_platform_ioremap_resource(pdev, 0);
if (IS_ERR(gpai->regs))
return PTR_ERR(gpai->regs);
clk = devm_clk_get(&pdev->dev, "pclk");
if (IS_ERR(clk)) {
dev_err(&pdev->dev, "Failed to get pclk\n");
return PTR_ERR(clk);
}
gpai->pclk_rate = clk_get_rate(clk);
dev_dbg(&pdev->dev, "PCLK rate %d\n", gpai->pclk_rate);
gpai->clk = devm_clk_get(&pdev->dev, "gpai");
if (IS_ERR(gpai->clk)) {
dev_err(&pdev->dev, "no clock defined\n");
return PTR_ERR(gpai->clk);
}
ret = clk_prepare_enable(gpai->clk);
if (ret) {
dev_err(&pdev->dev, "Failed to enable clk, return %d\n", ret);
return ret;
}
gpai->rst = devm_reset_control_get_optional_shared(&pdev->dev, NULL);
if (IS_ERR(gpai->rst)) {
ret = PTR_ERR(gpai->rst);
goto disable_clk;
}
reset_control_deassert(gpai->rst);
irq = platform_get_irq(pdev, 0);
if (irq < 0) {
dev_err(&pdev->dev, "Failed to get irq\n");
goto disable_rst;
}
ret = devm_request_irq(&pdev->dev, irq, aic_gpai_isr,
0, AIC_GPAI_NAME, gpai);
if (ret < 0) {
dev_err(&pdev->dev, "Failed to request IRQ %d\n", irq);
goto disable_rst;
}
gpai->irq = irq;
gpai->pdev = pdev;
gpai_enable(gpai->regs, 1);
platform_set_drvdata(pdev, iodev);
gpai_parse_dt(gpai);
iodev->name = AIC_GPAI_NAME;
iodev->dev.parent = &pdev->dev;
iodev->dev.of_node = pdev->dev.of_node;
iodev->info = &aic_gpai_iio_info;
iodev->modes = INDIO_DIRECT_MODE;
iodev->channels = gpai->data->channels;
iodev->num_channels = gpai->data->num_channels;
ret = iio_device_register(iodev);
if (ret)
goto free_irq;
for (i = 0; i < AIC_GPAI_MAX_CH; i++) {
if (gpai->chan[i].available)
gpai_ch_init(gpai, i);
}
dev_info(&pdev->dev, "Artinchip GPAI Loaded.\n");
return 0;
free_irq:
free_irq(gpai->irq, gpai);
disable_rst:
reset_control_assert(gpai->rst);
disable_clk:
clk_disable_unprepare(gpai->clk);
return ret;
}
static int aic_gpai_remove(struct platform_device *pdev)
{
struct iio_dev *iodev = platform_get_drvdata(pdev);
struct aic_gpai_dev *gpai = iio_priv(iodev);
iio_device_unregister(iodev);
gpai_enable(gpai->regs, 0);
free_irq(gpai->irq, gpai);
reset_control_assert(gpai->rst);
clk_disable_unprepare(gpai->clk);
return 0;
}
static struct platform_driver aic_gpai_driver = {
.probe = aic_gpai_probe,
.remove = aic_gpai_remove,
.driver = {
.name = AIC_GPAI_NAME,
.of_match_table = aic_gpai_match,
.pm = &aic_gpai_pm_ops,
},
};
module_platform_driver(aic_gpai_driver);
MODULE_AUTHOR("Matteo <duanmt@artinchip.com>");
MODULE_DESCRIPTION("GPAI driver of Artinchip SoC");
MODULE_LICENSE("GPL");