You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
304 lines
7.0 KiB
304 lines
7.0 KiB
/* |
|
* Copyright (C) 2012 CERN (www.cern.ch) |
|
* Author: Alessandro Rubini <rubini@gnudd.com> |
|
* |
|
* Released according to the GNU GPL, version 2 or any later version. |
|
* |
|
* This work is part of the White Rabbit project, a research effort led |
|
* by CERN, the European Institute for Nuclear Research. |
|
*/ |
|
#include <linux/kernel.h> |
|
#include <linux/module.h> |
|
#include <linux/slab.h> |
|
#include <linux/init.h> |
|
#include <linux/device.h> |
|
#include <linux/fmc.h> |
|
|
|
static int fmc_check_version(unsigned long version, const char *name) |
|
{ |
|
if (__FMC_MAJOR(version) != FMC_MAJOR) { |
|
pr_err("%s: \"%s\" has wrong major (has %li, expected %i)\n", |
|
__func__, name, __FMC_MAJOR(version), FMC_MAJOR); |
|
return -EINVAL; |
|
} |
|
|
|
if (__FMC_MINOR(version) != FMC_MINOR) |
|
pr_info("%s: \"%s\" has wrong minor (has %li, expected %i)\n", |
|
__func__, name, __FMC_MINOR(version), FMC_MINOR); |
|
return 0; |
|
} |
|
|
|
static int fmc_uevent(struct device *dev, struct kobj_uevent_env *env) |
|
{ |
|
/* struct fmc_device *fdev = to_fmc_device(dev); */ |
|
|
|
/* FIXME: The MODALIAS */ |
|
add_uevent_var(env, "MODALIAS=%s", "fmc"); |
|
return 0; |
|
} |
|
|
|
static int fmc_probe(struct device *dev) |
|
{ |
|
struct fmc_driver *fdrv = to_fmc_driver(dev->driver); |
|
struct fmc_device *fdev = to_fmc_device(dev); |
|
|
|
return fdrv->probe(fdev); |
|
} |
|
|
|
static int fmc_remove(struct device *dev) |
|
{ |
|
struct fmc_driver *fdrv = to_fmc_driver(dev->driver); |
|
struct fmc_device *fdev = to_fmc_device(dev); |
|
|
|
return fdrv->remove(fdev); |
|
} |
|
|
|
static void fmc_shutdown(struct device *dev) |
|
{ |
|
/* not implemented but mandatory */ |
|
} |
|
|
|
static struct bus_type fmc_bus_type = { |
|
.name = "fmc", |
|
.match = fmc_match, |
|
.uevent = fmc_uevent, |
|
.probe = fmc_probe, |
|
.remove = fmc_remove, |
|
.shutdown = fmc_shutdown, |
|
}; |
|
|
|
static void fmc_release(struct device *dev) |
|
{ |
|
struct fmc_device *fmc = container_of(dev, struct fmc_device, dev); |
|
|
|
kfree(fmc); |
|
} |
|
|
|
/* |
|
* The eeprom is exported in sysfs, through a binary attribute |
|
*/ |
|
|
|
static ssize_t fmc_read_eeprom(struct file *file, struct kobject *kobj, |
|
struct bin_attribute *bin_attr, |
|
char *buf, loff_t off, size_t count) |
|
{ |
|
struct device *dev; |
|
struct fmc_device *fmc; |
|
int eelen; |
|
|
|
dev = container_of(kobj, struct device, kobj); |
|
fmc = container_of(dev, struct fmc_device, dev); |
|
eelen = fmc->eeprom_len; |
|
if (off > eelen) |
|
return -ESPIPE; |
|
if (off == eelen) |
|
return 0; /* EOF */ |
|
if (off + count > eelen) |
|
count = eelen - off; |
|
memcpy(buf, fmc->eeprom + off, count); |
|
return count; |
|
} |
|
|
|
static ssize_t fmc_write_eeprom(struct file *file, struct kobject *kobj, |
|
struct bin_attribute *bin_attr, |
|
char *buf, loff_t off, size_t count) |
|
{ |
|
struct device *dev; |
|
struct fmc_device *fmc; |
|
|
|
dev = container_of(kobj, struct device, kobj); |
|
fmc = container_of(dev, struct fmc_device, dev); |
|
return fmc->op->write_ee(fmc, off, buf, count); |
|
} |
|
|
|
static struct bin_attribute fmc_eeprom_attr = { |
|
.attr = { .name = "eeprom", .mode = S_IRUGO | S_IWUSR, }, |
|
.size = 8192, /* more or less standard */ |
|
.read = fmc_read_eeprom, |
|
.write = fmc_write_eeprom, |
|
}; |
|
|
|
/* |
|
* Functions for client modules follow |
|
*/ |
|
|
|
int fmc_driver_register(struct fmc_driver *drv) |
|
{ |
|
if (fmc_check_version(drv->version, drv->driver.name)) |
|
return -EINVAL; |
|
drv->driver.bus = &fmc_bus_type; |
|
return driver_register(&drv->driver); |
|
} |
|
EXPORT_SYMBOL(fmc_driver_register); |
|
|
|
void fmc_driver_unregister(struct fmc_driver *drv) |
|
{ |
|
driver_unregister(&drv->driver); |
|
} |
|
EXPORT_SYMBOL(fmc_driver_unregister); |
|
|
|
/* |
|
* When a device set is registered, all eeproms must be read |
|
* and all FRUs must be parsed |
|
*/ |
|
int fmc_device_register_n(struct fmc_device **devs, int n) |
|
{ |
|
struct fmc_device *fmc, **devarray; |
|
uint32_t device_id; |
|
int i, ret = 0; |
|
|
|
if (n < 1) |
|
return 0; |
|
|
|
/* Check the version of the first data structure (function prints) */ |
|
if (fmc_check_version(devs[0]->version, devs[0]->carrier_name)) |
|
return -EINVAL; |
|
|
|
devarray = kmemdup(devs, n * sizeof(*devs), GFP_KERNEL); |
|
if (!devarray) |
|
return -ENOMEM; |
|
|
|
/* Make all other checks before continuing, for all devices */ |
|
for (i = 0; i < n; i++) { |
|
fmc = devarray[i]; |
|
if (!fmc->hwdev) { |
|
pr_err("%s: device nr. %i has no hwdev pointer\n", |
|
__func__, i); |
|
ret = -EINVAL; |
|
break; |
|
} |
|
if (fmc->flags & FMC_DEVICE_NO_MEZZANINE) { |
|
dev_info(fmc->hwdev, "absent mezzanine in slot %d\n", |
|
fmc->slot_id); |
|
continue; |
|
} |
|
if (!fmc->eeprom) { |
|
dev_err(fmc->hwdev, "no eeprom provided for slot %i\n", |
|
fmc->slot_id); |
|
ret = -EINVAL; |
|
} |
|
if (!fmc->eeprom_addr) { |
|
dev_err(fmc->hwdev, "no eeprom_addr for slot %i\n", |
|
fmc->slot_id); |
|
ret = -EINVAL; |
|
} |
|
if (!fmc->carrier_name || !fmc->carrier_data || |
|
!fmc->device_id) { |
|
dev_err(fmc->hwdev, |
|
"deivce nr %i: carrier name, " |
|
"data or dev_id not set\n", i); |
|
ret = -EINVAL; |
|
} |
|
if (ret) |
|
break; |
|
|
|
} |
|
if (ret) { |
|
kfree(devarray); |
|
return ret; |
|
} |
|
|
|
/* Validation is ok. Now init and register the devices */ |
|
for (i = 0; i < n; i++) { |
|
fmc = devarray[i]; |
|
|
|
fmc->nr_slots = n; /* each slot must know how many are there */ |
|
fmc->devarray = devarray; |
|
|
|
device_initialize(&fmc->dev); |
|
fmc->dev.release = fmc_release; |
|
fmc->dev.parent = fmc->hwdev; |
|
|
|
/* Fill the identification stuff (may fail) */ |
|
fmc_fill_id_info(fmc); |
|
|
|
fmc->dev.bus = &fmc_bus_type; |
|
|
|
/* Name from mezzanine info or carrier info. Or 0,1,2.. */ |
|
device_id = fmc->device_id; |
|
if (!fmc->mezzanine_name) |
|
dev_set_name(&fmc->dev, "fmc-%04x", device_id); |
|
else |
|
dev_set_name(&fmc->dev, "%s-%04x", fmc->mezzanine_name, |
|
device_id); |
|
ret = device_add(&fmc->dev); |
|
if (ret < 0) { |
|
dev_err(fmc->hwdev, "Slot %i: Failed in registering " |
|
"\"%s\"\n", fmc->slot_id, fmc->dev.kobj.name); |
|
goto out; |
|
} |
|
ret = sysfs_create_bin_file(&fmc->dev.kobj, &fmc_eeprom_attr); |
|
if (ret < 0) { |
|
dev_err(&fmc->dev, "Failed in registering eeprom\n"); |
|
goto out1; |
|
} |
|
/* This device went well, give information to the user */ |
|
fmc_dump_eeprom(fmc); |
|
fmc_dump_sdb(fmc); |
|
} |
|
return 0; |
|
|
|
out1: |
|
device_del(&fmc->dev); |
|
out: |
|
fmc_free_id_info(fmc); |
|
put_device(&fmc->dev); |
|
|
|
kfree(devarray); |
|
for (i--; i >= 0; i--) { |
|
sysfs_remove_bin_file(&devs[i]->dev.kobj, &fmc_eeprom_attr); |
|
device_del(&devs[i]->dev); |
|
fmc_free_id_info(devs[i]); |
|
put_device(&devs[i]->dev); |
|
} |
|
return ret; |
|
|
|
} |
|
EXPORT_SYMBOL(fmc_device_register_n); |
|
|
|
int fmc_device_register(struct fmc_device *fmc) |
|
{ |
|
return fmc_device_register_n(&fmc, 1); |
|
} |
|
EXPORT_SYMBOL(fmc_device_register); |
|
|
|
void fmc_device_unregister_n(struct fmc_device **devs, int n) |
|
{ |
|
int i; |
|
|
|
if (n < 1) |
|
return; |
|
|
|
/* Free devarray first, not used by the later loop */ |
|
kfree(devs[0]->devarray); |
|
|
|
for (i = 0; i < n; i++) { |
|
sysfs_remove_bin_file(&devs[i]->dev.kobj, &fmc_eeprom_attr); |
|
device_del(&devs[i]->dev); |
|
fmc_free_id_info(devs[i]); |
|
put_device(&devs[i]->dev); |
|
} |
|
} |
|
EXPORT_SYMBOL(fmc_device_unregister_n); |
|
|
|
void fmc_device_unregister(struct fmc_device *fmc) |
|
{ |
|
fmc_device_unregister_n(&fmc, 1); |
|
} |
|
EXPORT_SYMBOL(fmc_device_unregister); |
|
|
|
/* Init and exit are trivial */ |
|
static int fmc_init(void) |
|
{ |
|
return bus_register(&fmc_bus_type); |
|
} |
|
|
|
static void fmc_exit(void) |
|
{ |
|
bus_unregister(&fmc_bus_type); |
|
} |
|
|
|
module_init(fmc_init); |
|
module_exit(fmc_exit); |
|
|
|
MODULE_LICENSE("GPL");
|
|
|