// SPDX-License-Identifier: GPL-2.0 /* * HMS Profinet Client Driver * * Copyright (C) 2018 Arcx Inc */ #include #include #include #include /* move to when taking this out of staging */ #include "../fieldbus_dev.h" /* move to 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, "%02X:%02X:%02X:%02X:%02X:%02X\n", response.addr[0], response.addr[1], response.addr[2], response.addr[3], response.addr[4], response.addr[5]); } 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 = AB_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 int profinet_remove(struct anybuss_client *client) { struct profi_priv *priv = anybuss_get_drvdata(client); fieldbus_dev_unregister(&priv->fbdev); return 0; } static struct anybuss_client_driver profinet_driver = { .probe = profinet_probe, .remove = profinet_remove, .driver = { .name = "hms-profinet", .owner = THIS_MODULE, }, .fieldbus_type = 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 "); MODULE_DESCRIPTION("HMS Profinet IRT Driver (Anybus-S)"); MODULE_LICENSE("GPL v2");