Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Linux/drivers/mfd/   (Open Source Betriebssystem Version 6.17.9©)  Datei vom 24.10.2025 mit Größe 10 kB image not shown  

Quelle  cgbc-core.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-or-later
/*
 * Congatec Board Controller core driver.
 *
 * The x86 Congatec modules have an embedded micro controller named Board
 * Controller. This Board Controller has a Watchdog timer, some GPIOs, and two
 * I2C busses.
 *
 * Copyright (C) 2024 Bootlin
 *
 * Author: Thomas Richard <thomas.richard@bootlin.com>
 */


#include <linux/dmi.h>
#include <linux/iopoll.h>
#include <linux/mfd/cgbc.h>
#include <linux/mfd/core.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/sysfs.h>

#define CGBC_IO_SESSION_BASE 0x0E20
#define CGBC_IO_SESSION_END 0x0E30
#define CGBC_IO_CMD_BASE 0x0E00
#define CGBC_IO_CMD_END  0x0E10

#define CGBC_MASK_STATUS (BIT(6) | BIT(7))
#define CGBC_MASK_DATA_COUNT 0x1F
#define CGBC_MASK_ERROR_CODE 0x1F

#define CGBC_STATUS_DATA_READY 0x00
#define CGBC_STATUS_CMD_READY BIT(6)
#define CGBC_STATUS_ERROR (BIT(6) | BIT(7))

#define CGBC_SESSION_CMD  0x00
#define CGBC_SESSION_CMD_IDLE  0x00
#define CGBC_SESSION_CMD_REQUEST 0x01
#define CGBC_SESSION_DATA  0x01
#define CGBC_SESSION_STATUS  0x02
#define CGBC_SESSION_STATUS_FREE 0x03
#define CGBC_SESSION_ACCESS  0x04
#define CGBC_SESSION_ACCESS_GAINED 0x00

#define CGBC_SESSION_VALID_MIN  0x02
#define CGBC_SESSION_VALID_MAX  0xFE

#define CGBC_CMD_STROBE   0x00
#define CGBC_CMD_INDEX   0x02
#define CGBC_CMD_INDEX_CBM_MAN8  0x00
#define CGBC_CMD_INDEX_CBM_AUTO32 0x03
#define CGBC_CMD_DATA   0x04
#define CGBC_CMD_ACCESS   0x0C

#define CGBC_CMD_GET_FW_REV 0x21

static struct platform_device *cgbc_pdev;

/* Wait the Board Controller is ready to receive some session commands */
static int cgbc_wait_device(struct cgbc_device_data *cgbc)
{
 u16 status;
 int ret;

 ret = readx_poll_timeout(ioread16, cgbc->io_session + CGBC_SESSION_STATUS, status,
     status == CGBC_SESSION_STATUS_FREE, 0, 500000);

 if (ret || ioread32(cgbc->io_session + CGBC_SESSION_ACCESS))
  ret = -ENODEV;

 return ret;
}

static int cgbc_session_command(struct cgbc_device_data *cgbc, u8 cmd)
{
 int ret;
 u8 val;

 ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
     val == CGBC_SESSION_CMD_IDLE, 0, 100000);
 if (ret)
  return ret;

 iowrite8(cmd, cgbc->io_session + CGBC_SESSION_CMD);

 ret = readx_poll_timeout(ioread8, cgbc->io_session + CGBC_SESSION_CMD, val,
     val == CGBC_SESSION_CMD_IDLE, 0, 100000);
 if (ret)
  return ret;

 ret = (int)ioread8(cgbc->io_session + CGBC_SESSION_DATA);

 iowrite8(CGBC_SESSION_STATUS_FREE, cgbc->io_session + CGBC_SESSION_STATUS);

 return ret;
}

static int cgbc_session_request(struct cgbc_device_data *cgbc)
{
 int ret;

 ret = cgbc_wait_device(cgbc);

 if (ret)
  return dev_err_probe(cgbc->dev, ret, "device not found or not ready\n");

 cgbc->session = cgbc_session_command(cgbc, CGBC_SESSION_CMD_REQUEST);

 /* The Board Controller sent us a wrong session handle, we cannot communicate with it */
 if (cgbc->session < CGBC_SESSION_VALID_MIN || cgbc->session > CGBC_SESSION_VALID_MAX)
  return dev_err_probe(cgbc->dev, -ECONNREFUSED,
         "failed to get a valid session handle\n");

 return 0;
}

static void cgbc_session_release(struct cgbc_device_data *cgbc)
{
 if (cgbc_session_command(cgbc, cgbc->session) != cgbc->session)
  dev_warn(cgbc->dev, "failed to release session\n");
}

