| // SPDX-License-Identifier: GPL-2.0 |
| /* Copyright (C) 2019 Synaptics Incorporated |
| * |
| * Author: Benson Gui <Benson.Gui@synaptics.com> |
| * |
| */ |
| |
| #include <linux/module.h> |
| #include <linux/of_address.h> |
| #include <linux/of_device.h> |
| #include <linux/sysfs.h> |
| |
| #define AS370_AXI_CNT_NUM 3 |
| |
| #define RA_MC6Ctrl_AxiPCntCTRL 0x001C |
| #define RA_MC6Ctrl_AxiMst0 0x0020 |
| #define RA_MC6Ctrl_AxiMst0DXBAR 0x0024 |
| #define RA_MC6Ctrl_AxiMst1 0x0028 |
| |
| #define RA_MC6Ctrl_Mstr0PCnt 0x002C |
| #define RA_MC6Ctrl_Mstr0DXBARPCnt 0x0060 |
| |
| #define RA_AxiPCntStat_OF_STATUS 0x002C |
| #define RA_AxiPCntStat_TOTAL_CNT 0x0000 |
| #define RA_AxiPCntStat_ARWAIT_CNT 0x0004 |
| #define RA_AxiPCntStat_RWAIT_CNT 0x0008 |
| #define RA_AxiPCntStat_RIDLE_CNT 0x000C |
| #define RA_AxiPCntStat_RDATA_CNT 0x0010 |
| #define RA_AxiPCntStat_AWWAIT_CNT 0x0014 |
| #define RA_AxiPCntStat_WWAIT_CNT 0x0018 |
| #define RA_AxiPCntStat_WIDLE_CNT 0x001C |
| #define RA_AxiPCntStat_WDATA_CNT 0x0020 |
| #define RA_AxiPCntStat_AWDATA_CNT 0x0024 |
| #define RA_AxiPCntStat_ARDATA_CNT 0x0028 |
| |
| struct axi_perf_counter { |
| unsigned int total; |
| |
| unsigned int arwait; |
| unsigned int rwait; |
| unsigned int rdata; |
| unsigned int ardata; |
| |
| unsigned int awwait; |
| unsigned int wwait; |
| unsigned int wdata; |
| unsigned int awdata; |
| |
| unsigned int status; |
| }; |
| |
| #define CTRL_CLR_SHIFT 0 |
| #define CTRL_EN_SHIFT 8 |
| #define CTRL_LATCH_SHIFT 16 |
| #define CTRL_CLR_MASK (0xf << CTRL_CLR_SHIFT) |
| #define CTRL_EN_MASK (0xf << CTRL_EN_SHIFT) |
| #define CTRL_LATCH_MASK (0xf << CTRL_LATCH_SHIFT) |
| |
| #define OVERFLOW_TOTAL BIT(0) |
| #define OVERFLOW_ARWAIT BIT(1) |
| #define OVERFLOW_RWAIT BIT(2) |
| #define OVERFLOW_RIDLE BIT(3) |
| #define OVERFLOW_RDATA BIT(4) |
| #define OVERFLOW_AWWAIT BIT(5) |
| #define OVERFLOW_WWAIT BIT(6) |
| #define OVERFLOW_WIDLE BIT(7) |
| #define OVERFLOW_WDATA BIT(8) |
| #define OVERFLOW_AWDATA BIT(9) |
| #define OVERFLOW_ARDATA BIT(10) |
| |
| static const struct |
| { |
| const char* name; |
| unsigned int mask_id; |
| } as370_mask_table[] = |
| { |
| {"*PORT0" , (0x0000 << 16) + 0x0000, }, // 0 |
| {"CPU" , (0x0001 << 16) + 0x0001, }, |
| {"DXBAR" , (0x0001 << 16) + 0x0000, }, |
| {"USB2" , (0x003f << 16) + 0x0020, }, |
| {"SDIO" , (0x003f << 16) + 0x0028, }, // 4 |
| {"NAND" , (0x003f << 16) + 0x0000, }, |
| {"PCIE" , (0x002f << 16) + 0x0008, }, |
| {"PCIE_M1" , (0x003f << 16) + 0x0018, }, |
| {"PCIE_M0" , (0x003f << 16) + 0x0008, }, // 8 |
| {"EMMC" , (0x003f << 16) + 0x0010, }, |
| {"PB" , (0x003f << 16) + 0x0030, }, |
| {"BUSMON" , (0xffff << 16) + 0x0004, }, |
| {"BCM" , (0x003f << 16) + 0x0016, }, // 12 |
| {"BCMCPU" , (0xffff << 16) + 0x002e, }, |
| {"BCMDIR" , (0xffff << 16) + 0x000e, }, |
| {"AIO" , (0x0007 << 16) + 0x0002, }, |
| {"*PORT1" , (0x0000 << 16) + 0x0000, }, // 16 |
| {"NNA" , (0x0001 << 16) + 0x0001, }, |
| {"IMTEST" , (0x0001 << 16) + 0x0000, }, |
| }; |
| |
| #define CTL_CH0_BIT BIT(0) |
| #define CTL_CH1_BIT BIT(7) |
| #define CTL_CH2_BIT BIT(1) |
| #define CTL_ALL_CH_BITS (CTL_CH0_BIT | CTL_CH1_BIT | CTL_CH2_BIT) |
| |
| static unsigned int as370_axi_cnt_ctrl_bit[AS370_AXI_CNT_NUM] = |
| { |
| CTL_CH0_BIT, |
| CTL_CH1_BIT, |
| CTL_CH2_BIT, |
| }; |
| |
| /* default axi counter mask id*/ |
| static unsigned int as370_axi_default_id[AS370_AXI_CNT_NUM] = |
| { |
| 1, /* CPU */ |
| 2, /* DXBAR */ |
| 16, /* PORT1 */ |
| }; |
| |
| static const unsigned int as370_axi_id_range[AS370_AXI_CNT_NUM + 1] = |
| { |
| 0, /* *PORT0 */ |
| 2, /* DXBAR */ |
| 16, /* *PORT1 */ |
| 19, /* IMTEST + 1 */ |
| }; |
| |
| static unsigned int as370_axi_cnt_mst_offset[AS370_AXI_CNT_NUM] = |
| { |
| RA_MC6Ctrl_AxiMst0, |
| RA_MC6Ctrl_AxiMst0DXBAR, |
| RA_MC6Ctrl_AxiMst1, |
| }; |
| |
| struct axi_meter_priv { |
| struct platform_device *pdev; |
| const char *dev_name; |
| void __iomem *base; |
| spinlock_t lock; |
| struct axi_perf_counter perf_cnts[AS370_AXI_CNT_NUM]; |
| unsigned int cnt_ids[AS370_AXI_CNT_NUM]; |
| bool cnt_en[AS370_AXI_CNT_NUM]; |
| /* axi_meter sysfs interface */ |
| struct kobject kobj; |
| }; |
| |
| static int as370_axi_clear_perf_cnt(struct axi_meter_priv *axi_meter, int cnt_idx) |
| { |
| u32 ctrl; |
| |
| if (cnt_idx >= AS370_AXI_CNT_NUM) |
| return -EINVAL; |
| |
| spin_lock(&axi_meter->lock); |
| ctrl = readl(axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| /* set clr bit, latch bit -> 0*/ |
| ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT); |
| ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_CLR_SHIFT); |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| /* clear clr bit, latch bit 0 -> 1*/ |
| ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_CLR_SHIFT); |
| ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT); |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| spin_unlock(&axi_meter->lock); |
| |
| return 0; |
| } |
| |
| static int as370_axi_enable_perf_cnt(struct axi_meter_priv *axi_meter, int cnt_idx, int en) |
| { |
| u32 ctrl; |
| if (cnt_idx >= AS370_AXI_CNT_NUM) |
| return -EINVAL; |
| |
| spin_lock(&axi_meter->lock); |
| ctrl = readl(axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| if (en) { |
| axi_meter->cnt_en[cnt_idx] = 1; |
| ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_EN_SHIFT); |
| } else { |
| axi_meter->cnt_en[cnt_idx] = 0; |
| ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_EN_SHIFT); |
| } |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| spin_unlock(&axi_meter->lock); |
| |
| return 0; |
| } |
| |
| static int as370_axi_config_mask(struct axi_meter_priv *axi_meter, unsigned int cnt_idx, unsigned int id) |
| { |
| unsigned int mask; |
| |
| if (cnt_idx >= AS370_AXI_CNT_NUM || (id >= ARRAY_SIZE(as370_mask_table))) |
| return -EINVAL; |
| |
| if ((id < as370_axi_id_range[cnt_idx]) || (id >= as370_axi_id_range[cnt_idx + 1])) |
| return -EINVAL; |
| |
| axi_meter->cnt_ids[cnt_idx] = id; |
| |
| mask = as370_mask_table[id].mask_id; |
| /*disable the counter*/ |
| as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 0); |
| as370_axi_clear_perf_cnt(axi_meter, cnt_idx); |
| /*update the mask*/ |
| writel(mask, axi_meter->base + as370_axi_cnt_mst_offset[cnt_idx]); |
| /*enable the counter*/ |
| as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 1); |
| |
| return 0; |
| } |
| |
| static int as370_axi_latch_perf_cnt(struct axi_meter_priv *axi_meter, int cnt_idx) |
| { |
| u32 ctrl; |
| |
| if (cnt_idx >= AS370_AXI_CNT_NUM) |
| return -EINVAL; |
| |
| spin_lock(&axi_meter->lock); |
| ctrl = readl(axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| ctrl &= ~(as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT); |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| ctrl |= (as370_axi_cnt_ctrl_bit[cnt_idx] << CTRL_LATCH_SHIFT); |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| spin_unlock(&axi_meter->lock); |
| |
| return 0; |
| } |
| |
| static void as370_axi_init_perf_cnt(struct axi_meter_priv *axi_meter) |
| { |
| unsigned int mask; |
| int i; |
| u32 ctrl; |
| |
| for (i = 0; i < AS370_AXI_CNT_NUM; i++) { |
| axi_meter->cnt_ids[i] = as370_axi_default_id[i]; |
| mask = as370_mask_table[axi_meter->cnt_ids[i]].mask_id; |
| writel(mask, axi_meter->base + as370_axi_cnt_mst_offset[i]); |
| } |
| |
| /* clear all counters */ |
| ctrl = CTL_ALL_CH_BITS << CTRL_CLR_SHIFT; |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| |
| /* latch zero and disable all counters */ |
| ctrl = CTL_ALL_CH_BITS << CTRL_LATCH_SHIFT; |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| } |
| |
| static void as370_axi_stop_perf_cnt(struct axi_meter_priv *axi_meter) |
| { |
| u32 ctrl; |
| |
| spin_lock(&axi_meter->lock); |
| |
| /* clear all counters */ |
| ctrl = CTL_ALL_CH_BITS << CTRL_CLR_SHIFT; |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| |
| /* latch zero and disable all counters */ |
| ctrl = CTL_ALL_CH_BITS << CTRL_LATCH_SHIFT; |
| writel(ctrl, axi_meter->base + RA_MC6Ctrl_AxiPCntCTRL); |
| |
| spin_unlock(&axi_meter->lock); |
| } |
| |
| static int as370_axi_update_perf_cnt(struct axi_meter_priv *axi_meter, unsigned int cnt_idx) |
| { |
| unsigned int group_offset; |
| struct axi_perf_counter *axi_perf_cnt; |
| |
| if (cnt_idx >= AS370_AXI_CNT_NUM) |
| return -EINVAL; |
| |
| as370_axi_latch_perf_cnt(axi_meter, cnt_idx); |
| |
| axi_perf_cnt = &axi_meter->perf_cnts[cnt_idx]; |
| group_offset = RA_MC6Ctrl_Mstr0PCnt + cnt_idx*(RA_MC6Ctrl_Mstr0DXBARPCnt - RA_MC6Ctrl_Mstr0PCnt); |
| spin_lock(&axi_meter->lock); |
| axi_perf_cnt->status = readl(axi_meter->base + group_offset + RA_AxiPCntStat_OF_STATUS); |
| axi_perf_cnt->total = readl(axi_meter->base + group_offset + RA_AxiPCntStat_TOTAL_CNT); |
| axi_perf_cnt->rdata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_RDATA_CNT); |
| axi_perf_cnt->ardata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_ARDATA_CNT); |
| axi_perf_cnt->wdata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_WDATA_CNT); |
| axi_perf_cnt->awdata = readl(axi_meter->base + group_offset + RA_AxiPCntStat_AWDATA_CNT); |
| spin_unlock(&axi_meter->lock); |
| |
| return 0; |
| } |
| |
| struct axi_meter_sysfs_attr { |
| struct attribute attr; |
| ssize_t (*show)(struct axi_meter_priv *, char *); |
| ssize_t (*store)(struct axi_meter_priv *, const char *, size_t count); |
| }; |
| |
| #define AM_ATTR_RO(_name) \ |
| struct axi_meter_sysfs_attr axi_meter_attr_##_name = \ |
| __ATTR(_name, S_IRUGO, axi_meter_attr_##_name##_show, NULL) |
| |
| #define AM_ATTR_RW(_name) \ |
| struct axi_meter_sysfs_attr axi_meter_attr_##_name = \ |
| __ATTR(_name, S_IRUGO | S_IWUSR, axi_meter_attr_##_name##_show, axi_meter_attr_##_name##_store) |
| |
| static ssize_t axi_meter_attr_show(struct kobject *kobj, struct attribute *attr, |
| char *buf) |
| { |
| struct axi_meter_priv *axi_meter; |
| struct axi_meter_sysfs_attr *axi_meter_attr; |
| ssize_t ret; |
| |
| axi_meter_attr = container_of(attr, struct axi_meter_sysfs_attr, attr); |
| if (!axi_meter_attr->show) |
| return -EIO; |
| |
| axi_meter = container_of(kobj, struct axi_meter_priv, kobj); |
| ret = axi_meter_attr->show(axi_meter, buf); |
| return ret; |
| } |
| |
| static ssize_t axi_meter_attr_store(struct kobject *kobj, struct attribute *attr, |
| const char *buf, size_t count) |
| { |
| struct axi_meter_priv *axi_meter; |
| struct axi_meter_sysfs_attr *axi_meter_attr; |
| ssize_t ret; |
| |
| axi_meter_attr = container_of(attr, struct axi_meter_sysfs_attr, attr); |
| if (!axi_meter_attr->store) |
| return -EIO; |
| |
| axi_meter = container_of(kobj, struct axi_meter_priv, kobj); |
| ret = axi_meter_attr->store(axi_meter, buf, count); |
| |
| return ret; |
| } |
| |
| static ssize_t axi_meter_attr_cnt0_show(struct axi_meter_priv *axi_meter, char *buf) |
| { |
| int len; |
| u32 overflow; |
| struct axi_perf_counter *perf_cnt; |
| |
| as370_axi_update_perf_cnt(axi_meter, 0); |
| |
| perf_cnt = &axi_meter->perf_cnts[0]; |
| overflow = perf_cnt->status; |
| len = sprintf(buf, "%s: tot 0x%08x, AR: 0x%08x%s, AW: 0x%08x%s\n", |
| as370_mask_table[axi_meter->cnt_ids[0]].name, |
| perf_cnt->total, |
| perf_cnt->ardata, (overflow & OVERFLOW_ARDATA) ? " overflow" : "", |
| perf_cnt->awdata, (overflow & OVERFLOW_AWDATA) ? " overflow" : ""); |
| |
| return len; |
| } |
| |
| static ssize_t axi_meter_attr_cnt1_show(struct axi_meter_priv *axi_meter, char *buf) |
| { |
| int len; |
| u32 overflow; |
| struct axi_perf_counter *perf_cnt; |
| |
| as370_axi_update_perf_cnt(axi_meter, 1); |
| |
| perf_cnt = &axi_meter->perf_cnts[1]; |
| overflow = perf_cnt->status; |
| len = sprintf(buf, "%s: tot 0x%08x, AR: 0x%08x%s, AW: 0x%08x%s\n", |
| as370_mask_table[axi_meter->cnt_ids[1]].name, |
| perf_cnt->total, |
| perf_cnt->ardata, (overflow & OVERFLOW_ARDATA) ? " overflow" : "", |
| perf_cnt->awdata, (overflow & OVERFLOW_AWDATA) ? " overflow" : ""); |
| |
| return len; |
| } |
| |
| static ssize_t axi_meter_attr_cnt2_show(struct axi_meter_priv *axi_meter, char *buf) |
| { |
| int len; |
| u32 overflow; |
| struct axi_perf_counter *perf_cnt; |
| |
| as370_axi_update_perf_cnt(axi_meter, 2); |
| |
| perf_cnt = &axi_meter->perf_cnts[2]; |
| overflow = perf_cnt->status; |
| len = sprintf(buf, "%s: tot 0x%08x, AR: 0x%08x%s, AW: 0x%08x%s\n", |
| as370_mask_table[axi_meter->cnt_ids[2]].name, |
| perf_cnt->total, |
| perf_cnt->ardata, (overflow & OVERFLOW_ARDATA) ? " overflow" : "", |
| perf_cnt->awdata, (overflow & OVERFLOW_AWDATA) ? " overflow" : ""); |
| |
| return len; |
| } |
| |
| static ssize_t axi_meter_attr_enable_show(struct axi_meter_priv *axi_meter, char *buf) |
| { |
| return sprintf(buf, "cnt0 : %d\ncnt1 : %d\ncnt2 : %d\n", |
| axi_meter->cnt_en[0], axi_meter->cnt_en[1], axi_meter->cnt_en[2]); |
| } |
| |
| static ssize_t axi_meter_attr_enable_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count) |
| { |
| unsigned int cnt_idx; |
| cnt_idx = simple_strtoul(buf, NULL, 0); |
| as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 1); |
| return count; |
| } |
| static ssize_t axi_meter_attr_disable_show(struct axi_meter_priv *axi_meter, char *buf) |
| { |
| return 0; |
| } |
| |
| static ssize_t axi_meter_attr_disable_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count) |
| { |
| unsigned int cnt_idx; |
| cnt_idx = simple_strtoul(buf, NULL, 0); |
| as370_axi_enable_perf_cnt(axi_meter, cnt_idx, 0); |
| return count; |
| } |
| |
| static ssize_t axi_meter_attr_clear_show(struct axi_meter_priv *axi_meter, char *buf) |
| { |
| return 0; |
| } |
| |
| static ssize_t axi_meter_attr_clear_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count) |
| { |
| unsigned int cnt_idx; |
| cnt_idx = simple_strtoul(buf, NULL, 0); |
| as370_axi_clear_perf_cnt(axi_meter, cnt_idx); |
| return count; |
| } |
| |
| static ssize_t axi_meter_attr_mask_show(struct axi_meter_priv *axi_meter, char *buf) |
| { |
| return sprintf(buf, "cnt0 : %s\ncnt1 : %s\ncnt2 : %s\n", |
| as370_mask_table[axi_meter->cnt_ids[0]].name, |
| as370_mask_table[axi_meter->cnt_ids[1]].name, |
| as370_mask_table[axi_meter->cnt_ids[2]].name); |
| } |
| |
| static ssize_t axi_meter_attr_mask_store(struct axi_meter_priv *axi_meter, const char *buf, size_t count) |
| { |
| unsigned int cnt_idx, mask_id; |
| |
| if (sscanf(buf, "%d %d", &cnt_idx, &mask_id) < 0) { |
| return -1; |
| } |
| |
| if (as370_axi_config_mask(axi_meter, cnt_idx, mask_id) < 0) |
| return -1; |
| |
| return count; |
| } |
| |
| static AM_ATTR_RO(cnt0); |
| static AM_ATTR_RO(cnt1); |
| static AM_ATTR_RO(cnt2); |
| static AM_ATTR_RW(enable); |
| static AM_ATTR_RW(disable); |
| static AM_ATTR_RW(clear); |
| static AM_ATTR_RW(mask); |
| |
| static struct attribute *as370_axi_meter_attrs[] = { |
| &axi_meter_attr_cnt0.attr, |
| &axi_meter_attr_cnt1.attr, |
| &axi_meter_attr_cnt2.attr, |
| &axi_meter_attr_enable.attr, |
| &axi_meter_attr_disable.attr, |
| &axi_meter_attr_clear.attr, |
| &axi_meter_attr_mask.attr, |
| NULL, |
| }; |
| |
| static const struct sysfs_ops as370_axi_meter_sysfs_ops = { |
| .show = axi_meter_attr_show, |
| .store = axi_meter_attr_store, |
| }; |
| |
| static struct kobj_type axi_meter_ktype = { |
| .sysfs_ops = &as370_axi_meter_sysfs_ops, |
| .default_attrs = as370_axi_meter_attrs, |
| }; |
| |
| static int as370_axi_meter_sysfs_init(struct axi_meter_priv *axi_meter) |
| { |
| return kobject_init_and_add(&axi_meter->kobj, &axi_meter_ktype, |
| &axi_meter->pdev->dev.kobj, |
| "%s", axi_meter->dev_name); |
| } |
| |
| static int as370_axi_meter_probe(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct axi_meter_priv *axi_meter; |
| struct resource *res; |
| |
| axi_meter = devm_kzalloc(dev, sizeof(struct axi_meter_priv), GFP_KERNEL); |
| if (!axi_meter) |
| return -ENOMEM; |
| |
| res = platform_get_resource(pdev, IORESOURCE_MEM, 0); |
| axi_meter->base = devm_ioremap_resource(dev, res); |
| if (IS_ERR(axi_meter->base)) { |
| dev_err(dev, "map devmem failed\n"); |
| return -ENODEV; |
| } |
| |
| spin_lock_init(&axi_meter->lock); |
| axi_meter->dev_name = dev_name(dev); |
| axi_meter->pdev = pdev; |
| dev_set_drvdata(dev, axi_meter); |
| |
| as370_axi_init_perf_cnt(axi_meter); |
| |
| as370_axi_meter_sysfs_init(axi_meter); |
| dev_info(dev, "as370 axi meter start\n"); |
| return 0; |
| } |
| |
| static int as370_axi_meter_remove(struct platform_device *pdev) |
| { |
| struct device *dev = &pdev->dev; |
| struct axi_meter_priv *axi_meter; |
| |
| axi_meter = (struct axi_meter_priv *)dev_get_drvdata(dev); |
| |
| kobject_put(&axi_meter->kobj); |
| as370_axi_stop_perf_cnt(axi_meter); |
| |
| return 0; |
| } |
| |
| static const struct of_device_id as370_axi_meter_dt_ids[] = { |
| { .compatible = "syna,as370-axi-meter",}, |
| {} |
| }; |
| MODULE_DEVICE_TABLE(of, as370_axi-meter_dt_ids); |
| |
| static struct platform_driver as370_axi_meter_driver = { |
| .probe = as370_axi_meter_probe, |
| .remove = as370_axi_meter_remove, |
| .driver = { |
| .name = "syna-as370-axi-meter", |
| .of_match_table = as370_axi_meter_dt_ids, |
| }, |
| }; |
| module_platform_driver(as370_axi_meter_driver); |
| |
| MODULE_DESCRIPTION("Synaptics AS370 mc6 axi meter"); |
| MODULE_ALIAS("platform:as370-axi-meter"); |
| MODULE_LICENSE("GPL v2"); |