mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git
synced 2024-12-28 16:56:26 +00:00
staging: fieldbus: Delete unused driver
Sven Van Asbroeck contributed this driver in 2019. The following reasons lead to the removal: - This driver generates maintenance workload - only 11 patches during the last 3 years. Part of the patches seem to be motivated because of maintenance (for example - remove deprecated function) - Maintainer lost interest, last "Reviewed-by:" is May 2021 - no blog about usage of this driver The staging subsystem is the way for drivers into the kernel - at current speed and interest this is never going to happen. I think that fieldbus is an interesting topic. But when almost nobody cares about this driver, it does not make sense to keep it. Please consider that support will remain for years in the longterm kernels. Link: https://lore.kernel.org/linux-staging/96ae2b42-c0ce-4d9a-8933-eb874dc5589b@gmail.com/T/#u Signed-off-by: Philipp Hortmann <philipp.g.hortmann@gmail.com> Link: https://lore.kernel.org/r/20241102191436.23177-1-philipp.g.hortmann@gmail.com Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
This commit is contained in:
parent
41e883c137
commit
8898f64f7a
11
MAINTAINERS
11
MAINTAINERS
@ -21930,17 +21930,6 @@ L: linux-media@vger.kernel.org
|
||||
S: Maintained
|
||||
F: drivers/staging/media/atomisp/
|
||||
|
||||
STAGING - FIELDBUS SUBSYSTEM
|
||||
M: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
S: Maintained
|
||||
F: drivers/staging/fieldbus/*
|
||||
F: drivers/staging/fieldbus/Documentation/
|
||||
|
||||
STAGING - HMS ANYBUS-S BUS
|
||||
M: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
S: Maintained
|
||||
F: drivers/staging/fieldbus/anybuss/
|
||||
|
||||
STAGING - INDUSTRIAL IO
|
||||
M: Jonathan Cameron <jic23@kernel.org>
|
||||
L: linux-iio@vger.kernel.org
|
||||
|
@ -46,8 +46,6 @@ source "drivers/staging/vc04_services/Kconfig"
|
||||
|
||||
source "drivers/staging/axis-fifo/Kconfig"
|
||||
|
||||
source "drivers/staging/fieldbus/Kconfig"
|
||||
|
||||
source "drivers/staging/vme_user/Kconfig"
|
||||
|
||||
source "drivers/staging/gpib/Kconfig"
|
||||
|
@ -13,5 +13,4 @@ obj-$(CONFIG_MOST) += most/
|
||||
obj-$(CONFIG_GREYBUS) += greybus/
|
||||
obj-$(CONFIG_BCM2835_VCHIQ) += vc04_services/
|
||||
obj-$(CONFIG_XIL_AXIS_FIFO) += axis-fifo/
|
||||
obj-$(CONFIG_FIELDBUS_DEV) += fieldbus/
|
||||
obj-$(CONFIG_GPIB) += gpib/
|
||||
|
@ -1,31 +0,0 @@
|
||||
What: /dev/fieldbus_devX
|
||||
Date: December 2018
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The cdev interface to drivers for Fieldbus Device Memory
|
||||
(aka. Process Memory).
|
||||
|
||||
The following file operations are supported:
|
||||
|
||||
open(2)
|
||||
Create an I/O context associated with the file descriptor.
|
||||
|
||||
read(2)
|
||||
Read from Process Memory's "read area".
|
||||
Clears POLLERR | POLLPRI from the file descriptor.
|
||||
|
||||
write(2)
|
||||
Write to Process Memory's "write area".
|
||||
|
||||
poll(2), select(2), epoll_wait(2) etc.
|
||||
When a "Process Memory Read Area Changed" event occurs,
|
||||
POLLERR | POLLPRI will be set on the file descriptor.
|
||||
Note that POLLIN | POLLOUT events are always set, because the
|
||||
process memory area is always readable and writable.
|
||||
|
||||
close(2)
|
||||
Free up the I/O context that was associated
|
||||
with the file descriptor.
|
||||
|
||||
Users: TBD
|
@ -1,62 +0,0 @@
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/card_name
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
Human-readable name of the Fieldbus Device.
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/fieldbus_type
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The type of fieldbus implemented by this device.
|
||||
Possible values:
|
||||
'unknown'
|
||||
'profinet'
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/fieldbus_id
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The unique fieldbus id associated with this device.
|
||||
The exact format of this id is fieldbus type dependent, e.g.
|
||||
a mac address for profinet.
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/read_area_size
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The size, in bytes, of the Process Memory read area.
|
||||
Note: this area is accessible by reading from the associated
|
||||
character device (/dev/fieldbus_devX).
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/write_area_size
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
The size, in bytes, of the Process Memory write area.
|
||||
Note: this area is accessible by writing to the associated
|
||||
character device (/dev/fieldbus_devX)
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/online
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
Whether the fieldbus is online or offline.
|
||||
Possible values:
|
||||
'1' meaning 'online'
|
||||
'0' meaning 'offline'
|
||||
Note: an uevent is generated when this property changes.
|
||||
|
||||
What: /sys/class/fieldbus_dev/fieldbus_devX/enabled
|
||||
KernelVersion: 5.1 (staging)
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
||||
Description:
|
||||
Whether the device is enabled (power on) or
|
||||
disabled (power off).
|
||||
Possible values:
|
||||
'1' meaning enabled
|
||||
'0' meaning disabled
|
||||
Normally a r/o property, but optionally r/w:
|
||||
Writing '1' enables the device (power on) with default
|
||||
settings.
|
||||
Writing '0' disables the card (power off).
|
@ -1,71 +0,0 @@
|
||||
* Arcx Anybus-S controller
|
||||
|
||||
This chip communicates with the SoC over a parallel bus. It is
|
||||
expected that its Device Tree node is specified as the child of a node
|
||||
corresponding to the parallel bus used for communication.
|
||||
|
||||
Required properties:
|
||||
--------------------
|
||||
|
||||
- compatible : The following chip-specific string:
|
||||
"arcx,anybus-controller"
|
||||
|
||||
- reg : three areas:
|
||||
index 0: bus memory area where the cpld registers are located.
|
||||
index 1: bus memory area of the first host's dual-port ram.
|
||||
index 2: bus memory area of the second host's dual-port ram.
|
||||
|
||||
- reset-gpios : the GPIO pin connected to the reset line of the controller.
|
||||
|
||||
- interrupts : two interrupts:
|
||||
index 0: interrupt connected to the first host
|
||||
index 1: interrupt connected to the second host
|
||||
Generic interrupt client node bindings are described in
|
||||
interrupt-controller/interrupts.txt
|
||||
|
||||
Optional: use of subnodes
|
||||
-------------------------
|
||||
|
||||
The card connected to a host may need additional properties. These can be
|
||||
specified in subnodes to the controller node.
|
||||
|
||||
The subnodes are identified by the standard 'reg' property. Which information
|
||||
exactly can be specified depends on the bindings for the function driver
|
||||
for the subnode.
|
||||
|
||||
Required controller node properties when using subnodes:
|
||||
- #address-cells: should be one.
|
||||
- #size-cells: should be zero.
|
||||
|
||||
Required subnode properties:
|
||||
- reg: Must contain the host index of the card this subnode describes:
|
||||
<0> for the first host on the controller
|
||||
<1> for the second host on the controller
|
||||
Note that only a single card can be plugged into a host, so the host
|
||||
index uniquely describes the card location.
|
||||
|
||||
Example of usage:
|
||||
-----------------
|
||||
|
||||
This example places the bridge on top of the i.MX WEIM parallel bus, see:
|
||||
Documentation/devicetree/bindings/memory-controllers/fsl/fsl,imx-weim.yaml
|
||||
|
||||
&weim {
|
||||
controller@0,0 {
|
||||
compatible = "arcx,anybus-controller";
|
||||
reg = <0 0 0x100>, <0 0x400000 0x800>, <1 0x400000 0x800>;
|
||||
reset-gpios = <&gpio5 2 GPIO_ACTIVE_HIGH>;
|
||||
interrupt-parent = <&gpio1>;
|
||||
interrupts = <1 IRQ_TYPE_LEVEL_LOW>, <5 IRQ_TYPE_LEVEL_LOW>;
|
||||
/* fsl,weim-cs-timing is a i.MX WEIM bus specific property */
|
||||
fsl,weim-cs-timing = <0x024400b1 0x00001010 0x20081100
|
||||
0x00000000 0xa0000240 0x00000000>;
|
||||
/* optional subnode for a card plugged into the first host */
|
||||
#address-cells = <1>;
|
||||
#size-cells = <0>;
|
||||
card@0 {
|
||||
reg = <0>;
|
||||
/* card specific properties go here */
|
||||
};
|
||||
};
|
||||
};
|
@ -1,66 +0,0 @@
|
||||
Fieldbus-Device Subsystem
|
||||
============================================
|
||||
|
||||
Part 0 - What is a Fieldbus Device ?
|
||||
------------------------------------
|
||||
|
||||
Fieldbus is the name of a family of industrial computer network protocols used
|
||||
for real-time distributed control, standardized as IEC 61158.
|
||||
|
||||
A complex automated industrial system -- such as manufacturing assembly line --
|
||||
usually needs a distributed control system -- an organized hierarchy of
|
||||
controller systems -- to function. In this hierarchy, there is usually a
|
||||
Human Machine Interface (HMI) at the top, where an operator can monitor or
|
||||
operate the system. This is typically linked to a middle layer of programmable
|
||||
logic controllers (PLC) via a non-time-critical communications system
|
||||
(e.g. Ethernet). At the bottom of the control chain is the fieldbus that links
|
||||
the PLCs to the components that actually do the work, such as sensors,
|
||||
actuators, electric motors, console lights, switches, valves and contactors.
|
||||
|
||||
(Source: Wikipedia)
|
||||
|
||||
A "Fieldbus Device" is such an actuator, motor, console light, switch, ...
|
||||
controlled via the Fieldbus by a PLC aka "Fieldbus Controller".
|
||||
|
||||
Communication between PLC and device typically happens via process data memory,
|
||||
separated into input and output areas. The Fieldbus then cyclically transfers
|
||||
the PLC's output area to the device's input area, and vice versa.
|
||||
|
||||
Part I - Why do we need this subsystem?
|
||||
---------------------------------------
|
||||
|
||||
Fieldbus device (client) adapters are commercially available. They allow data
|
||||
exchange with a PLC aka "Fieldbus Controller" via process data memory.
|
||||
|
||||
They are typically used when a Linux device wants to expose itself as an
|
||||
actuator, motor, console light, switch, etc. over the fieldbus.
|
||||
|
||||
The purpose of this subsystem is:
|
||||
a) present a general, standardized, extensible API/ABI to userspace; and
|
||||
b) present a convenient interface to drivers.
|
||||
|
||||
Part II - How can drivers use the subsystem?
|
||||
--------------------------------------------
|
||||
|
||||
Any driver that wants to register as a Fieldbus Device should allocate and
|
||||
populate a 'struct fieldbus_dev' (from include/linux/fieldbus_dev.h).
|
||||
Registration then happens by calling fieldbus_dev_register().
|
||||
|
||||
Part III - How can userspace use the subsystem?
|
||||
-----------------------------------------------
|
||||
|
||||
Fieldbus protocols and adapters are diverse and varied. However, they share
|
||||
a limited few common behaviours and properties. This allows us to define
|
||||
a simple interface consisting of a character device and a set of sysfs files:
|
||||
|
||||
See:
|
||||
drivers/staging/fieldbus/Documentation/ABI/sysfs-class-fieldbus-dev
|
||||
drivers/staging/fieldbus/Documentation/ABI/fieldbus-dev-cdev
|
||||
|
||||
Note that this simple interface does not provide a way to modify adapter
|
||||
configuration settings. It is therefore useful only for adapters that get their
|
||||
configuration settings some other way, e.g. non-volatile memory on the adapter,
|
||||
through the network, ...
|
||||
|
||||
At a later phase, this simple interface can easily co-exist with a future
|
||||
(netlink-based ?) configuration settings interface.
|
@ -1,19 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
menuconfig FIELDBUS_DEV
|
||||
tristate "Fieldbus Device Support"
|
||||
help
|
||||
Support for Fieldbus Device Adapters.
|
||||
|
||||
Fieldbus device (client) adapters allow data exchange with a PLC aka.
|
||||
"Fieldbus Controller" over a fieldbus (Profinet, FLNet, etc.)
|
||||
|
||||
They are typically used when a Linux device wants to expose itself
|
||||
as an actuator, motor, console light, switch, etc. over the fieldbus.
|
||||
|
||||
This framework is designed to provide a generic interface to Fieldbus
|
||||
Devices from both the Linux Kernel and the userspace.
|
||||
|
||||
If unsure, say no.
|
||||
|
||||
source "drivers/staging/fieldbus/anybuss/Kconfig"
|
||||
|
@ -1,7 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for fieldbus_dev drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_FIELDBUS_DEV) += fieldbus_dev.o anybuss/
|
||||
fieldbus_dev-y := dev_core.o
|
@ -1,5 +0,0 @@
|
||||
TODO:
|
||||
-Get more people/drivers to use the Fieldbus userspace ABI. It requires
|
||||
verification/sign-off by multiple users.
|
||||
|
||||
Contact: Sven Van Asbroeck <TheSven73@gmail.com>
|
@ -1,41 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
config HMS_ANYBUSS_BUS
|
||||
tristate "HMS Anybus-S Bus Support"
|
||||
select REGMAP
|
||||
depends on OF && FIELDBUS_DEV
|
||||
help
|
||||
Driver for the HMS Industrial Networks Anybus-S bus.
|
||||
You can attach a single Anybus-S compatible card to it, which
|
||||
typically provides fieldbus and industrial ethernet
|
||||
functionality.
|
||||
|
||||
if HMS_ANYBUSS_BUS
|
||||
|
||||
config ARCX_ANYBUS_CONTROLLER
|
||||
tristate "Arcx Anybus-S Controller"
|
||||
depends on OF && GPIOLIB && HAS_IOMEM && REGULATOR
|
||||
select REGMAP_MMIO
|
||||
help
|
||||
Select this to get support for the Arcx Anybus controller.
|
||||
It connects to the SoC via a parallel memory bus, and
|
||||
embeds up to two Anybus-S buses (slots).
|
||||
There is also a CAN power readout, unrelated to the Anybus,
|
||||
modelled as a regulator.
|
||||
|
||||
config HMS_PROFINET
|
||||
tristate "HMS Profinet IRT Controller (Anybus-S)"
|
||||
depends on FIELDBUS_DEV && HMS_ANYBUSS_BUS
|
||||
help
|
||||
If you say yes here you get support for the HMS Industrial
|
||||
Networks Profinet IRT Controller.
|
||||
|
||||
It will be registered with the kernel as a fieldbus_dev,
|
||||
so userspace can interact with it via the fieldbus_dev userspace
|
||||
interface(s).
|
||||
|
||||
This driver can also be built as a module. If so, the module
|
||||
will be called hms-profinet.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endif
|
@ -1,10 +0,0 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
#
|
||||
# Makefile for anybuss drivers.
|
||||
#
|
||||
|
||||
obj-$(CONFIG_HMS_ANYBUSS_BUS) += anybuss_core.o
|
||||
anybuss_core-y += host.o
|
||||
|
||||
obj-$(CONFIG_ARCX_ANYBUS_CONTROLLER) += arcx-anybus.o
|
||||
obj-$(CONFIG_HMS_PROFINET) += hms-profinet.o
|
@ -1,95 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Anybus-S client adapter definitions
|
||||
*
|
||||
* Copyright 2018 Arcx Inc
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_ANYBUSS_CLIENT_H__
|
||||
#define __LINUX_ANYBUSS_CLIENT_H__
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/types.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
/* move to <linux/fieldbus_dev.h> when taking this out of staging */
|
||||
#include "../fieldbus_dev.h"
|
||||
|
||||
struct anybuss_host;
|
||||
|
||||
struct anybuss_client {
|
||||
struct device dev;
|
||||
struct anybuss_host *host;
|
||||
__be16 anybus_id;
|
||||
/*
|
||||
* these can be optionally set by the client to receive event
|
||||
* notifications from the host.
|
||||
*/
|
||||
void (*on_area_updated)(struct anybuss_client *client);
|
||||
void (*on_online_changed)(struct anybuss_client *client, bool online);
|
||||
};
|
||||
|
||||
struct anybuss_client_driver {
|
||||
struct device_driver driver;
|
||||
int (*probe)(struct anybuss_client *adev);
|
||||
void (*remove)(struct anybuss_client *adev);
|
||||
u16 anybus_id;
|
||||
};
|
||||
|
||||
int anybuss_client_driver_register(struct anybuss_client_driver *drv);
|
||||
void anybuss_client_driver_unregister(struct anybuss_client_driver *drv);
|
||||
|
||||
static inline struct anybuss_client *to_anybuss_client(struct device *dev)
|
||||
{
|
||||
return container_of(dev, struct anybuss_client, dev);
|
||||
}
|
||||
|
||||
#define to_anybuss_client_driver(__drv) container_of_const(__drv, struct anybuss_client_driver, driver)
|
||||
|
||||
static inline void *
|
||||
anybuss_get_drvdata(const struct anybuss_client *client)
|
||||
{
|
||||
return dev_get_drvdata(&client->dev);
|
||||
}
|
||||
|
||||
static inline void
|
||||
anybuss_set_drvdata(struct anybuss_client *client, void *data)
|
||||
{
|
||||
dev_set_drvdata(&client->dev, data);
|
||||
}
|
||||
|
||||
int anybuss_set_power(struct anybuss_client *client, bool power_on);
|
||||
|
||||
struct anybuss_memcfg {
|
||||
u16 input_io;
|
||||
u16 input_dpram;
|
||||
u16 input_total;
|
||||
|
||||
u16 output_io;
|
||||
u16 output_dpram;
|
||||
u16 output_total;
|
||||
|
||||
enum fieldbus_dev_offl_mode offl_mode;
|
||||
};
|
||||
|
||||
int anybuss_start_init(struct anybuss_client *client,
|
||||
const struct anybuss_memcfg *cfg);
|
||||
int anybuss_finish_init(struct anybuss_client *client);
|
||||
int anybuss_read_fbctrl(struct anybuss_client *client, u16 addr,
|
||||
void *buf, size_t count);
|
||||
int anybuss_send_msg(struct anybuss_client *client, u16 cmd_num,
|
||||
const void *buf, size_t count);
|
||||
int anybuss_send_ext(struct anybuss_client *client, u16 cmd_num,
|
||||
const void *buf, size_t count);
|
||||
int anybuss_recv_msg(struct anybuss_client *client, u16 cmd_num,
|
||||
void *buf, size_t count);
|
||||
|
||||
/* these help clients make a struct file_operations */
|
||||
int anybuss_write_input(struct anybuss_client *client,
|
||||
const char __user *buf, size_t size,
|
||||
loff_t *offset);
|
||||
int anybuss_read_output(struct anybuss_client *client,
|
||||
char __user *buf, size_t size,
|
||||
loff_t *offset);
|
||||
|
||||
#endif /* __LINUX_ANYBUSS_CLIENT_H__ */
|
@ -1,47 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Anybus-S controller definitions
|
||||
*
|
||||
* Copyright 2018 Arcx Inc
|
||||
*/
|
||||
|
||||
#ifndef __LINUX_ANYBUSS_CONTROLLER_H__
|
||||
#define __LINUX_ANYBUSS_CONTROLLER_H__
|
||||
|
||||
#include <linux/device.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/*
|
||||
* To instantiate an Anybus-S host, a controller should provide the following:
|
||||
* - a reset function which resets the attached card;
|
||||
* - a regmap which provides access to the attached card's dpram;
|
||||
* - the irq of the attached card
|
||||
*/
|
||||
/**
|
||||
* struct anybuss_ops - Controller resources to instantiate an Anybus-S host
|
||||
*
|
||||
* @reset: asserts/deasserts the anybus card's reset line.
|
||||
* @regmap: provides access to the card's dual-port RAM area.
|
||||
* @irq: number of the interrupt connected to the card's interrupt line.
|
||||
* @host_idx: for multi-host controllers, the host index:
|
||||
* 0 for the first host on the controller, 1 for the second, etc.
|
||||
*/
|
||||
struct anybuss_ops {
|
||||
void (*reset)(struct device *dev, bool assert);
|
||||
struct regmap *regmap;
|
||||
int irq;
|
||||
int host_idx;
|
||||
};
|
||||
|
||||
struct anybuss_host;
|
||||
|
||||
struct anybuss_host * __must_check
|
||||
anybuss_host_common_probe(struct device *dev,
|
||||
const struct anybuss_ops *ops);
|
||||
void anybuss_host_common_remove(struct anybuss_host *host);
|
||||
|
||||
struct anybuss_host * __must_check
|
||||
devm_anybuss_host_common_probe(struct device *dev,
|
||||
const struct anybuss_ops *ops);
|
||||
|
||||
#endif /* __LINUX_ANYBUSS_CONTROLLER_H__ */
|
@ -1,379 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Arcx Anybus-S Controller driver
|
||||
*
|
||||
* Copyright (C) 2018 Arcx Inc
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/platform_device.h>
|
||||
#include <linux/gpio/consumer.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/of.h>
|
||||
#include <linux/delay.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/regulator/driver.h>
|
||||
#include <linux/regulator/machine.h>
|
||||
#include <linux/regmap.h>
|
||||
|
||||
/* move to <linux/anybuss-controller.h> when taking this out of staging */
|
||||
#include "anybuss-controller.h"
|
||||
|
||||
#define CPLD_STATUS1 0x80
|
||||
#define CPLD_CONTROL 0x80
|
||||
#define CPLD_CONTROL_CRST 0x40
|
||||
#define CPLD_CONTROL_RST1 0x04
|
||||
#define CPLD_CONTROL_RST2 0x80
|
||||
#define CPLD_STATUS1_AB 0x02
|
||||
#define CPLD_STATUS1_CAN_POWER 0x01
|
||||
#define CPLD_DESIGN_LO 0x81
|
||||
#define CPLD_DESIGN_HI 0x82
|
||||
#define CPLD_CAP 0x83
|
||||
#define CPLD_CAP_COMPAT 0x01
|
||||
#define CPLD_CAP_SEP_RESETS 0x02
|
||||
|
||||
struct controller_priv {
|
||||
struct device *class_dev;
|
||||
bool common_reset;
|
||||
struct gpio_desc *reset_gpiod;
|
||||
void __iomem *cpld_base;
|
||||
struct mutex ctrl_lock; /* protects CONTROL register */
|
||||
u8 control_reg;
|
||||
char version[3];
|
||||
u16 design_no;
|
||||
};
|
||||
|
||||
static void do_reset(struct controller_priv *cd, u8 rst_bit, bool reset)
|
||||
{
|
||||
mutex_lock(&cd->ctrl_lock);
|
||||
/*
|
||||
* CPLD_CONTROL is write-only, so cache its value in
|
||||
* cd->control_reg
|
||||
*/
|
||||
if (reset)
|
||||
cd->control_reg &= ~rst_bit;
|
||||
else
|
||||
cd->control_reg |= rst_bit;
|
||||
writeb(cd->control_reg, cd->cpld_base + CPLD_CONTROL);
|
||||
/*
|
||||
* h/w work-around:
|
||||
* the hardware is 'too fast', so a reset followed by an immediate
|
||||
* not-reset will _not_ change the anybus reset line in any way,
|
||||
* losing the reset. to prevent this from happening, introduce
|
||||
* a minimum reset duration.
|
||||
* Verified minimum safe duration required using a scope
|
||||
* on 14-June-2018: 100 us.
|
||||
*/
|
||||
if (reset)
|
||||
usleep_range(100, 200);
|
||||
mutex_unlock(&cd->ctrl_lock);
|
||||
}
|
||||
|
||||
static int anybuss_reset(struct controller_priv *cd,
|
||||
unsigned long id, bool reset)
|
||||
{
|
||||
if (id >= 2)
|
||||
return -EINVAL;
|
||||
if (cd->common_reset)
|
||||
do_reset(cd, CPLD_CONTROL_CRST, reset);
|
||||
else
|
||||
do_reset(cd, id ? CPLD_CONTROL_RST2 : CPLD_CONTROL_RST1, reset);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void export_reset_0(struct device *dev, bool assert)
|
||||
{
|
||||
struct controller_priv *cd = dev_get_drvdata(dev);
|
||||
|
||||
anybuss_reset(cd, 0, assert);
|
||||
}
|
||||
|
||||
static void export_reset_1(struct device *dev, bool assert)
|
||||
{
|
||||
struct controller_priv *cd = dev_get_drvdata(dev);
|
||||
|
||||
anybuss_reset(cd, 1, assert);
|
||||
}
|
||||
|
||||
/*
|
||||
* parallel bus limitation:
|
||||
*
|
||||
* the anybus is 8-bit wide. we can't assume that the hardware will translate
|
||||
* word accesses on the parallel bus to multiple byte-accesses on the anybus.
|
||||
*
|
||||
* the imx WEIM bus does not provide this type of translation.
|
||||
*
|
||||
* to be safe, we will limit parallel bus accesses to a single byte
|
||||
* at a time for now.
|
||||
*/
|
||||
|
||||
static const struct regmap_config arcx_regmap_cfg = {
|
||||
.reg_bits = 16,
|
||||
.val_bits = 8,
|
||||
.max_register = 0x7ff,
|
||||
.use_single_read = true,
|
||||
.use_single_write = true,
|
||||
/*
|
||||
* single-byte parallel bus accesses are atomic, so don't
|
||||
* require any synchronization.
|
||||
*/
|
||||
.disable_locking = true,
|
||||
};
|
||||
|
||||
static struct regmap *create_parallel_regmap(struct platform_device *pdev,
|
||||
int idx)
|
||||
{
|
||||
void __iomem *base;
|
||||
struct device *dev = &pdev->dev;
|
||||
|
||||
base = devm_platform_ioremap_resource(pdev, idx + 1);
|
||||
if (IS_ERR(base))
|
||||
return ERR_CAST(base);
|
||||
return devm_regmap_init_mmio(dev, base, &arcx_regmap_cfg);
|
||||
}
|
||||
|
||||
static struct anybuss_host *
|
||||
create_anybus_host(struct platform_device *pdev, int idx)
|
||||
{
|
||||
struct anybuss_ops ops = {};
|
||||
|
||||
switch (idx) {
|
||||
case 0:
|
||||
ops.reset = export_reset_0;
|
||||
break;
|
||||
case 1:
|
||||
ops.reset = export_reset_1;
|
||||
break;
|
||||
default:
|
||||
return ERR_PTR(-EINVAL);
|
||||
}
|
||||
ops.host_idx = idx;
|
||||
ops.regmap = create_parallel_regmap(pdev, idx);
|
||||
if (IS_ERR(ops.regmap))
|
||||
return ERR_CAST(ops.regmap);
|
||||
ops.irq = platform_get_irq(pdev, idx);
|
||||
if (ops.irq < 0)
|
||||
return ERR_PTR(ops.irq);
|
||||
return devm_anybuss_host_common_probe(&pdev->dev, &ops);
|
||||
}
|
||||
|
||||
static ssize_t version_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct controller_priv *cd = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%s\n", cd->version);
|
||||
}
|
||||
static DEVICE_ATTR_RO(version);
|
||||
|
||||
static ssize_t design_number_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct controller_priv *cd = dev_get_drvdata(dev);
|
||||
|
||||
return sprintf(buf, "%d\n", cd->design_no);
|
||||
}
|
||||
static DEVICE_ATTR_RO(design_number);
|
||||
|
||||
static struct attribute *controller_attributes[] = {
|
||||
&dev_attr_version.attr,
|
||||
&dev_attr_design_number.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static const struct attribute_group controller_attribute_group = {
|
||||
.attrs = controller_attributes,
|
||||
};
|
||||
|
||||
static const struct attribute_group *controller_attribute_groups[] = {
|
||||
&controller_attribute_group,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static void controller_device_release(struct device *dev)
|
||||
{
|
||||
kfree(dev);
|
||||
}
|
||||
|
||||
static int can_power_is_enabled(struct regulator_dev *rdev)
|
||||
{
|
||||
struct controller_priv *cd = rdev_get_drvdata(rdev);
|
||||
|
||||
return !(readb(cd->cpld_base + CPLD_STATUS1) & CPLD_STATUS1_CAN_POWER);
|
||||
}
|
||||
|
||||
static const struct regulator_ops can_power_ops = {
|
||||
.is_enabled = can_power_is_enabled,
|
||||
};
|
||||
|
||||
static const struct regulator_desc can_power_desc = {
|
||||
.name = "regulator-can-power",
|
||||
.id = -1,
|
||||
.type = REGULATOR_VOLTAGE,
|
||||
.owner = THIS_MODULE,
|
||||
.ops = &can_power_ops,
|
||||
};
|
||||
|
||||
static const struct class controller_class = {
|
||||
.name = "arcx_anybus_controller",
|
||||
};
|
||||
|
||||
static DEFINE_IDA(controller_index_ida);
|
||||
|
||||
static int controller_probe(struct platform_device *pdev)
|
||||
{
|
||||
struct controller_priv *cd;
|
||||
struct device *dev = &pdev->dev;
|
||||
struct regulator_config config = { };
|
||||
struct regulator_dev *regulator;
|
||||
int err, id;
|
||||
struct anybuss_host *host;
|
||||
u8 status1, cap;
|
||||
|
||||
cd = devm_kzalloc(dev, sizeof(*cd), GFP_KERNEL);
|
||||
if (!cd)
|
||||
return -ENOMEM;
|
||||
dev_set_drvdata(dev, cd);
|
||||
mutex_init(&cd->ctrl_lock);
|
||||
cd->reset_gpiod = devm_gpiod_get(dev, "reset", GPIOD_OUT_LOW);
|
||||
if (IS_ERR(cd->reset_gpiod))
|
||||
return PTR_ERR(cd->reset_gpiod);
|
||||
|
||||
/* CPLD control memory, sits at index 0 */
|
||||
cd->cpld_base = devm_platform_ioremap_resource(pdev, 0);
|
||||
if (IS_ERR(cd->cpld_base)) {
|
||||
dev_err(dev,
|
||||
"failed to map cpld base address\n");
|
||||
err = PTR_ERR(cd->cpld_base);
|
||||
goto out_reset;
|
||||
}
|
||||
|
||||
/* identify cpld */
|
||||
status1 = readb(cd->cpld_base + CPLD_STATUS1);
|
||||
cd->design_no = (readb(cd->cpld_base + CPLD_DESIGN_HI) << 8) |
|
||||
readb(cd->cpld_base + CPLD_DESIGN_LO);
|
||||
snprintf(cd->version, sizeof(cd->version), "%c%d",
|
||||
'A' + ((status1 >> 5) & 0x7),
|
||||
(status1 >> 2) & 0x7);
|
||||
dev_info(dev, "design number %d, revision %s\n",
|
||||
cd->design_no,
|
||||
cd->version);
|
||||
cap = readb(cd->cpld_base + CPLD_CAP);
|
||||
if (!(cap & CPLD_CAP_COMPAT)) {
|
||||
dev_err(dev, "unsupported controller [cap=0x%02X]", cap);
|
||||
err = -ENODEV;
|
||||
goto out_reset;
|
||||
}
|
||||
|
||||
if (status1 & CPLD_STATUS1_AB) {
|
||||
dev_info(dev, "has anybus-S slot(s)");
|
||||
cd->common_reset = !(cap & CPLD_CAP_SEP_RESETS);
|
||||
dev_info(dev, "supports %s", cd->common_reset ?
|
||||
"a common reset" : "separate resets");
|
||||
for (id = 0; id < 2; id++) {
|
||||
host = create_anybus_host(pdev, id);
|
||||
if (!IS_ERR(host))
|
||||
continue;
|
||||
err = PTR_ERR(host);
|
||||
/* -ENODEV is fine, it just means no card detected */
|
||||
if (err != -ENODEV)
|
||||
goto out_reset;
|
||||
}
|
||||
}
|
||||
|
||||
id = ida_alloc(&controller_index_ida, GFP_KERNEL);
|
||||
if (id < 0) {
|
||||
err = id;
|
||||
goto out_reset;
|
||||
}
|
||||
/* export can power readout as a regulator */
|
||||
config.dev = dev;
|
||||
config.driver_data = cd;
|
||||
regulator = devm_regulator_register(dev, &can_power_desc, &config);
|
||||
if (IS_ERR(regulator)) {
|
||||
err = PTR_ERR(regulator);
|
||||
goto out_ida;
|
||||
}
|
||||
/* make controller info visible to userspace */
|
||||
cd->class_dev = kzalloc(sizeof(*cd->class_dev), GFP_KERNEL);
|
||||
if (!cd->class_dev) {
|
||||
err = -ENOMEM;
|
||||
goto out_ida;
|
||||
}
|
||||
cd->class_dev->class = &controller_class;
|
||||
cd->class_dev->groups = controller_attribute_groups;
|
||||
cd->class_dev->parent = dev;
|
||||
cd->class_dev->id = id;
|
||||
cd->class_dev->release = controller_device_release;
|
||||
dev_set_name(cd->class_dev, "%d", cd->class_dev->id);
|
||||
dev_set_drvdata(cd->class_dev, cd);
|
||||
err = device_register(cd->class_dev);
|
||||
if (err)
|
||||
goto out_dev;
|
||||
return 0;
|
||||
out_dev:
|
||||
put_device(cd->class_dev);
|
||||
out_ida:
|
||||
ida_free(&controller_index_ida, id);
|
||||
out_reset:
|
||||
gpiod_set_value_cansleep(cd->reset_gpiod, 1);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void controller_remove(struct platform_device *pdev)
|
||||
{
|
||||
struct controller_priv *cd = platform_get_drvdata(pdev);
|
||||
int id = cd->class_dev->id;
|
||||
|
||||
device_unregister(cd->class_dev);
|
||||
ida_free(&controller_index_ida, id);
|
||||
gpiod_set_value_cansleep(cd->reset_gpiod, 1);
|
||||
}
|
||||
|
||||
static const struct of_device_id controller_of_match[] = {
|
||||
{ .compatible = "arcx,anybus-controller" },
|
||||
{ }
|
||||
};
|
||||
|
||||
MODULE_DEVICE_TABLE(of, controller_of_match);
|
||||
|
||||
static struct platform_driver controller_driver = {
|
||||
.probe = controller_probe,
|
||||
.remove = controller_remove,
|
||||
.driver = {
|
||||
.name = "arcx-anybus-controller",
|
||||
.of_match_table = controller_of_match,
|
||||
},
|
||||
};
|
||||
|
||||
static int __init controller_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = class_register(&controller_class);
|
||||
if (err)
|
||||
return err;
|
||||
err = platform_driver_register(&controller_driver);
|
||||
if (err)
|
||||
class_unregister(&controller_class);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit controller_exit(void)
|
||||
{
|
||||
platform_driver_unregister(&controller_driver);
|
||||
class_unregister(&controller_class);
|
||||
ida_destroy(&controller_index_ida);
|
||||
}
|
||||
|
||||
module_init(controller_init);
|
||||
module_exit(controller_exit);
|
||||
|
||||
MODULE_DESCRIPTION("Arcx Anybus-S Controller driver");
|
||||
MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -1,224 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* HMS Profinet Client Driver
|
||||
*
|
||||
* Copyright (C) 2018 Arcx Inc
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* move to <linux/fieldbus_dev.h> when taking this out of staging */
|
||||
#include "../fieldbus_dev.h"
|
||||
|
||||
/* move to <linux/anybuss-client.h> when taking this out of staging */
|
||||
#include "anybuss-client.h"
|
||||
|
||||
#define PROFI_DPRAM_SIZE 512
|
||||
|
||||
/*
|
||||
* ---------------------------------------------------------------
|
||||
* Anybus Profinet mailbox messages - definitions
|
||||
* ---------------------------------------------------------------
|
||||
* note that we're depending on the layout of these structures being
|
||||
* exactly as advertised.
|
||||
*/
|
||||
|
||||
struct msg_mac_addr {
|
||||
u8 addr[6];
|
||||
};
|
||||
|
||||
struct profi_priv {
|
||||
struct fieldbus_dev fbdev;
|
||||
struct anybuss_client *client;
|
||||
struct mutex enable_lock; /* serializes card enable */
|
||||
bool power_on;
|
||||
};
|
||||
|
||||
static ssize_t
|
||||
profi_read_area(struct fieldbus_dev *fbdev, char __user *buf, size_t size,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct profi_priv *priv = container_of(fbdev, struct profi_priv, fbdev);
|
||||
|
||||
return anybuss_read_output(priv->client, buf, size, offset);
|
||||
}
|
||||
|
||||
static ssize_t
|
||||
profi_write_area(struct fieldbus_dev *fbdev, const char __user *buf,
|
||||
size_t size, loff_t *offset)
|
||||
{
|
||||
struct profi_priv *priv = container_of(fbdev, struct profi_priv, fbdev);
|
||||
|
||||
return anybuss_write_input(priv->client, buf, size, offset);
|
||||
}
|
||||
|
||||
static int profi_id_get(struct fieldbus_dev *fbdev, char *buf,
|
||||
size_t max_size)
|
||||
{
|
||||
struct profi_priv *priv = container_of(fbdev, struct profi_priv, fbdev);
|
||||
struct msg_mac_addr response;
|
||||
int ret;
|
||||
|
||||
ret = anybuss_recv_msg(priv->client, 0x0010, &response,
|
||||
sizeof(response));
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return snprintf(buf, max_size, "%pM\n", response.addr);
|
||||
}
|
||||
|
||||
static bool profi_enable_get(struct fieldbus_dev *fbdev)
|
||||
{
|
||||
struct profi_priv *priv = container_of(fbdev, struct profi_priv, fbdev);
|
||||
bool power_on;
|
||||
|
||||
mutex_lock(&priv->enable_lock);
|
||||
power_on = priv->power_on;
|
||||
mutex_unlock(&priv->enable_lock);
|
||||
|
||||
return power_on;
|
||||
}
|
||||
|
||||
static int __profi_enable(struct profi_priv *priv)
|
||||
{
|
||||
int ret;
|
||||
struct anybuss_client *client = priv->client;
|
||||
/* Initialization Sequence, Generic Anybus Mode */
|
||||
const struct anybuss_memcfg mem_cfg = {
|
||||
.input_io = 220,
|
||||
.input_dpram = PROFI_DPRAM_SIZE,
|
||||
.input_total = PROFI_DPRAM_SIZE,
|
||||
.output_io = 220,
|
||||
.output_dpram = PROFI_DPRAM_SIZE,
|
||||
.output_total = PROFI_DPRAM_SIZE,
|
||||
.offl_mode = FIELDBUS_DEV_OFFL_MODE_CLEAR,
|
||||
};
|
||||
|
||||
/*
|
||||
* switch anybus off then on, this ensures we can do a complete
|
||||
* configuration cycle in case anybus was already on.
|
||||
*/
|
||||
anybuss_set_power(client, false);
|
||||
ret = anybuss_set_power(client, true);
|
||||
if (ret)
|
||||
goto err;
|
||||
ret = anybuss_start_init(client, &mem_cfg);
|
||||
if (ret)
|
||||
goto err;
|
||||
ret = anybuss_finish_init(client);
|
||||
if (ret)
|
||||
goto err;
|
||||
priv->power_on = true;
|
||||
return 0;
|
||||
|
||||
err:
|
||||
anybuss_set_power(client, false);
|
||||
priv->power_on = false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __profi_disable(struct profi_priv *priv)
|
||||
{
|
||||
struct anybuss_client *client = priv->client;
|
||||
|
||||
anybuss_set_power(client, false);
|
||||
priv->power_on = false;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int profi_simple_enable(struct fieldbus_dev *fbdev, bool enable)
|
||||
{
|
||||
int ret;
|
||||
struct profi_priv *priv = container_of(fbdev, struct profi_priv, fbdev);
|
||||
|
||||
mutex_lock(&priv->enable_lock);
|
||||
if (enable)
|
||||
ret = __profi_enable(priv);
|
||||
else
|
||||
ret = __profi_disable(priv);
|
||||
mutex_unlock(&priv->enable_lock);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void profi_on_area_updated(struct anybuss_client *client)
|
||||
{
|
||||
struct profi_priv *priv = anybuss_get_drvdata(client);
|
||||
|
||||
fieldbus_dev_area_updated(&priv->fbdev);
|
||||
}
|
||||
|
||||
static void profi_on_online_changed(struct anybuss_client *client, bool online)
|
||||
{
|
||||
struct profi_priv *priv = anybuss_get_drvdata(client);
|
||||
|
||||
fieldbus_dev_online_changed(&priv->fbdev, online);
|
||||
}
|
||||
|
||||
static int profinet_probe(struct anybuss_client *client)
|
||||
{
|
||||
struct profi_priv *priv;
|
||||
struct device *dev = &client->dev;
|
||||
int err;
|
||||
|
||||
client->on_area_updated = profi_on_area_updated;
|
||||
client->on_online_changed = profi_on_online_changed;
|
||||
priv = devm_kzalloc(dev, sizeof(*priv), GFP_KERNEL);
|
||||
if (!priv)
|
||||
return -ENOMEM;
|
||||
mutex_init(&priv->enable_lock);
|
||||
priv->client = client;
|
||||
priv->fbdev.read_area_sz = PROFI_DPRAM_SIZE;
|
||||
priv->fbdev.write_area_sz = PROFI_DPRAM_SIZE;
|
||||
priv->fbdev.card_name = "HMS Profinet IRT (Anybus-S)";
|
||||
priv->fbdev.fieldbus_type = FIELDBUS_DEV_TYPE_PROFINET;
|
||||
priv->fbdev.read_area = profi_read_area;
|
||||
priv->fbdev.write_area = profi_write_area;
|
||||
priv->fbdev.fieldbus_id_get = profi_id_get;
|
||||
priv->fbdev.enable_get = profi_enable_get;
|
||||
priv->fbdev.simple_enable_set = profi_simple_enable;
|
||||
priv->fbdev.parent = dev;
|
||||
err = fieldbus_dev_register(&priv->fbdev);
|
||||
if (err < 0)
|
||||
return err;
|
||||
dev_info(dev, "card detected, registered as %s",
|
||||
dev_name(priv->fbdev.dev));
|
||||
anybuss_set_drvdata(client, priv);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void profinet_remove(struct anybuss_client *client)
|
||||
{
|
||||
struct profi_priv *priv = anybuss_get_drvdata(client);
|
||||
|
||||
fieldbus_dev_unregister(&priv->fbdev);
|
||||
}
|
||||
|
||||
static struct anybuss_client_driver profinet_driver = {
|
||||
.probe = profinet_probe,
|
||||
.remove = profinet_remove,
|
||||
.driver = {
|
||||
.name = "hms-profinet",
|
||||
.owner = THIS_MODULE,
|
||||
},
|
||||
.anybus_id = 0x0089,
|
||||
};
|
||||
|
||||
static int __init profinet_init(void)
|
||||
{
|
||||
return anybuss_client_driver_register(&profinet_driver);
|
||||
}
|
||||
module_init(profinet_init);
|
||||
|
||||
static void __exit profinet_exit(void)
|
||||
{
|
||||
return anybuss_client_driver_unregister(&profinet_driver);
|
||||
}
|
||||
module_exit(profinet_exit);
|
||||
|
||||
MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>");
|
||||
MODULE_DESCRIPTION("HMS Profinet IRT Driver (Anybus-S)");
|
||||
MODULE_LICENSE("GPL v2");
|
File diff suppressed because it is too large
Load Diff
@ -1,344 +0,0 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Fieldbus Device Driver Core
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/device.h>
|
||||
#include <linux/idr.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
/* move to <linux/fieldbus_dev.h> when taking this out of staging */
|
||||
#include "fieldbus_dev.h"
|
||||
|
||||
/* Maximum number of fieldbus devices */
|
||||
#define MAX_FIELDBUSES 32
|
||||
|
||||
/* the dev_t structure to store the dynamically allocated fieldbus devices */
|
||||
static dev_t fieldbus_devt;
|
||||
static DEFINE_IDA(fieldbus_ida);
|
||||
static DEFINE_MUTEX(fieldbus_mtx);
|
||||
|
||||
static ssize_t online_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "%d\n", !!fb->online);
|
||||
}
|
||||
static DEVICE_ATTR_RO(online);
|
||||
|
||||
static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
|
||||
if (!fb->enable_get)
|
||||
return -EINVAL;
|
||||
return sysfs_emit(buf, "%d\n", !!fb->enable_get(fb));
|
||||
}
|
||||
|
||||
static ssize_t enabled_store(struct device *dev, struct device_attribute *attr,
|
||||
const char *buf, size_t n)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
bool value;
|
||||
int ret;
|
||||
|
||||
if (!fb->simple_enable_set)
|
||||
return -ENOTSUPP;
|
||||
ret = kstrtobool(buf, &value);
|
||||
if (ret)
|
||||
return ret;
|
||||
ret = fb->simple_enable_set(fb, value);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
return n;
|
||||
}
|
||||
static DEVICE_ATTR_RW(enabled);
|
||||
|
||||
static ssize_t card_name_show(struct device *dev, struct device_attribute *attr,
|
||||
char *buf)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
|
||||
/* card_name was provided by child driver. */
|
||||
return sysfs_emit(buf, "%s\n", fb->card_name);
|
||||
}
|
||||
static DEVICE_ATTR_RO(card_name);
|
||||
|
||||
static ssize_t read_area_size_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "%zu\n", fb->read_area_sz);
|
||||
}
|
||||
static DEVICE_ATTR_RO(read_area_size);
|
||||
|
||||
static ssize_t write_area_size_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
|
||||
return sysfs_emit(buf, "%zu\n", fb->write_area_sz);
|
||||
}
|
||||
static DEVICE_ATTR_RO(write_area_size);
|
||||
|
||||
static ssize_t fieldbus_id_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
|
||||
return fb->fieldbus_id_get(fb, buf, PAGE_SIZE);
|
||||
}
|
||||
static DEVICE_ATTR_RO(fieldbus_id);
|
||||
|
||||
static ssize_t fieldbus_type_show(struct device *dev,
|
||||
struct device_attribute *attr, char *buf)
|
||||
{
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
const char *t;
|
||||
|
||||
switch (fb->fieldbus_type) {
|
||||
case FIELDBUS_DEV_TYPE_PROFINET:
|
||||
t = "profinet";
|
||||
break;
|
||||
default:
|
||||
t = "unknown";
|
||||
break;
|
||||
}
|
||||
|
||||
return sysfs_emit(buf, "%s\n", t);
|
||||
}
|
||||
static DEVICE_ATTR_RO(fieldbus_type);
|
||||
|
||||
static struct attribute *fieldbus_attrs[] = {
|
||||
&dev_attr_enabled.attr,
|
||||
&dev_attr_card_name.attr,
|
||||
&dev_attr_fieldbus_id.attr,
|
||||
&dev_attr_read_area_size.attr,
|
||||
&dev_attr_write_area_size.attr,
|
||||
&dev_attr_online.attr,
|
||||
&dev_attr_fieldbus_type.attr,
|
||||
NULL,
|
||||
};
|
||||
|
||||
static umode_t fieldbus_is_visible(struct kobject *kobj, struct attribute *attr,
|
||||
int n)
|
||||
{
|
||||
struct device *dev = kobj_to_dev(kobj);
|
||||
struct fieldbus_dev *fb = dev_get_drvdata(dev);
|
||||
umode_t mode = attr->mode;
|
||||
|
||||
if (attr == &dev_attr_enabled.attr) {
|
||||
mode = 0;
|
||||
if (fb->enable_get)
|
||||
mode |= 0444;
|
||||
if (fb->simple_enable_set)
|
||||
mode |= 0200;
|
||||
}
|
||||
|
||||
return mode;
|
||||
}
|
||||
|
||||
static const struct attribute_group fieldbus_group = {
|
||||
.attrs = fieldbus_attrs,
|
||||
.is_visible = fieldbus_is_visible,
|
||||
};
|
||||
__ATTRIBUTE_GROUPS(fieldbus);
|
||||
|
||||
static const struct class fieldbus_class = {
|
||||
.name = "fieldbus_dev",
|
||||
.dev_groups = fieldbus_groups,
|
||||
};
|
||||
|
||||
struct fb_open_file {
|
||||
struct fieldbus_dev *fbdev;
|
||||
int dc_event;
|
||||
};
|
||||
|
||||
static int fieldbus_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
struct fb_open_file *of;
|
||||
struct fieldbus_dev *fbdev = container_of(inode->i_cdev,
|
||||
struct fieldbus_dev,
|
||||
cdev);
|
||||
|
||||
of = kzalloc(sizeof(*of), GFP_KERNEL);
|
||||
if (!of)
|
||||
return -ENOMEM;
|
||||
of->fbdev = fbdev;
|
||||
filp->private_data = of;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int fieldbus_release(struct inode *node, struct file *filp)
|
||||
{
|
||||
struct fb_open_file *of = filp->private_data;
|
||||
|
||||
kfree(of);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t fieldbus_read(struct file *filp, char __user *buf, size_t size,
|
||||
loff_t *offset)
|
||||
{
|
||||
struct fb_open_file *of = filp->private_data;
|
||||
struct fieldbus_dev *fbdev = of->fbdev;
|
||||
|
||||
of->dc_event = fbdev->dc_event;
|
||||
return fbdev->read_area(fbdev, buf, size, offset);
|
||||
}
|
||||
|
||||
static ssize_t fieldbus_write(struct file *filp, const char __user *buf,
|
||||
size_t size, loff_t *offset)
|
||||
{
|
||||
struct fb_open_file *of = filp->private_data;
|
||||
struct fieldbus_dev *fbdev = of->fbdev;
|
||||
|
||||
return fbdev->write_area(fbdev, buf, size, offset);
|
||||
}
|
||||
|
||||
static __poll_t fieldbus_poll(struct file *filp, poll_table *wait)
|
||||
{
|
||||
struct fb_open_file *of = filp->private_data;
|
||||
struct fieldbus_dev *fbdev = of->fbdev;
|
||||
__poll_t mask = EPOLLIN | EPOLLRDNORM | EPOLLOUT | EPOLLWRNORM;
|
||||
|
||||
poll_wait(filp, &fbdev->dc_wq, wait);
|
||||
/* data changed ? */
|
||||
if (fbdev->dc_event != of->dc_event)
|
||||
mask |= EPOLLPRI | EPOLLERR;
|
||||
return mask;
|
||||
}
|
||||
|
||||
static const struct file_operations fieldbus_fops = {
|
||||
.open = fieldbus_open,
|
||||
.release = fieldbus_release,
|
||||
.read = fieldbus_read,
|
||||
.write = fieldbus_write,
|
||||
.poll = fieldbus_poll,
|
||||
.llseek = generic_file_llseek,
|
||||
.owner = THIS_MODULE,
|
||||
};
|
||||
|
||||
void fieldbus_dev_area_updated(struct fieldbus_dev *fb)
|
||||
{
|
||||
fb->dc_event++;
|
||||
wake_up_all(&fb->dc_wq);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fieldbus_dev_area_updated);
|
||||
|
||||
void fieldbus_dev_online_changed(struct fieldbus_dev *fb, bool online)
|
||||
{
|
||||
fb->online = online;
|
||||
kobject_uevent(&fb->dev->kobj, KOBJ_CHANGE);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fieldbus_dev_online_changed);
|
||||
|
||||
static void __fieldbus_dev_unregister(struct fieldbus_dev *fb)
|
||||
{
|
||||
if (!fb)
|
||||
return;
|
||||
device_destroy(&fieldbus_class, fb->cdev.dev);
|
||||
cdev_del(&fb->cdev);
|
||||
ida_free(&fieldbus_ida, fb->id);
|
||||
}
|
||||
|
||||
void fieldbus_dev_unregister(struct fieldbus_dev *fb)
|
||||
{
|
||||
mutex_lock(&fieldbus_mtx);
|
||||
__fieldbus_dev_unregister(fb);
|
||||
mutex_unlock(&fieldbus_mtx);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fieldbus_dev_unregister);
|
||||
|
||||
static int __fieldbus_dev_register(struct fieldbus_dev *fb)
|
||||
{
|
||||
dev_t devno;
|
||||
int err;
|
||||
|
||||
if (!fb)
|
||||
return -EINVAL;
|
||||
if (!fb->read_area || !fb->write_area || !fb->fieldbus_id_get)
|
||||
return -EINVAL;
|
||||
fb->id = ida_alloc_max(&fieldbus_ida, MAX_FIELDBUSES - 1, GFP_KERNEL);
|
||||
if (fb->id < 0)
|
||||
return fb->id;
|
||||
devno = MKDEV(MAJOR(fieldbus_devt), fb->id);
|
||||
init_waitqueue_head(&fb->dc_wq);
|
||||
cdev_init(&fb->cdev, &fieldbus_fops);
|
||||
err = cdev_add(&fb->cdev, devno, 1);
|
||||
if (err) {
|
||||
pr_err("fieldbus_dev%d unable to add device %d:%d\n",
|
||||
fb->id, MAJOR(fieldbus_devt), fb->id);
|
||||
goto err_cdev;
|
||||
}
|
||||
fb->dev = device_create(&fieldbus_class, fb->parent, devno, fb,
|
||||
"fieldbus_dev%d", fb->id);
|
||||
if (IS_ERR(fb->dev)) {
|
||||
err = PTR_ERR(fb->dev);
|
||||
goto err_dev_create;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_dev_create:
|
||||
cdev_del(&fb->cdev);
|
||||
err_cdev:
|
||||
ida_free(&fieldbus_ida, fb->id);
|
||||
return err;
|
||||
}
|
||||
|
||||
int fieldbus_dev_register(struct fieldbus_dev *fb)
|
||||
{
|
||||
int err;
|
||||
|
||||
mutex_lock(&fieldbus_mtx);
|
||||
err = __fieldbus_dev_register(fb);
|
||||
mutex_unlock(&fieldbus_mtx);
|
||||
|
||||
return err;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(fieldbus_dev_register);
|
||||
|
||||
static int __init fieldbus_init(void)
|
||||
{
|
||||
int err;
|
||||
|
||||
err = class_register(&fieldbus_class);
|
||||
if (err < 0) {
|
||||
pr_err("fieldbus_dev: could not register class\n");
|
||||
return err;
|
||||
}
|
||||
err = alloc_chrdev_region(&fieldbus_devt, 0,
|
||||
MAX_FIELDBUSES, "fieldbus_dev");
|
||||
if (err < 0) {
|
||||
pr_err("fieldbus_dev: unable to allocate char dev region\n");
|
||||
goto err_alloc;
|
||||
}
|
||||
return 0;
|
||||
|
||||
err_alloc:
|
||||
class_unregister(&fieldbus_class);
|
||||
return err;
|
||||
}
|
||||
|
||||
static void __exit fieldbus_exit(void)
|
||||
{
|
||||
unregister_chrdev_region(fieldbus_devt, MAX_FIELDBUSES);
|
||||
class_unregister(&fieldbus_class);
|
||||
ida_destroy(&fieldbus_ida);
|
||||
}
|
||||
|
||||
subsys_initcall(fieldbus_init);
|
||||
module_exit(fieldbus_exit);
|
||||
|
||||
MODULE_AUTHOR("Sven Van Asbroeck <TheSven73@gmail.com>");
|
||||
MODULE_AUTHOR("Jonathan Stiles <jonathans@arcx.com>");
|
||||
MODULE_DESCRIPTION("Fieldbus Device Driver Core");
|
||||
MODULE_LICENSE("GPL v2");
|
@ -1,114 +0,0 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* Fieldbus Device Driver Core
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __FIELDBUS_DEV_H
|
||||
#define __FIELDBUS_DEV_H
|
||||
|
||||
#include <linux/cdev.h>
|
||||
#include <linux/wait.h>
|
||||
|
||||
enum fieldbus_dev_type {
|
||||
FIELDBUS_DEV_TYPE_UNKNOWN = 0,
|
||||
FIELDBUS_DEV_TYPE_PROFINET,
|
||||
};
|
||||
|
||||
enum fieldbus_dev_offl_mode {
|
||||
FIELDBUS_DEV_OFFL_MODE_CLEAR = 0,
|
||||
FIELDBUS_DEV_OFFL_MODE_FREEZE,
|
||||
FIELDBUS_DEV_OFFL_MODE_SET
|
||||
};
|
||||
|
||||
/**
|
||||
* struct fieldbus_dev - Fieldbus device
|
||||
* @read_area: [DRIVER] function to read the process data area of the
|
||||
* device. same parameters/return values as
|
||||
* the read function in struct file_operations
|
||||
* @write_area: [DRIVER] function to write to the process data area of
|
||||
* the device. same parameters/return values as
|
||||
* the write function in struct file_operations
|
||||
* @write_area_sz [DRIVER] size of the writable process data area
|
||||
* @read_area_sz [DRIVER] size of the readable process data area
|
||||
* @card_name [DRIVER] name of the card, e.g. "ACME Inc. profinet"
|
||||
* @fieldbus_type [DRIVER] fieldbus type of this device, e.g.
|
||||
* FIELDBUS_DEV_TYPE_PROFINET
|
||||
* @enable_get [DRIVER] function which returns true if the card
|
||||
* is enabled, false otherwise
|
||||
* @fieldbus_id_get [DRIVER] function to retrieve the unique fieldbus id
|
||||
* by which this device can be identified;
|
||||
* return value follows the snprintf convention
|
||||
* @simple_enable_set [DRIVER] (optional) function to enable the device
|
||||
* according to its default settings
|
||||
* @parent [DRIVER] (optional) the device's parent device
|
||||
*/
|
||||
struct fieldbus_dev {
|
||||
ssize_t (*read_area)(struct fieldbus_dev *fbdev, char __user *buf,
|
||||
size_t size, loff_t *offset);
|
||||
ssize_t (*write_area)(struct fieldbus_dev *fbdev,
|
||||
const char __user *buf, size_t size,
|
||||
loff_t *offset);
|
||||
size_t write_area_sz, read_area_sz;
|
||||
const char *card_name;
|
||||
enum fieldbus_dev_type fieldbus_type;
|
||||
bool (*enable_get)(struct fieldbus_dev *fbdev);
|
||||
int (*fieldbus_id_get)(struct fieldbus_dev *fbdev, char *buf,
|
||||
size_t max_size);
|
||||
int (*simple_enable_set)(struct fieldbus_dev *fbdev, bool enable);
|
||||
struct device *parent;
|
||||
|
||||
/* private data */
|
||||
int id;
|
||||
struct cdev cdev;
|
||||
struct device *dev;
|
||||
int dc_event;
|
||||
wait_queue_head_t dc_wq;
|
||||
bool online;
|
||||
};
|
||||
|
||||
#if IS_ENABLED(CONFIG_FIELDBUS_DEV)
|
||||
|
||||
/**
|
||||
* fieldbus_dev_unregister()
|
||||
* - unregister a previously registered fieldbus device
|
||||
* @fb: Device structure previously registered
|
||||
**/
|
||||
void fieldbus_dev_unregister(struct fieldbus_dev *fb);
|
||||
|
||||
/**
|
||||
* fieldbus_dev_register()
|
||||
* - register a device with the fieldbus device subsystem
|
||||
* @fb: Device structure filled by the device driver
|
||||
**/
|
||||
int __must_check fieldbus_dev_register(struct fieldbus_dev *fb);
|
||||
|
||||
/**
|
||||
* fieldbus_dev_area_updated()
|
||||
* - notify the subsystem that an external fieldbus controller updated
|
||||
* the process data area
|
||||
* @fb: Device structure
|
||||
**/
|
||||
void fieldbus_dev_area_updated(struct fieldbus_dev *fb);
|
||||
|
||||
/**
|
||||
* fieldbus_dev_online_changed()
|
||||
* - notify the subsystem that the fieldbus online status changed
|
||||
* @fb: Device structure
|
||||
**/
|
||||
void fieldbus_dev_online_changed(struct fieldbus_dev *fb, bool online);
|
||||
|
||||
#else /* IS_ENABLED(CONFIG_FIELDBUS_DEV) */
|
||||
|
||||
static inline void fieldbus_dev_unregister(struct fieldbus_dev *fb) {}
|
||||
static inline int __must_check fieldbus_dev_register(struct fieldbus_dev *fb)
|
||||
{
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
|
||||
static inline void fieldbus_dev_area_updated(struct fieldbus_dev *fb) {}
|
||||
static inline void fieldbus_dev_online_changed(struct fieldbus_dev *fb,
|
||||
bool online) {}
|
||||
|
||||
#endif /* IS_ENABLED(CONFIG_FIELDBUS_DEV) */
|
||||
#endif /* __FIELDBUS_DEV_H */
|
Loading…
Reference in New Issue
Block a user