USB: cxacru: export detailed device info through sysfs
[pandora-kernel.git] / drivers / usb / atm / cxacru.c
index 3dfa3e4..cdcdfed 100644 (file)
 #include <linux/errno.h>
 #include <linux/slab.h>
 #include <linux/init.h>
-#include <linux/device.h>      /* FIXME: linux/firmware.h should include it itself */
+#include <linux/device.h>
 #include <linux/firmware.h>
 #include <linux/mutex.h>
 
 #include "usbatm.h"
 
-#define DRIVER_AUTHOR  "Roman Kagan, David Woodhouse, Duncan Sands"
-#define DRIVER_VERSION "0.2"
+#define DRIVER_AUTHOR  "Roman Kagan, David Woodhouse, Duncan Sands, Simon Arlott"
+#define DRIVER_VERSION "0.3"
 #define DRIVER_DESC    "Conexant AccessRunner ADSL USB modem driver"
 
 static const char cxacru_driver_name[] = "cxacru";
@@ -64,7 +64,7 @@ static const char cxacru_driver_name[] = "cxacru";
 #define SDRAM_ENA      0x1
 
 #define CMD_TIMEOUT    2000    /* msecs */
-#define POLL_INTERVAL  5000    /* msecs */
+#define POLL_INTERVAL  1       /* secs */
 
 /* commands for interaction with the modem through the control channel before
  * firmware is loaded  */