static bool cgbc_command_lock(struct cgbc_device_data *cgbc)
{
 iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);

 return ioread8(cgbc->io_cmd + CGBC_CMD_ACCESS) == cgbc->session;
}

static void cgbc_command_unlock(struct cgbc_device_data *cgbc)
{
 iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_ACCESS);
}

int cgbc_command(struct cgbc_device_data *cgbc, void *cmd, unsigned int cmd_size, void *data,
   unsigned int data_size, u8 *status)
{
 u8 checksum = 0, data_checksum = 0, istatus = 0, val;
 u8 *_data = (u8 *)data;
 u8 *_cmd = (u8 *)cmd;
 int mode_change = -1;
 bool lock;
 int ret, i;

 mutex_lock(&cgbc->lock);

 /* Request access */
 ret = readx_poll_timeout(cgbc_command_lock, cgbc, lock, lock, 0, 100000);
 if (ret)
  goto out;

 /* Wait board controller is ready */
 ret = readx_poll_timeout(ioread8, cgbc->io_cmd + CGBC_CMD_STROBE, val,
     val == CGBC_CMD_STROBE, 0, 100000);
 if (ret)
  goto release;

 /* Write command packet */
 if (cmd_size <= 2) {
  iowrite8(CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
 } else {
  iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);
  if ((cmd_size % 4) != 0x03)
   mode_change = (cmd_size & 0xFFFC) - 1;
 }

 for (i = 0; i < cmd_size; i++) {
  iowrite8(_cmd[i], cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));
  checksum ^= _cmd[i];
  if (mode_change == i)
   iowrite8((i + 1) | CGBC_CMD_INDEX_CBM_MAN8, cgbc->io_cmd + CGBC_CMD_INDEX);
 }

 /* Append checksum byte */
 iowrite8(checksum, cgbc->io_cmd + CGBC_CMD_DATA + (i % 4));

 /* Perform command strobe */
 iowrite8(cgbc->session, cgbc->io_cmd + CGBC_CMD_STROBE);

 /* Rewind cmd buffer index */
 iowrite8(CGBC_CMD_INDEX_CBM_AUTO32, cgbc->io_cmd + CGBC_CMD_INDEX);

 /* Wait command completion */
 ret = read_poll_timeout(ioread8, val, val == CGBC_CMD_STROBE, 0, 100000, false,
    cgbc->io_cmd + CGBC_CMD_STROBE);
 if (ret)
  goto release;

 istatus = ioread8(cgbc->io_cmd + CGBC_CMD_DATA);
 checksum = istatus;

 /* Check command status */
 switch (istatus & CGBC_MASK_STATUS) {
 case CGBC_STATUS_DATA_READY:
  if (istatus > data_size)
   istatus = data_size;
  for (i = 0; i < istatus; i++) {
   _data[i] = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
   checksum ^= _data[i];
  }
  data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + ((i + 1) % 4));
  istatus &= CGBC_MASK_DATA_COUNT;
  break;
 case CGBC_STATUS_ERROR:
 case CGBC_STATUS_CMD_READY:
  data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
  if ((istatus & CGBC_MASK_STATUS) == CGBC_STATUS_ERROR)
   ret = -EIO;
  istatus = istatus & CGBC_MASK_ERROR_CODE;
  break;
 default:
  data_checksum = ioread8(cgbc->io_cmd + CGBC_CMD_DATA + 1);
  istatus &= CGBC_MASK_ERROR_CODE;
  ret = -EIO;
  break;
 }

 /* Checksum verification */
 if (ret == 0 && data_checksum != checksum)
  ret = -EIO;

release:
 cgbc_command_unlock(cgbc);

out:
 mutex_unlock(&cgbc->lock);

 if (status)
  *status = istatus;

 return ret;
}
EXPORT_SYMBOL_GPL(cgbc_command);

static struct mfd_cell cgbc_devs[] = {
 { .name = "cgbc-wdt" },
 { .name = "cgbc-gpio" },
 { .name = "cgbc-i2c", .id = 1 },
 { .name = "cgbc-i2c", .id = 2 },
 { .name = "cgbc-hwmon" },
};

static int cgbc_map(struct cgbc_device_data *cgbc)
{
 struct device *dev = cgbc->dev;
 struct platform_device *pdev = to_platform_device(dev);
 struct resource *ioport;

 ioport = platform_get_resource(pdev, IORESOURCE_IO, 0);
 if (!ioport)
  return -EINVAL;

 cgbc->io_session = devm_ioport_map(dev, ioport->start, resource_size(ioport));
 if (!cgbc->io_session)
  return -ENOMEM;

 ioport = platform_get_resource(pdev, IORESOURCE_IO, 1);
 if (!ioport)
  return -EINVAL;

 cgbc->io_cmd = devm_ioport_map(dev, ioport->start, resource_size(ioport));
 if (!cgbc->io_cmd)
  return -ENOMEM;

 return 0;
}

