This adds the core for CAN transceivers
Signed-off-by: Kurt Van Dijck <[email protected]> ---- Index: include/socketcan/can/dev.h =================================================================== --- include/socketcan/can/dev.h (revision 1069) +++ include/socketcan/can/dev.h (working copy) @@ -55,6 +55,7 @@ unsigned int echo_skb_max; struct sk_buff **echo_skb; + struct can_transceiver *cantr; }; #if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,21) Index: include/socketcan/can/error.h =================================================================== --- include/socketcan/can/error.h (revision 1069) +++ include/socketcan/can/error.h (working copy) @@ -28,6 +28,7 @@ #define CAN_ERR_BUSOFF 0x00000040U /* bus off */ #define CAN_ERR_BUSERROR 0x00000080U /* bus error (may flood!) */ #define CAN_ERR_RESTARTED 0x00000100U /* controller restarted */ +#define CAN_ERR_TRANSCEIVER 0x00000200U /* transceiver alert */ /* arbitration lost in bit ... / data[0] */ #define CAN_ERR_LOSTARB_UNSPEC 0x00 /* unspecified */ @@ -78,18 +79,20 @@ #define CAN_ERR_PROT_LOC_INTERM 0x12 /* intermission */ /* error status of CAN-transceiver / data[4] */ -/* CANH CANL */ +/* CANL CANH */ #define CAN_ERR_TRX_UNSPEC 0x00 /* 0000 0000 */ #define CAN_ERR_TRX_CANH_NO_WIRE 0x04 /* 0000 0100 */ #define CAN_ERR_TRX_CANH_SHORT_TO_BAT 0x05 /* 0000 0101 */ #define CAN_ERR_TRX_CANH_SHORT_TO_VCC 0x06 /* 0000 0110 */ #define CAN_ERR_TRX_CANH_SHORT_TO_GND 0x07 /* 0000 0111 */ +#define CAN_ERR_TRX_CANL_SHORT_TO_CANH 0x08 /* 0000 1000 */ #define CAN_ERR_TRX_CANL_NO_WIRE 0x40 /* 0100 0000 */ #define CAN_ERR_TRX_CANL_SHORT_TO_BAT 0x50 /* 0101 0000 */ #define CAN_ERR_TRX_CANL_SHORT_TO_VCC 0x60 /* 0110 0000 */ #define CAN_ERR_TRX_CANL_SHORT_TO_GND 0x70 /* 0111 0000 */ -#define CAN_ERR_TRX_CANL_SHORT_TO_CANH 0x80 /* 1000 0000 */ +#define CAN_ERR_TRX_UNKNOWN_ERROR 0xF0 + /* controller specific additional information / data[5..7] */ #endif /* CAN_ERROR_H */ Index: include/socketcan/can/transceiver.h =================================================================== --- include/socketcan/can/transceiver.h (revision 0) +++ include/socketcan/can/transceiver.h (revision 0) @@ -0,0 +1,99 @@ +/* + * socketcan/can/transceiver.h + * + * Definitions for the CAN transceiver class device interface + * + * Copyright (C) 2009 Kurt Van Dijck <[email protected]> + * + * Send feedback to <[email protected]> + */ + +#include <linux/device.h> +#include <linux/netdevice.h> + +#ifndef CAN_TRANSCEIVER_H +#define CAN_TRANSCEIVER_H + +/* + * CAN transceiver modes + */ +#define CANTR_MODE_OFF 0 +#define CANTR_MODE_ON 1 + +/* + * CAN transceiver class device + */ +struct can_transceiver { + int id; + struct device dev; + int (*get_max_bitrate)(struct can_transceiver *); + /* returns any CAN_ERR_TRX_ constants, or 0 when OK */ + int (*get_state)(struct can_transceiver *); + /* mode is any CANTR_MODE_... */ + int (*set_mode)(struct can_transceiver *, int mode); + /* match_name & netdev are protected by the global cantr_lock */ + char *match_name; + struct net_device *netdev; + struct mutex lock; /* protects the next fields */ + int mode; +}; +#define to_can_transceiver(d) (container_of((d), struct can_transceiver, dev)) + +static inline void cantr_set_drvdata(struct can_transceiver *cantr, void *p) +{ + dev_set_drvdata(&cantr->dev, p); +} +static inline void *cantr_get_drvdata(struct can_transceiver *cantr) +{ + return dev_get_drvdata(&cantr->dev); +} + +/** + * can_transceiver_register - register w/ can_tranceiver class + * @cantr: the device to register + * @parent: the parent device + * + * Returns 0 on success + */ +extern int can_transceiver_register(struct can_transceiver *); + +/** + * can_transceiver_unregister - removes the can transceiver + * + * @cantr: the can transceiver class device to remove + */ +extern void can_transceiver_unregister(struct can_transceiver *); + +/** + * can_transceiver_set_match_name + * @cantr: the CAN transceiver to detach + * @name: the new name. may be 0. + * + * This function changes the name that the transceiver uses to match + * a net_device. Calling this function may change the associated + * net_device + */ +extern void can_transceiver_set_match_name(struct can_transceiver *, + const char *name); + +/* + * alert from can transceiver. + * the can transceiver core should do something with it + */ +extern void can_transceiver_alert(struct can_transceiver *cantr, int state); +/* + * utility functions + */ +extern int can_transceiver_set_mode(struct can_transceiver *cantr, int mode); +extern int can_transceiver_get_mode(struct can_transceiver *cantr); +extern int can_transceiver_get_max_bitrate(struct can_transceiver *cantr); +/** + * can_transceiver_get_state - get the current state of the transceiver + * @cantr: the can transceiver + * + * Returns: 0 for ready, -error for software failure, +num for hardware failure + */ +extern int can_transceiver_get_state(struct can_transceiver *cantr); + +#endif /* CAN_TRANSCEIVER_H */ + Index: Makefile =================================================================== --- Makefile (revision 1069) +++ Makefile (working copy) @@ -6,6 +6,9 @@ export CONFIG_CAN_VCAN=m export CONFIG_CAN_SLCAN=m +export CONFIG_CANTR_CORE=m +export CONFIG_CANTR_PCA82C251=m +export CONFIG_CANTR_TJA1041=m export CONFIG_CAN_DEV=m export CONFIG_CAN_CALC_BITTIMING=y #export CONFIG_CAN_DEV_SYSFS=y Index: drivers/net/can/dev.c =================================================================== --- drivers/net/can/dev.c (revision 1069) +++ drivers/net/can/dev.c (working copy) @@ -38,6 +38,7 @@ #endif #include "sysfs.h" #endif +#include <socketcan/can/transceiver.h> #define MOD_DESC "CAN device driver interface" @@ -591,7 +592,12 @@ #ifdef CONFIG_CAN_DEV_SYSFS int err; #endif +#ifdef CONFIG_CANTR_CORE + int ret; + int max_bitrate; +#endif + if (!priv->bittiming.tq && !priv->bittiming.bitrate) { dev_err(ND2D(dev), "bit-timing not yet defined\n"); return -EINVAL; @@ -609,6 +615,20 @@ return err; } #endif +#ifdef CONFIG_CANTR_CORE + if (!priv->cantr) + goto no_transceiver; + max_bitrate = can_transceiver_get_max_bitrate(priv->cantr); + if (priv->bittiming.bitrate <= max_bitrate) + goto transceiver_bitrate_ok; + dev_err(ND2D(dev), "bitrate over transceiver's range\n"); + return -EINVAL; +transceiver_bitrate_ok: + ret = can_transceiver_set_mode(priv->cantr, CANTR_MODE_ON); + if (ret < 0) + return ret; +no_transceiver: +#endif /* Switch carrier on if device was stopped while in bus-off state */ if (!netif_carrier_ok(dev)) @@ -633,6 +653,13 @@ if (del_timer_sync(&priv->restart_timer)) dev_put(dev); can_flush_echo_skb(dev); +#ifdef CONFIG_CANTR_CORE + if (!priv->cantr) + goto no_transceiver; + can_transceiver_set_mode(priv->cantr, CANTR_MODE_OFF); +no_transceiver: + ; /* remove warning 'label at end of ...' */ +#endif } EXPORT_SYMBOL_GPL(close_candev); Index: drivers/net/can/Makefile =================================================================== --- drivers/net/can/Makefile (revision 1069) +++ drivers/net/can/Makefile (working copy) @@ -34,6 +34,8 @@ obj-$(CONFIG_CAN_VCAN) += vcan.o obj-$(CONFIG_CAN_SLCAN) += slcan.o +obj-$(CONFIG_CANTR_CORE) += transceiver/ + obj-$(CONFIG_CAN_DEV) += can-dev.o can-dev-y := dev.o can-dev-$(CONFIG_CAN_DEV_SYSFS) += sysfs.o @@ -60,5 +62,8 @@ ifneq ($(CONFIG_CAN_CALC_BITTIMING),n) EXTRA_CFLAGS += -DCONFIG_CAN_CALC_BITTIMING endif +ifneq ($(CONFIG_CANTR_CORE),n) + EXTRA_CFLAGS += -DCONFIG_CANTR_CORE +endif endif Index: drivers/net/can/Kconfig =================================================================== --- drivers/net/can/Kconfig (revision 1069) +++ drivers/net/can/Kconfig (working copy) @@ -37,6 +37,29 @@ source "drivers/net/can/old/Kconfig" endif +config CANTR_CORE + tristate "CAN transceiver core" + depends on CAN + default Y + ---help--- + Say y to have support for extended functions of various CAN transceivers. + +config CANTR_PCA82C251 + tristate "NXP PCA82C251" + depends on CANTR_CORE + default Y + ---help--- + this is the most used CAN transceiver. Drivers can use it directly or via + a platform device + +config CANTR_TJA1041 + tristate "NXP TJA1041" + depends on CANTR_CORE + default Y + ---help--- + This is an advanced CAN transceiver. It can detect short-circuits on the bus, + has error indications, ... + config CAN_DEV tristate "Platform CAN drivers with Netlink support" depends on CAN Index: drivers/net/can/transceiver/Makefile =================================================================== --- drivers/net/can/transceiver/Makefile (revision 0) +++ drivers/net/can/transceiver/Makefile (revision 0) @@ -0,0 +1,22 @@ +ifeq ($(KERNELRELEASE),) + +KERNELDIR := /lib/modules/$(shell uname -r)/build +PWD := $(shell pwd) +TOPDIR := $(PWD)/../../../.. + +modules modules_install clean: + $(MAKE) -C $(KERNELDIR) M=$(PWD) $@ TOPDIR=$(TOPDIR) + +else + +-include $(TOPDIR)/Makefile.common + +obj-$(CONFIG_CANTR_CORE) += can-transceiver.o +obj-$(CONFIG_CANTR_PCA82C251) += pca82c251.o +obj-$(CONFIG_CANTR_TJA1041) += tja1041.o + +ifeq ($(CONFIG_CAN_DEBUG_DEVICES),y) + EXTRA_CFLAGS += -DDEBUG +endif + +endif Index: drivers/net/can/transceiver/can-transceiver.c =================================================================== --- drivers/net/can/transceiver/can-transceiver.c (revision 0) +++ drivers/net/can/transceiver/can-transceiver.c (revision 0) @@ -0,0 +1,511 @@ +/* + * CAN transceiver subsystem, base class + * + * Copyright (C) 2009 EIA Electronics + * Author: Kurt Van Dijck <[email protected]> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/module.h> +#include <linux/errno.h> +#include <linux/err.h> +#include <linux/mutex.h> +#include <linux/idr.h> +#include <linux/sysfs.h> +#include <linux/timer.h> +#include <linux/netdevice.h> +#include <linux/if_arp.h> +#include <socketcan/can.h> +#include <socketcan/can/dev.h> +#include <socketcan/can/error.h> +#include <socketcan/can/transceiver.h> +#include <net/rtnetlink.h> + +static DEFINE_IDR(cantr_idr); +static DEFINE_MUTEX(cantr_lock); +static struct class *cantr_class; + +int can_transceiver_get_state(struct can_transceiver *cantr) +{ + if (!cantr->get_state) + return 0; + return cantr->get_state(cantr); +} +EXPORT_SYMBOL_GPL(can_transceiver_get_state); + +int can_transceiver_get_mode(struct can_transceiver *cantr) +{ + int ret; + + ret = mutex_lock_interruptible(&cantr->lock); + if (ret < 0) + return ret; + ret = cantr->mode; + mutex_unlock(&cantr->lock); + return ret; +} +EXPORT_SYMBOL_GPL(can_transceiver_get_mode); + +int can_transceiver_set_mode(struct can_transceiver *cantr, int mode) +{ + int ret; + + ret = mutex_lock_interruptible(&cantr->lock); + if (ret < 0) + return ret; + + if (cantr->mode == mode) + goto done; + + if (cantr->set_mode) { + ret = cantr->set_mode(cantr, mode); + if (ret < 0) + goto failed; + } + if (mode && !cantr->mode) + get_device(&cantr->dev); + else if (!mode && cantr->mode) + put_device(&cantr->dev); + + cantr->mode = mode; +done: + mutex_unlock(&cantr->lock); + return mode; +failed: + mutex_unlock(&cantr->lock); + return ret; +} +EXPORT_SYMBOL_GPL(can_transceiver_set_mode); + +int can_transceiver_get_max_bitrate(struct can_transceiver *cantr) +{ + if (!cantr->get_max_bitrate) + /* must suppose it's ok, assume 1GBit as upper limit */ + return 1000000000; + return cantr->get_max_bitrate(cantr); +} +EXPORT_SYMBOL_GPL(can_transceiver_get_max_bitrate); + +static void can_transceiver_notify(struct net_device *dev, int state) +{ + struct sk_buff *skb; + struct can_frame *cf; +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,23) + struct net_device_stats *stats = can_get_stats(dev); +#else + struct net_device_stats *stats = &dev->stats; +#endif + /* send message upstream */ + skb = alloc_can_err_skb(dev, &cf); + if (unlikely(!skb)) + return; + cf->can_id |= CAN_ERR_TRANSCEIVER; + cf->data[4] = state; + netif_rx(skb); + +#if LINUX_VERSION_CODE < KERNEL_VERSION(2,6,32) + dev->last_rx = jiffies; +#endif + stats->rx_packets++; + stats->rx_bytes += cf->can_dlc; +} + +/* + * to be called from within can transceivers + * but not from interrupt context + * each transceiver should deal with that on his own way + * to avoid a lot of redundant work + */ +void can_transceiver_alert(struct can_transceiver *cantr, int state) +{ + if (!cantr->netdev) + return; + if (state < 0) + return; + + can_transceiver_notify(cantr->netdev, state); + + rtnl_lock(); + dev_alert(&cantr->dev, "bring %s down\n", cantr->netdev->name); + dev_close(cantr->netdev); + rtnl_unlock(); +} +EXPORT_SYMBOL_GPL(can_transceiver_alert); + +static int _can_transceiver_attach(struct can_transceiver *cantr, + struct net_device *netdev) +{ + struct can_priv *priv = netdev_priv(netdev); + int ret; + int initial_mode = 0; + + if (netdev->type != ARPHRD_CAN) + return -EBADF; + + if (!cantr) { + /* special case: cantr is supplied in netdev during create */ + cantr = priv->cantr; + if (!cantr) + return -EINVAL; + initial_mode = 1; + } + if (cantr->netdev) { + ret = -EBUSY; + goto failed; + } + if (!initial_mode && priv->cantr && (cantr != priv->cantr)) { + ret = -EBUSY; + goto failed; + } + cantr->netdev = netdev; + priv->cantr = cantr; + ret = sysfs_create_link(&cantr->dev.kobj, &netdev->dev.kobj, + "netdev"); + if (ret < 0) + goto failed; + ret = sysfs_create_link(&netdev->dev.kobj, &cantr->dev.kobj, + "can-transceiver"); + if (ret < 0) + goto failed; + dev_info(&cantr->dev, "attached to %s\n", netdev->name); + + return 0; +failed: + cantr->netdev = 0; + priv->cantr = 0; + sysfs_remove_link(&netdev->dev.kobj, "can-transceiver"); + sysfs_remove_link(&cantr->dev.kobj, "netdev"); + dev_err(&cantr->dev, "attach to %s failed\n", netdev->name); + return ret; +} + +static void _can_transceiver_detach(struct can_transceiver *cantr) +{ + struct net_device *netdev; + struct can_priv *priv; + + if (!cantr->netdev) + return; + netdev = cantr->netdev; + priv = netdev_priv(netdev); + priv->cantr = 0; + cantr->netdev = 0; + sysfs_remove_link(&netdev->dev.kobj, "can-transceiver"); + sysfs_remove_link(&cantr->dev.kobj, "netdev"); + dev_info(&cantr->dev, "detached from %s\n", netdev->name); +} + +static int cantr_match_netdev(struct device *dev, void *p) +{ + struct can_transceiver *cantr = to_can_transceiver(dev); + struct net_device *netdev = p; + const char *name; + + if (!cantr->match_name) + return 0; + if (!netdev->dev.parent) + return 0; + name = dev_name(netdev->dev.parent); + if (!name || !strlen(name)) + return 0; + return !strcmp(name, cantr->match_name); +} + +void can_transceiver_set_match_name(struct can_transceiver *cantr, + const char *name) +{ + struct net_device *netdev; + struct can_priv *priv; + + if (name && !name[0]) + name = 0; + mutex_lock(&cantr_lock); + /* remove current assignment (if there is one) */ + netdev = cantr->netdev; + if (netdev) { + dev_hold(netdev); + _can_transceiver_detach(cantr); + dev_put(netdev); + + } + + kfree(cantr->match_name); + cantr->match_name = 0; + if (!name) + goto name_done; + cantr->match_name = kstrdup(name, GFP_KERNEL); + + read_lock(&dev_base_lock); + for_each_netdev(&init_net, netdev) { + dev_hold(netdev); + if (!cantr_match_netdev(&cantr->dev, netdev)) + goto netdev_ignore; + if (netdev->type != ARPHRD_CAN) + goto netdev_ignore; + priv = netdev_priv(netdev); + if (priv->cantr) + goto netdev_ignore; + read_unlock(&dev_base_lock); + _can_transceiver_attach(cantr, netdev); + if (netif_running(netdev)) + can_transceiver_set_mode(cantr, CANTR_MODE_ON); + dev_put(netdev); + goto name_done; +netdev_ignore: + dev_put(netdev); + } + read_unlock(&dev_base_lock); +name_done: + mutex_unlock(&cantr_lock); + return; +} +EXPORT_SYMBOL_GPL(can_transceiver_set_match_name); + +/* + * notification callback + */ +static int can_transceiver_notifications(struct notifier_block *nb, + unsigned long msg, void *data) +{ + struct net_device *netdev = data; + struct can_priv *priv = netdev_priv(netdev); + struct device *dev; + struct can_transceiver *cantr; + + if (netdev->type != ARPHRD_CAN) + return NOTIFY_DONE; + + switch (msg) { + case NETDEV_REGISTER: + mutex_lock(&cantr_lock); + if (priv->cantr) { + cantr = priv->cantr; + if (!get_device(&cantr->dev)) + goto register_done; + _can_transceiver_attach(0, netdev); + goto attached; + } + dev = class_find_device(cantr_class, 0, + netdev, cantr_match_netdev); + if (!dev) + goto register_done; + cantr = to_can_transceiver(dev); + _can_transceiver_attach(cantr, netdev); +attached: + /* class_find_device did get_device() */ + put_device(&cantr->dev); +register_done: + mutex_unlock(&cantr_lock); + break; + + case NETDEV_UNREGISTER: + mutex_lock(&cantr_lock); + if (priv->cantr) + _can_transceiver_detach(priv->cantr); + priv->cantr = 0; + mutex_unlock(&cantr_lock); + break; + } + return NOTIFY_DONE; +} + +/* notifier block for netdevice event */ +static struct notifier_block can_transceiver_notifier __read_mostly = { + .notifier_call = can_transceiver_notifications, +}; + +/* + * sysfs + */ +static ssize_t show_max_bitrate(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct can_transceiver *cantr = to_can_transceiver(dev); + + return sprintf(buf, "%i\n", can_transceiver_get_max_bitrate(cantr)); +} + +static ssize_t show_mode(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct can_transceiver *cantr = to_can_transceiver(dev); + + return sprintf(buf, "%i\n", cantr->mode); +} + +static ssize_t store_mode(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct can_transceiver *cantr = to_can_transceiver(dev); + unsigned long new; + int ret = -EINVAL; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + ret = strict_strtoul(buf, 0, &new); + if (ret < 0) + return ret; + + ret = can_transceiver_set_mode(cantr, new); + if (ret < 0) + return ret; + return len; +} + +static ssize_t show_cantr_state(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct can_transceiver *cantr = to_can_transceiver(dev); + + return sprintf(buf, "%i\n", can_transceiver_get_state(cantr)); +} + +static ssize_t show_match(struct device *dev, + struct device_attribute *attr, char *buf) +{ + struct can_transceiver *cantr = to_can_transceiver(dev); + + return sprintf(buf, "%s\n", cantr->match_name); +} +static ssize_t store_match(struct device *dev, + struct device_attribute *attr, const char *buf, size_t len) +{ + struct can_transceiver *cantr = to_can_transceiver(dev); + char *tmp, *p; + + if (!capable(CAP_NET_ADMIN)) + return -EPERM; + + tmp = kstrdup(buf, GFP_KERNEL); + for (p = &tmp[len-1]; (p >= tmp) && strchr(" \t\r\n\v\f", *p); --p) + *p = 0; + can_transceiver_set_match_name(cantr, tmp); + kfree(tmp); + return len; +} + +static struct device_attribute class_dev_attrs[] = { + __ATTR(max_bitrate, S_IRUGO, show_max_bitrate, 0), + __ATTR(mode, S_IRUGO | S_IWUSR | S_IWGRP, show_mode, store_mode), + __ATTR(state, S_IRUGO, show_cantr_state, 0), + __ATTR(match, S_IRUGO | S_IWUSR, show_match, store_match), + { }, +}; + +/* + * CLASS interface + */ +static void cantr_release(struct device *dev) +{ +} + +int can_transceiver_register(struct can_transceiver *cantr) +{ + int id, ret; + + if (idr_pre_get(&cantr_idr, GFP_KERNEL) == 0) { + ret = -ENOMEM; + goto fail_idr; + } + mutex_lock(&cantr_lock); + ret = idr_get_new(&cantr_idr, cantr, &id); + mutex_unlock(&cantr_lock); + if (ret < 0) + goto fail_idr; + + id = id & MAX_ID_MASK; + cantr->id = id; + cantr->dev.class = cantr_class; + cantr->dev.release = cantr_release; + dev_set_name(&cantr->dev, "cantr%d", id); + mutex_init(&cantr->lock); + ret = device_register(&cantr->dev); + if (ret < 0) + goto fail_register; + + dev_notice(&cantr->dev, "loaded [%s]\n", + cantr->dev.parent ? dev_name(cantr->dev.parent) : "<>"); + return 0; + +fail_register: + dev_err(cantr->dev.parent, + "can transceiver core: unable to register, ret %i\n", ret); + mutex_lock(&cantr_lock); + idr_remove(&cantr_idr, id); + mutex_unlock(&cantr_lock); +fail_idr: + return ret; +} +EXPORT_SYMBOL_GPL(can_transceiver_register); + +void can_transceiver_unregister(struct can_transceiver *cantr) +{ + struct net_device *netdev; + /* unregister device with lock, so no other CAN netdev can attach. */ + mutex_lock(&cantr_lock); + netdev = cantr->netdev; + if (netdev) { + dev_hold(netdev); + _can_transceiver_detach(cantr); + dev_put(netdev); + } + + device_unregister(&cantr->dev); + idr_remove(&cantr_idr, cantr->id); + mutex_unlock(&cantr_lock); + + kfree(cantr->match_name); + cantr->match_name = 0; + + dev_notice(&cantr->dev, "unloaded\n"); +} +EXPORT_SYMBOL_GPL(can_transceiver_unregister); + +/* + * can_transceiver_start_mod - initialize can transceiver subsystem + */ +static int __init can_transceiver_start_mod(void) +{ + int ret; + + BUG_ON(cantr_class); + cantr_class = class_create(THIS_MODULE, "can-transceiver"); + if (IS_ERR(cantr_class)) { + printk(KERN_ERR "%s: couldn't create class\n", __FILE__); + ret = PTR_ERR(cantr_class); + goto fail_class; + } + cantr_class->dev_attrs = class_dev_attrs; + ret = register_netdevice_notifier(&can_transceiver_notifier); + if (ret < 0) + goto fail_notifier; + /*cantr_class->suspend = cantr_suspend;*/ + /*cantr_class->resume = cantr_resume;*/ + return 0; + +fail_notifier: + class_destroy(cantr_class); +fail_class: + return ret; +} + +/* + * can_transceiver_stop_mod - stop can transceiver subsystem + */ +static void __exit can_transceiver_stop_mod(void) +{ + unregister_netdevice_notifier(&can_transceiver_notifier); + class_destroy(cantr_class); +} + +subsys_initcall(can_transceiver_start_mod); +module_exit(can_transceiver_stop_mod); + +MODULE_AUTHOR("Kurt Van Dijck <[email protected]>"); +MODULE_DESCRIPTION("CAN transceiver class support"); +MODULE_LICENSE("GPL"); + _______________________________________________ Socketcan-core mailing list [email protected] https://lists.berlios.de/mailman/listinfo/socketcan-core