@@ -159,6 +159,7 @@ struct cxacru_data {
 
        int line_status;
        struct delayed_work poll_work;
+       u32 card_info[CXINF_MAX];
 
        /* contol handles */
        struct mutex cm_serialize;
@@ -170,6 +171,151 @@ struct cxacru_data {
        struct completion snd_done;
 };
 
+/* Card info exported through sysfs */
+#define CXACRU__ATTR_INIT(_name) \
+static DEVICE_ATTR(_name, S_IRUGO, cxacru_sysfs_show_##_name, NULL)
+
+#define CXACRU_ATTR_INIT(_value, _type, _name) \
+static ssize_t cxacru_sysfs_show_##_name(struct device *dev, \
+       struct device_attribute *attr, char *buf) \
+{ \
+       struct usb_interface *intf = to_usb_interface(dev); \
+       struct usbatm_data *usbatm_instance = usb_get_intfdata(intf); \
+       struct cxacru_data *instance = usbatm_instance->driver_data; \
+       return cxacru_sysfs_showattr_##_type(instance->card_info[_value], buf); \
+} \
+CXACRU__ATTR_INIT(_name)
+
+#define CXACRU_ATTR_CREATE(_v, _t, _name) CXACRU_DEVICE_CREATE_FILE(_name)
+#define CXACRU__ATTR_CREATE(_name)        CXACRU_DEVICE_CREATE_FILE(_name)
+
+#define CXACRU_ATTR_REMOVE(_v, _t, _name) CXACRU_DEVICE_REMOVE_FILE(_name)
+#define CXACRU__ATTR_REMOVE(_name)        CXACRU_DEVICE_REMOVE_FILE(_name)
+
+static ssize_t cxacru_sysfs_showattr_u32(u32 value, char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%u\n", value);
+}
+
+static ssize_t cxacru_sysfs_showattr_s8(s8 value, char *buf)
+{
+       return snprintf(buf, PAGE_SIZE, "%d\n", value);
+}
+
+static ssize_t cxacru_sysfs_showattr_dB(s16 value, char *buf)
+{
+       if (unlikely(value < 0)) {
+               return snprintf(buf, PAGE_SIZE, "%d.%02u\n",
+                                               value / 100, -value % 100);
+       } else {
+               return snprintf(buf, PAGE_SIZE, "%d.%02u\n",
+                                               value / 100, value % 100);
+       }
+}
+
+static ssize_t cxacru_sysfs_showattr_bool(u32 value, char *buf)
+{
+       switch (value) {
+       case 0: return snprintf(buf, PAGE_SIZE, "no\n");
+       case 1: return snprintf(buf, PAGE_SIZE, "yes\n");
+       default: return 0;
+       }
+}
+
+static ssize_t cxacru_sysfs_showattr_LINK(u32 value, char *buf)
+{
+       switch (value) {
+       case 1: return snprintf(buf, PAGE_SIZE, "not connected\n");
+       case 2: return snprintf(buf, PAGE_SIZE, "connected\n");
+       case 3: return snprintf(buf, PAGE_SIZE, "lost\n");
+       default: return snprintf(buf, PAGE_SIZE, "unknown (%u)\n", value);
+       }
+}
+
+static ssize_t cxacru_sysfs_showattr_LINE(u32 value, char *buf)
+{
+       switch (value) {
+       case 0: return snprintf(buf, PAGE_SIZE, "down\n");
+       case 1: return snprintf(buf, PAGE_SIZE, "attempting to activate\n");
+       case 2: return snprintf(buf, PAGE_SIZE, "training\n");
+       case 3: return snprintf(buf, PAGE_SIZE, "channel analysis\n");
+       case 4: return snprintf(buf, PAGE_SIZE, "exchange\n");
+       case 5: return snprintf(buf, PAGE_SIZE, "up\n");
+       case 6: return snprintf(buf, PAGE_SIZE, "waiting\n");
+       case 7: return snprintf(buf, PAGE_SIZE, "initialising\n");
+       default: return snprintf(buf, PAGE_SIZE, "unknown (%u)\n", value);
+       }
+}
+
+static ssize_t cxacru_sysfs_showattr_MODU(u32 value, char *buf)
+{
+       switch (value) {
+       case 0: return 0;
+       case 1: return snprintf(buf, PAGE_SIZE, "ANSI T1.413\n");
+       case 2: return snprintf(buf, PAGE_SIZE, "ITU-T G.992.1 (G.DMT)\n");
+       case 3: return snprintf(buf, PAGE_SIZE, "ITU-T G.992.2 (G.LITE)\n");
+       default: return snprintf(buf, PAGE_SIZE, "unknown (%u)\n", value);
+       }
+}
+
+/*
+ * This could use MAC_ADDRESS_HIGH and MAC_ADDRESS_LOW, but since
+ * this data is already in atm_dev there's no point.
+ *
+ * MAC_ADDRESS_HIGH = 0x????5544
+ * MAC_ADDRESS_LOW  = 0x33221100
+ * Where 00-55 are bytes 0-5 of the MAC.
+ */
+static ssize_t cxacru_sysfs_show_mac_address(struct device *dev,
+       struct device_attribute *attr, char *buf)
+{
+       struct usb_interface *intf = to_usb_interface(dev);
+       struct usbatm_data *usbatm_instance = usb_get_intfdata(intf);
+       struct atm_dev *atm_dev = usbatm_instance->atm_dev;
+
+       return snprintf(buf, PAGE_SIZE, "%02x:%02x:%02x:%02x:%02x:%02x\n",
+                       atm_dev->esi[0], atm_dev->esi[1], atm_dev->esi[2],
+                       atm_dev->esi[3], atm_dev->esi[4], atm_dev->esi[5]);
+}
+
+/*
+ * All device attributes are included in CXACRU_ALL_FILES
+ * so that the same list can be used multiple times:
+ *     INIT   (define the device attributes)
+ *     CREATE (create all the device files)
+ *     REMOVE (remove all the device files)
+ *
+ * With the last two being defined as needed in the functions
+ * they are used in before calling CXACRU_ALL_FILES()
+ */
+#define CXACRU_ALL_FILES(_action) \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_RATE,           u32,  downstream_rate); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_RATE,             u32,  upstream_rate); \
+CXACRU_ATTR_##_action(CXINF_LINK_STATUS,               LINK, link_status); \
+CXACRU_ATTR_##_action(CXINF_LINE_STATUS,               LINE, line_status); \
+CXACRU__ATTR_##_action(                                      mac_address); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_SNR_MARGIN,       dB,   upstream_snr_margin); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_SNR_MARGIN,     dB,   downstream_snr_margin); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_ATTENUATION,      dB,   upstream_attenuation); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_ATTENUATION,    dB,   downstream_attenuation); \
+CXACRU_ATTR_##_action(CXINF_TRANSMITTER_POWER,         s8,   transmitter_power); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_BITS_PER_FRAME,   u32,  upstream_bits_per_frame); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_BITS_PER_FRAME, u32,  downstream_bits_per_frame); \
+CXACRU_ATTR_##_action(CXINF_STARTUP_ATTEMPTS,          u32,  startup_attempts); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_CRC_ERRORS,       u32,  upstream_crc_errors); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_CRC_ERRORS,     u32,  downstream_crc_errors); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_FEC_ERRORS,       u32,  upstream_fec_errors); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_FEC_ERRORS,     u32,  downstream_fec_errors); \
+CXACRU_ATTR_##_action(CXINF_UPSTREAM_HEC_ERRORS,       u32,  upstream_hec_errors); \
+CXACRU_ATTR_##_action(CXINF_DOWNSTREAM_HEC_ERRORS,     u32,  downstream_hec_errors); \
+CXACRU_ATTR_##_action(CXINF_LINE_STARTABLE,            bool, line_startable); \
+CXACRU_ATTR_##_action(CXINF_MODULATION,                MODU, modulation); \
+CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND,              u32,  adsl_headend); \
+CXACRU_ATTR_##_action(CXINF_ADSL_HEADEND_ENVIRONMENT,  u32,  adsl_headend_environment); \
+CXACRU_ATTR_##_action(CXINF_CONTROLLER_VERSION,        u32,  adsl_controller_version);
+
+CXACRU_ALL_FILES(INIT);
+
 /* the following three functions are stolen from drivers/usb/core/message.c */
 static void cxacru_blocking_completion(struct urb *urb)
 {
@@ -395,6 +541,8 @@ static void cxacru_poll_status(struct work_struct *work)
                goto reschedule;
        }
 