static const struct resource cgbc_resources[] = {
 {
  .start  = CGBC_IO_SESSION_BASE,
  .end    = CGBC_IO_SESSION_END,
  .flags  = IORESOURCE_IO,
 },
 {
  .start  = CGBC_IO_CMD_BASE,
  .end    = CGBC_IO_CMD_END,
  .flags  = IORESOURCE_IO,
 },
};

static ssize_t cgbc_version_show(struct device *dev,
     struct device_attribute *attr, char *buf)
{
 struct cgbc_device_data *cgbc = dev_get_drvdata(dev);

 return sysfs_emit(buf, "CGBCP%c%c%c\n", cgbc->version.feature, cgbc->version.major,
     cgbc->version.minor);
}

static DEVICE_ATTR_RO(cgbc_version);

static struct attribute *cgbc_attrs[] = {
 &dev_attr_cgbc_version.attr,
 NULL
};

ATTRIBUTE_GROUPS(cgbc);

static int cgbc_get_version(struct cgbc_device_data *cgbc)
{
 u8 cmd = CGBC_CMD_GET_FW_REV;
 u8 data[4];
 int ret;

 ret = cgbc_command(cgbc, &cmd, 1, &data, sizeof(data), NULL);
 if (ret)
  return ret;

 cgbc->version.feature = data[0];
 cgbc->version.major = data[1];
 cgbc->version.minor = data[2];

 return 0;
}

static int cgbc_init_device(struct cgbc_device_data *cgbc)
{
 int ret;

 ret = cgbc_session_request(cgbc);
 if (ret)
  return ret;

 ret = cgbc_get_version(cgbc);
 if (ret)
  goto release_session;

 ret = mfd_add_devices(cgbc->dev, -1, cgbc_devs, ARRAY_SIZE(cgbc_devs),
         NULL, 0, NULL);
 if (ret)
  goto release_session;

 return 0;

release_session:
 cgbc_session_release(cgbc);
 return ret;
}

static int cgbc_probe(struct platform_device *pdev)
{
 struct device *dev = &pdev->dev;
 struct cgbc_device_data *cgbc;
 int ret;

 cgbc = devm_kzalloc(dev, sizeof(*cgbc), GFP_KERNEL);
 if (!cgbc)
  return -ENOMEM;

 cgbc->dev = dev;

 ret = cgbc_map(cgbc);
 if (ret)
  return ret;

 mutex_init(&cgbc->lock);

 platform_set_drvdata(pdev, cgbc);

 return cgbc_init_device(cgbc);
}

static void cgbc_remove(struct platform_device *pdev)
{
 struct cgbc_device_data *cgbc = platform_get_drvdata(pdev);

 cgbc_session_release(cgbc);

 mfd_remove_devices(&pdev->dev);
}

static struct platform_driver cgbc_driver = {
 .driver  = {
  .name  = "cgbc",
  .dev_groups = cgbc_groups,
 },
 .probe  = cgbc_probe,
 .remove  = cgbc_remove,
};

static const struct dmi_system_id cgbc_dmi_table[] __initconst = {
 {
  .ident = "SA7",
  .matches = {
   DMI_MATCH(DMI_BOARD_VENDOR, "congatec"),
   DMI_MATCH(DMI_BOARD_NAME, "conga-SA7"),
  },
 },
 {
  .ident = "SA8",
  .matches = {
   DMI_MATCH(DMI_BOARD_VENDOR, "congatec"),
   DMI_MATCH(DMI_BOARD_NAME, "conga-SA8"),
  },
 },
 {}
};
MODULE_DEVICE_TABLE(dmi, cgbc_dmi_table);

static int __init cgbc_init(void)
{
 const struct dmi_system_id *id;
 int ret = -ENODEV;

 id = dmi_first_match(cgbc_dmi_table);
 if (IS_ERR_OR_NULL(id))
  return ret;

 cgbc_pdev = platform_device_register_simple("cgbc", PLATFORM_DEVID_NONE, cgbc_resources,
          ARRAY_SIZE(cgbc_resources));
 if (IS_ERR(cgbc_pdev))
  return PTR_ERR(cgbc_pdev);

 return platform_driver_register(&cgbc_driver);
}

static void __exit cgbc_exit(void)
{
 platform_device_unregister(cgbc_pdev);
 platform_driver_unregister(&cgbc_driver);
}

module_init(cgbc_init);
module_exit(cgbc_exit);

MODULE_DESCRIPTION("Congatec Board Controller Core Driver");
MODULE_AUTHOR("Thomas Richard ");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:cgbc-core");

Messung V0.5
C=95 H=96 G=95

¤ Dauer der Verarbeitung: 0.5 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.