/* SPDX-License-Identifier: GPL-2.0-only */ /* * Copyright (c) 2022, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */ #include "../include/m_ttcan.h" #define spec_bar() asm volatile(ALTERNATIVE("dsb nsh\nisb\n", \ SB_BARRIER_INSN"nop\n", \ ARM64_HAS_SB)) static int mttcan_check_fec_validity(struct mttcan_priv *priv, unsigned int fec); static ssize_t show_std_fltr(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); int cur_filter_size = priv->ttcan->fltr_config.std_fltr_size; ssize_t ret, total = 0; int i = 0; ret = sprintf(buf, "%s\n", "Standard Filters"); if (ret < 0) { pr_err("sprintf() failed at line %d\n", __LINE__); return -ENOMEM; } total += ret; while (cur_filter_size--) { ret = sprintf(buf+total, "%d. 0x%x\n", i, ttcan_get_std_id_filter(priv->ttcan, i)); if (ret < 0) { pr_err("sprintf() failed at line %d\n", __LINE__); return -ENOMEM; } total += ret; i++; } return total; } static ssize_t show_xtd_fltr(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); int cur_filter_size = priv->ttcan->fltr_config.xtd_fltr_size; ssize_t ret, total = 0; int i = 0; ret = sprintf(buf, "%s\n", "Extended Filters"); if (ret < 0) { pr_err("sprintf() failed at line %d\n", __LINE__); return -ENOMEM; } total += ret; while (cur_filter_size--) { ret = sprintf(buf+total, "%d. 0x%llx\n", i, ttcan_get_xtd_id_filter(priv->ttcan, i)); if (ret < 0) { pr_err("sprintf() failed at line %d\n", __LINE__); return -ENOMEM; } total += ret; i++; } return total; } static ssize_t show_gfc_fltr(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "%s\n0x%x\n", "Global filter", ttcan_get_gfc(priv->ttcan)); } static ssize_t store_gfc_fltr(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); struct ttcan_controller *ttcan = priv->ttcan; unsigned int anfs, anfe; unsigned int rrfs, rrfe; u32 gfc; int ret; if (ndev->flags & IFF_UP) { dev_err(dev, "GFC cannot be configured as device is running\n"); return -EBUSY; } ret = sscanf(buf, "anfs=%u anfe=%u rrfs=%u rrfe=%u", &anfs, &anfe, &rrfs, &rrfe); if ((ret < 4) || ((anfs | anfe) > 3) || ((rrfs | rrfe) > 1)) { dev_err(dev, "Invalid Global filter\n"); pr_err("usage:anfs=0..3 anfe=0..3 rrfs=0/1 rrfe=0/1\n"); return -EINVAL; } if (((anfs == GFC_ANFS_RXFIFO_0) || (anfe == GFC_ANFE_RXFIFO_0)) && (ttcan->mram_cfg[MRAM_RXF0].num == 0U)) { dev_err(priv->device, "RX FIFO 0 is not used currently."); dev_err(priv->device, " Change it via DT\n"); return -EINVAL; } if (((anfs == GFC_ANFS_RXFIFO_1) || (anfe == GFC_ANFE_RXFIFO_1)) && (ttcan->mram_cfg[MRAM_RXF1].num == 0U)) { dev_err(priv->device, "RX FIFO 1 is not used currently."); dev_err(priv->device, " Change it via DT\n"); return -EINVAL; } gfc = 0; gfc = (anfs << MTT_GFC_ANFS_SHIFT) & MTT_GFC_ANFS_MASK; gfc |= (anfe << MTT_GFC_ANFE_SHIFT) & MTT_GFC_ANFE_MASK; gfc |= (rrfs << MTT_GFC_RRFS_SHIFT) & MTT_GFC_RRFS_MASK; gfc |= (rrfe << MTT_GFC_RRFE_SHIFT) & MTT_GFC_RRFE_MASK; priv->gfc_reg = gfc; ttcan_set_gfc(priv->ttcan, gfc); return count; } static ssize_t show_xidam(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "%s\n0x%x\n", "XIDAM", ttcan_get_xidam(priv->ttcan)); } static ssize_t store_xidam(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); unsigned int xidam; if (ndev->flags & IFF_UP) { dev_err(dev, "XIDAM is protected, device is running\n"); return -EBUSY; } /* usage: xidam=MASK */ if (sscanf(buf, "xidam=%u", &xidam) != 1) { dev_err(dev, "Invalid XIDAM MASK\n"); pr_err("usage: xidam=MASK\n"); return -EINVAL; } priv->xidam_reg = xidam; ttcan_set_xidam(priv->ttcan, xidam); return count; } static int mttcan_check_fec_validity(struct mttcan_priv *priv, unsigned int fec) { struct ttcan_controller *ttcan = priv->ttcan; if (fec > FEC_RXBUF) { dev_err(priv->device, "sfec/efec should be in range 0-7\n"); return -EINVAL; } if (((fec == FEC_RXFIFO_0) || (fec == FEC_RXFIFO_0_PRIO)) && (ttcan->mram_cfg[MRAM_RXF0].num == 0U)) { dev_err(priv->device, "RX FIFO 0 is not used currently."); dev_err(priv->device, " Change it via DT\n"); return -EINVAL; } if (((fec == FEC_RXFIFO_1) || (fec == FEC_RXFIFO_1_PRIO)) && (ttcan->mram_cfg[MRAM_RXF1].num == 0U)) { dev_err(priv->device, "RX FIFO 1 is not used currently."); dev_err(priv->device, " Change it via DT\n"); return -EINVAL; } if ((fec == FEC_RXBUF) && (ttcan->mram_cfg[MRAM_RXB].num == 0U)) { dev_err(priv->device, "RX Buffer is not used currently."); dev_err(priv->device, " Change it via DT\n"); return -EINVAL; } return 0; } static ssize_t store_std_fltr(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); struct net_device *ndev = to_net_dev(dev); unsigned int sft, sfec; unsigned int sfid1, sfid2; int idx = -1, cur_filter_size; int items; int ret; if (ndev->flags & IFF_UP) { dev_err(dev, "device is running\n"); return -EBUSY; } /* usage: sft="0/1/2/3" sfec=1...7 sfid1="ID1" sfid2="ID2" idx=%u */ ret = sscanf(buf, "sft=%u sfec=%u sfid1=%X sfid2=%X idx=%u", &sft, &sfec, &sfid1, &sfid2, &idx); if (ret < 4) { /* Not passing index is allowed */ dev_err(dev, "Invalid std filter\n"); pr_err("usage:sft=0..3 sfec=0..7 sfid1=ID1h sfid2=ID2h idx=i\n"); return -EINVAL; } items = ret; cur_filter_size = priv->ttcan->fltr_config.std_fltr_size; if ((idx > cur_filter_size) || (idx == -1)) { if (cur_filter_size >= priv->ttcan->mram_cfg[MRAM_SIDF].num) { dev_err(dev, "Max Invalid std filter Index\n"); return -ENOSPC; } } ret = mttcan_check_fec_validity(priv, sfec); if (ret < 0) { dev_err(dev, "Invalid sfec value\n"); return -EINVAL; } if (items == 5) { if (idx > cur_filter_size) { dev_err(dev, "Invalid std filter Index\n"); return -EINVAL; } /* array access based on user provided index/data */ spec_bar(); ttcan_set_std_id_filter(priv->ttcan, priv->std_shadow, idx, (u8)sft, (u8)sfec, sfid1, sfid2); if (idx == cur_filter_size) priv->ttcan->fltr_config.std_fltr_size++; } else { /* array access based on user provided index/data */ spec_bar(); ttcan_set_std_id_filter(priv->ttcan, priv->std_shadow, cur_filter_size, (u8) sft, (u8)sfec, sfid1, sfid2); priv->ttcan->fltr_config.std_fltr_size++; } return count; } static ssize_t store_xtd_fltr(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); struct net_device *ndev = to_net_dev(dev); unsigned int eft, efec; unsigned int efid1, efid2; int idx = -1, cur_filter_size; int items; int ret; if (ndev->flags & IFF_UP) { dev_err(dev, "device is running\n"); return -EBUSY; } /* usage: eft="0/1/2/3" efec=1...7 efid1="ID1h" efid2="ID2h" idx=%u */ ret = sscanf(buf, "eft=%u efec=%u efid1=%X efid2=%X idx=%u", &eft, &efec, &efid1, &efid2, &idx); if (ret < 4) { /* Not passing index is allowed */ dev_err(dev, "Invalid xtd filter\n"); pr_err("usage:eft=0..3 efec=0..7 efid1=ID1h efid2=ID2h idx=i\n"); return -EINVAL; } items = ret; cur_filter_size = priv->ttcan->fltr_config.xtd_fltr_size; if ((idx > cur_filter_size) || (idx == -1)) { if (cur_filter_size >= priv->ttcan->mram_cfg[MRAM_XIDF].num) { dev_err(dev, "Max Invalid xtd filter Index\n"); return -ENOSPC; } } ret = mttcan_check_fec_validity(priv, efec); if (ret < 0) { dev_err(dev, "Invalid efec value\n"); return -EINVAL; } if (items == 5) { if (idx > cur_filter_size) { dev_err(dev, "Invalid xtd filter Index\n"); return -EINVAL; } /* array access based on user provided index/data */ spec_bar(); ttcan_set_xtd_id_filter(priv->ttcan, priv->xtd_shadow, idx, (u8) eft, (u8) efec, efid1, efid2); if (idx == cur_filter_size) priv->ttcan->fltr_config.xtd_fltr_size++; } else { /* array access based on user provided index/data */ spec_bar(); ttcan_set_xtd_id_filter(priv->ttcan, priv->xtd_shadow, cur_filter_size, (u8) eft, (u8) efec, efid1, efid2); priv->ttcan->fltr_config.xtd_fltr_size++; } return count; } static ssize_t show_tx_cancel(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "%s\n0x%x\n", "TXBCF", ttcan_read_tx_cancelled_reg(priv->ttcan)); } static ssize_t store_tx_cancel(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); unsigned int txcbr; /* usage: txbcr=bit_mask for buffer */ if (sscanf(buf, "txbcr=%X", &txcbr) != 1) { dev_err(dev, "Invalid TXBCR value\n"); pr_err("%s usage: txbcr=bit_mask to cancel\n", buf); return -EINVAL; } ttcan_set_tx_cancel_request(priv->ttcan, txcbr); return count; } static ssize_t show_ttrmc(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "%s\n0x%x\n", "TTRMC", ttcan_get_ttrmc(priv->ttcan)); } static ssize_t store_ttrmc(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); unsigned int rmps, xtd, rid; if (ndev->flags & IFF_UP) { dev_err(dev, "TTRMC is protected, device is running\n"); return -EBUSY; } /* usage: rmps=0/1 xtd=0/1 rid=ReferenceID */ if (sscanf(buf, "rmps=%u xtd=%u rid=%X", &rmps, &xtd, &rid) != 3) { dev_err(dev, "Invalid TTRMC\n"); pr_err("usage: rmps=0/1 xtd=0/1 rid=ReferenceID in hex\n"); return -EINVAL; } ttcan_set_ref_mesg(priv->ttcan, rid, rmps, xtd); return count; } static ssize_t show_ttocf(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "%s\n0x%x\n", "TTOCF", ttcan_get_ttocf(priv->ttcan)); } static ssize_t store_ttocf(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); unsigned int evtp, ecc, egtf, awl, eecs, irto, ldsdl, tm, gen, om; if (ndev->flags & IFF_UP) { dev_err(dev, "TTCOF is protected, device is running\n"); return -EBUSY; } /* usage: evtp=0/1 ecc=0/1 egtf=0/1 awl=0..255 eecs=0 irto=0..127 * ldsdl=0..7 tm=0/1 gen=0/1 om=0..4 */ if (sscanf(buf, "evtp=%u ecc=%u egtf=%u awl=%u eecs=%u irto=%u ldsdl=%u tm=%u gen=%u om=%u", &evtp, &ecc, &egtf, &awl, &eecs, &irto, &ldsdl, &tm, &gen, &om) != 10) { dev_err(dev, "Invalid TTOCF\n"); pr_err("usage: evtp=0/1 ecc=0/1 egtf=0/1 awl=0..255 eecs=0 irto=0..127 ldsdl=0..7 tm=0/1 gen=0/1 om=0..4\n"); return -EINVAL; } ttcan_set_tt_config(priv->ttcan, evtp, ecc, egtf, awl, eecs, irto, ldsdl, tm, gen, om); return count; } static ssize_t show_ttmlm(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "%s\n0x%x\n", "TTMLM", ttcan_get_ttmlm(priv->ttcan)); } static ssize_t store_ttmlm(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); unsigned int entt, txew, css, ccm; if (ndev->flags & IFF_UP) { dev_err(dev, "TTMLM is protected, device is running\n"); return -EBUSY; } /* usage: entt=0...4095 txew=0..15 css=0..2 ccm=0..63(2*pow(n)-1) */ if (sscanf(buf, "entt=%u txew=%u css=%u ccm=%u", &entt, &txew, &css, &ccm) != 4) { dev_err(dev, "Invalid TTMLM\n"); pr_err("usage: entt=0...4095 txew=0..15 css=0..2 ccm=0..63(2*pow(n)-1)\n"); return -EINVAL; } ttcan_set_matrix_limits(priv->ttcan, entt, txew, css, ccm); return count; } static ssize_t show_tttmc(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "%s\n0x%x\n", "TTTMC", ttcan_get_tttmc(priv->ttcan)); } static ssize_t store_tttmc(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); u32 tme, tttmc; if (ndev->flags & IFF_UP) { dev_err(dev, "TTTMC is protected, device is running\n"); return -EBUSY; } /* usage: tme=number of elements */ if (sscanf(buf, "tme=%u", &tme) != 1) { dev_err(dev, "Invalid TTTMC\n"); pr_err("usage: tme=0..64 (Num Elements)\n"); return -EINVAL; } if (tme > 64) tme = 64; tttmc = ttcan_get_tttmc(priv->ttcan); tttmc &= ~MTT_TTTMC_TME_MASK; tttmc |= (tme << MTT_TTTMC_TME_SHIFT) & MTT_TTTMC_TME_MASK; ttcan_set_tttmc(priv->ttcan, tttmc); return count; } static ssize_t show_cccr_txbar(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); unsigned int init = 0; init = ttcan_get_cccr(priv->ttcan) & 0x1; return sprintf(buf, "CCCR.INIT %s\n", init ? "set" : "reset"); } static ssize_t store_cccr_txbar(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); char str[32]; int txbar = 0; if ((sscanf(buf, "%s txbar=%d", str, &txbar) != 2) || (txbar > 32)) { dev_err(dev, "Invalid String or txbar\n"); pr_err("usage: reset/set txbar=0..32 (Num Elements)\n"); return -EINVAL; } /* usage: set/reset */ if (strcmp("set", str) == 0) ttcan_set_config_change_enable(priv->ttcan); else if (strcmp("reset", str) == 0) ttcan_reset_config_change_enable(priv->ttcan); else { dev_err(dev, "Invalid String\n"); pr_err("valid strings: set/reset\n"); return -EINVAL; } txbar = (1UL << txbar) - 1; ttcan_set_txbar(priv->ttcan, txbar); return count; } static ssize_t show_txbar(struct device *dev, struct device_attribute *devattr, char *buf) { return sprintf(buf, "Not implemented\n"); } static ssize_t store_txbar(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); unsigned int txbar = 0; /* usage: txbar=1...32*/ if ((sscanf(buf, "txbar=%u", &txbar) != 1) || (txbar > 32)) { dev_err(dev, "Invalid TXBAR\n"); pr_err("usage: txbar=1..32\n"); return -EINVAL; } txbar = (1UL << txbar) - 1; ttcan_set_txbar(priv->ttcan, txbar); return count; } static ssize_t show_trigger_mem(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); int cur = priv->ttcan->tt_mem_elements; ssize_t ret, total = 0; int i = 0; ret = sprintf(buf, "%s\n", "Trigger Memory Elements"); if (ret < 0) { pr_err("sprintf() failed at line %d\n", __LINE__); return -ENOMEM; } total += ret; while (cur--) { ret = sprintf(buf+total, "%d. 0x%llx\n", i, ttcan_get_trigger_mem(priv->ttcan, i)); if (ret < 0) { pr_err("sprintf() failed at line %d\n", __LINE__); return -ENOMEM; } total += ret; i++; } return total; } static ssize_t store_trigger_mem(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { unsigned int tm, cc, tmin, tmex, type; unsigned int ftype, mnr; int idx = -1, cur; int ret; struct net_device *ndev = to_net_dev(dev); struct mttcan_priv *priv = netdev_priv(ndev); if (ndev->flags & IFF_UP) { dev_err(dev, "Trigger Mem is protected, device is running\n"); return -EBUSY; } /* usage: tm=0..FFFF cc=0..127 tmin=0/1 tmex=0/1 type=0..10 ftype=0/1 mnr=0..31 idx=%u */ ret = sscanf(buf, "tm=%X cc=%u tmin=%u tmex=%u type=%u ftype=%u mnr=%u idx=%u", &tm, &cc, &tmin, &tmex, &type, &ftype, &mnr, &idx); if (ret < 7) { /* Not passing index is allowed */ dev_err(dev, "Invalid Trigger Element\n"); pr_err("tm=0..0xFFFF cc=0..127 tmin=0/1 tmex=0/1 type=0..10 ftype=0/1 mnr=0..31 idx=i\n"); return -EINVAL; } cur = priv->ttcan->tt_mem_elements; if ((idx > cur) || (idx == -1)) if (cur >= priv->ttcan->mram_cfg[MRAM_TMC].num) { dev_err(dev, "Max Invalid Trigger mem Index\n"); return -ENOSPC; } if (ret == 8) { if (idx > cur) { dev_err(dev, "Invalid Trigger Mem Index\n"); return -EINVAL; } /* array access based on user provided index/data */ spec_bar(); ttcan_set_trigger_mem(priv->ttcan, priv->tmc_shadow, idx, tm, cc, tmin, tmex, type, ftype, mnr); if (idx == cur) priv->ttcan->tt_mem_elements++; } else { /* array access based on user provided index/data */ spec_bar(); ttcan_set_trigger_mem(priv->ttcan, priv->tmc_shadow, cur, tm, cc, tmin, tmex, type, ftype, mnr); priv->ttcan->tt_mem_elements++; } return count; } static ssize_t show_tdc_offset(struct device *dev, struct device_attribute *devattr, char *buf) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); return sprintf(buf, "tdc_offset=0x%x, DBTP.tdc=%d\n", priv->ttcan->tdc_offset, priv->ttcan->tdc); } static ssize_t store_tdc_offset(struct device *dev, struct device_attribute *devattr, const char *buf, size_t count) { struct mttcan_priv *priv = netdev_priv(to_net_dev(dev)); unsigned int tdc_offset = 0; if ((sscanf(buf, "%X", &tdc_offset) != 1)) { dev_err(dev, "wrong tdc_offset\n"); return -EINVAL; } if (tdc_offset != 0) priv->ttcan->tdc = 1; else priv->ttcan->tdc = 0; priv->ttcan->tdc_offset = tdc_offset; return count; } static DEVICE_ATTR(std_filter, S_IRUGO | S_IWUSR, show_std_fltr, store_std_fltr); static DEVICE_ATTR(xtd_filter, S_IRUGO | S_IWUSR, show_xtd_fltr, store_xtd_fltr); static DEVICE_ATTR(gfc_filter, S_IRUGO | S_IWUSR, show_gfc_fltr, store_gfc_fltr); static DEVICE_ATTR(xidam, S_IRUGO | S_IWUSR, show_xidam, store_xidam); static DEVICE_ATTR(tx_cancel, S_IRUGO | S_IWUSR, show_tx_cancel, store_tx_cancel); static DEVICE_ATTR(ttrmc, S_IRUGO | S_IWUSR, show_ttrmc, store_ttrmc); static DEVICE_ATTR(ttocf, S_IRUGO | S_IWUSR, show_ttocf, store_ttocf); static DEVICE_ATTR(ttmlm, S_IRUGO | S_IWUSR, show_ttmlm, store_ttmlm); static DEVICE_ATTR(tttmc, S_IRUGO | S_IWUSR, show_tttmc, store_tttmc); static DEVICE_ATTR(txbar, S_IRUGO | S_IWUSR, show_txbar, store_txbar); static DEVICE_ATTR(cccr_init_txbar, S_IRUGO | S_IWUSR, show_cccr_txbar, store_cccr_txbar); static DEVICE_ATTR(trigger_mem, S_IRUGO | S_IWUSR, show_trigger_mem, store_trigger_mem); static DEVICE_ATTR(tdc_offset, S_IRUGO | S_IWUSR, show_tdc_offset, store_tdc_offset); static struct attribute *mttcan_attr[] = { &dev_attr_std_filter.attr, &dev_attr_xtd_filter.attr, &dev_attr_gfc_filter.attr, &dev_attr_xidam.attr, &dev_attr_tx_cancel.attr, &dev_attr_ttrmc.attr, &dev_attr_ttocf.attr, &dev_attr_ttmlm.attr, &dev_attr_tttmc.attr, &dev_attr_txbar.attr, &dev_attr_cccr_init_txbar.attr, &dev_attr_trigger_mem.attr, &dev_attr_tdc_offset.attr, NULL }; static const struct attribute_group mttcan_attr_group = { .attrs = mttcan_attr, }; int mttcan_create_sys_files(struct device *dev) { return sysfs_create_group(&dev->kobj, &mttcan_attr_group); } void mttcan_delete_sys_files(struct device *dev) { sysfs_remove_group(&dev->kobj, &mttcan_attr_group); }