+       memcpy(instance->card_info, buf, sizeof(instance->card_info));
+
        if (instance->line_status == buf[CXINF_LINE_STATUS])
                goto reschedule;
 
@@ -449,7 +597,8 @@ static void cxacru_poll_status(struct work_struct *work)
                break;
        }
 reschedule:
-       schedule_delayed_work(&instance->poll_work, msecs_to_jiffies(POLL_INTERVAL));
+       schedule_delayed_work(&instance->poll_work,
+                       round_jiffies_relative(msecs_to_jiffies(POLL_INTERVAL*1000)));
 }
 
 static int cxacru_fw(struct usb_device *usb_dev, enum cxacru_fw_request fw,
@@ -684,6 +833,7 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance,
 
        instance->usbatm = usbatm_instance;
        instance->modem_type = (struct cxacru_modem_type *) id->driver_info;
+       memset(instance->card_info, 0, sizeof(instance->card_info));
 
        instance->rcv_buf = (u8 *) __get_free_page(GFP_KERNEL);
        if (!instance->rcv_buf) {
@@ -710,6 +860,13 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance,
                goto fail;
        }
 
+       #define CXACRU_DEVICE_CREATE_FILE(_name) \
+               ret = device_create_file(&intf->dev, &dev_attr_##_name); \
+               if (unlikely(ret)) \
+                       goto fail_sysfs;
+       CXACRU_ALL_FILES(CREATE);
+       #undef CXACRU_DEVICE_CREATE_FILE
+
        usb_fill_int_urb(instance->rcv_urb,
                        usb_dev, usb_rcvintpipe(usb_dev, CXACRU_EP_CMD),
                        instance->rcv_buf, PAGE_SIZE,
@@ -730,6 +887,14 @@ static int cxacru_bind(struct usbatm_data *usbatm_instance,
 
        return 0;
 
+ fail_sysfs:
+       dbg("cxacru_bind: device_create_file failed (%d)\n", ret);
+
+       #define CXACRU_DEVICE_REMOVE_FILE(_name) \
+               device_remove_file(&intf->dev, &dev_attr_##_name);
+       CXACRU_ALL_FILES(REMOVE);
+       #undef CXACRU_DEVICE_REVOVE_FILE
+
  fail:
        free_page((unsigned long) instance->snd_buf);
        free_page((unsigned long) instance->rcv_buf);
@@ -762,6 +927,12 @@ static void cxacru_unbind(struct usbatm_data *usbatm_instance,
 
        free_page((unsigned long) instance->snd_buf);
        free_page((unsigned long) instance->rcv_buf);
+
+       #define CXACRU_DEVICE_REMOVE_FILE(_name) \
+               device_remove_file(&intf->dev, &dev_attr_##_name);
+       CXACRU_ALL_FILES(REMOVE);
+       #undef CXACRU_DEVICE_REVOVE_FILE
+
        kfree(instance);
 
        usbatm_instance->driver_data = NULL;