| // 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"); |