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

Quelle  i2c-gxp.c   Sprache: C

 
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (C) 2022 Hewlett-Packard Enterprise Development Company, L.P. */

#include <linux/err.h>
#include <linux/io.h>
#include <linux/i2c.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/regmap.h>
#include <linux/mfd/syscon.h>

#define GXP_MAX_I2C_ENGINE 10
static const char * const gxp_i2c_name[] = {
 "gxp-i2c0""gxp-i2c1""gxp-i2c2""gxp-i2c3",
 "gxp-i2c4""gxp-i2c5""gxp-i2c6""gxp-i2c7",
 "gxp-i2c8""gxp-i2c9" };

/* GXP I2C Global interrupt status/enable register*/
#define GXP_I2CINTSTAT  0x00
#define GXP_I2CINTEN  0x04

/* GXP I2C registers */
#define GXP_I2CSTAT  0x00
#define MASK_STOP_EVENT  0x20
#define MASK_ACK  0x08
#define MASK_RW   0x04
#define GXP_I2CEVTERR  0x01
#define MASK_SLAVE_CMD_EVENT 0x01
#define MASK_SLAVE_DATA_EVENT 0x02
#define MASK_MASTER_EVENT 0x10
#define GXP_I2CSNPDAT  0x02
#define GXP_I2CMCMD  0x04
#define GXP_I2CSCMD  0x06
#define GXP_I2CSNPAA  0x09
#define GXP_I2CADVFEAT  0x0A
#define GXP_I2COWNADR  0x0B
#define GXP_I2CFREQDIV  0x0C
#define GXP_I2CFLTFAIR  0x0D
#define GXP_I2CTMOEDG  0x0E
#define GXP_I2CCYCTIM  0x0F

/* I2CSCMD Bits */
#define SNOOP_EVT_CLR  0x80
#define SLAVE_EVT_CLR  0x40
#define SNOOP_EVT_MASK  0x20
#define SLAVE_EVT_MASK  0x10
#define SLAVE_ACK_ENAB  0x08
#define SLAVE_EVT_STALL  0x01

/* I2CMCMD Bits */
#define MASTER_EVT_CLR  0x80
#define MASTER_ACK_ENAB  0x08
#define RW_CMD   0x04
#define STOP_CMD  0x02
#define START_CMD  0x01

/* I2CTMOEDG value */
#define GXP_DATA_EDGE_RST_CTRL 0x0a /* 30ns */

/* I2CFLTFAIR Bits */
#define FILTER_CNT  0x30
#define FAIRNESS_CNT  0x02

enum {
 GXP_I2C_IDLE = 0,
 GXP_I2C_ADDR_PHASE,
 GXP_I2C_RDATA_PHASE,
 GXP_I2C_WDATA_PHASE,
 GXP_I2C_ADDR_NACK,
 GXP_I2C_DATA_NACK,
 GXP_I2C_ERROR,
 GXP_I2C_COMP
};

struct gxp_i2c_drvdata {
 struct device *dev;
 void __iomem *base;
 struct i2c_timings t;
 u32 engine;
 int irq;
 struct completion completion;
 struct i2c_adapter adapter;
 struct i2c_msg *curr_msg;
 int msgs_remaining;
 int msgs_num;
 u8 *buf;
 size_t buf_remaining;
 unsigned char state;
 struct i2c_client *slave;
 unsigned char stopped;
};

static struct regmap *i2cg_map;

static void gxp_i2c_start(struct gxp_i2c_drvdata *drvdata)
{
 u16 value;

 drvdata->buf = drvdata->curr_msg->buf;
 drvdata->buf_remaining = drvdata->curr_msg->len;

 /* Note: Address in struct i2c_msg is 7 bits */
 value = drvdata->curr_msg->addr << 9;

 /* Read or Write */
 value |= drvdata->curr_msg->flags & I2C_M_RD ? RW_CMD | START_CMD : START_CMD;

 drvdata->state = GXP_I2C_ADDR_PHASE;
 writew(value, drvdata->base + GXP_I2CMCMD);
}

static int gxp_i2c_master_xfer(struct i2c_adapter *adapter,
          struct i2c_msg *msgs, int num)
{
 int ret;
 struct gxp_i2c_drvdata *drvdata = i2c_get_adapdata(adapter);
 unsigned long time_left;

 drvdata->msgs_remaining = num;
 drvdata->curr_msg = msgs;
 drvdata->msgs_num = num;
 reinit_completion(&drvdata->completion);

 gxp_i2c_start(drvdata);

 time_left = wait_for_completion_timeout(&drvdata->completion,
      adapter->timeout);
 ret = num - drvdata->msgs_remaining;
 if (time_left == 0)
  return -ETIMEDOUT;

 if (drvdata->state == GXP_I2C_ADDR_NACK)
  return -ENXIO;

 if (drvdata->state == GXP_I2C_DATA_NACK)
  return -EIO;

 return ret;
}

static u32 gxp_i2c_func(struct i2c_adapter *adap)
{
 if (IS_ENABLED(CONFIG_I2C_SLAVE))
  return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SLAVE;

 return I2C_FUNC_I2C | I2C_FUNC_SMBUS_EMUL;
}

#if IS_ENABLED(CONFIG_I2C_SLAVE)
static int gxp_i2c_reg_slave(struct i2c_client *slave)
{
 struct gxp_i2c_drvdata *drvdata = i2c_get_adapdata(slave->adapter);

 if (drvdata->slave)
  return -EBUSY;

 if (slave->flags & I2C_CLIENT_TEN)
  return -EAFNOSUPPORT;

 drvdata->slave = slave;

 writeb(slave->addr << 1, drvdata->base + GXP_I2COWNADR);
 writeb(SLAVE_EVT_CLR | SNOOP_EVT_MASK | SLAVE_ACK_ENAB |
        SLAVE_EVT_STALL, drvdata->base + GXP_I2CSCMD);

 return 0;
}

static int gxp_i2c_unreg_slave(struct i2c_client *slave)
{
 struct gxp_i2c_drvdata *drvdata = i2c_get_adapdata(slave->adapter);

 WARN_ON(!drvdata->slave);

 writeb(0x00, drvdata->base + GXP_I2COWNADR);
 writeb(SNOOP_EVT_CLR | SLAVE_EVT_CLR | SNOOP_EVT_MASK |
        SLAVE_EVT_MASK, drvdata->base + GXP_I2CSCMD);

 drvdata->slave = NULL;

 return 0;
}
#endif

static const struct i2c_algorithm gxp_i2c_algo = {
 .xfer = gxp_i2c_master_xfer,
 .functionality = gxp_i2c_func,
#if IS_ENABLED(CONFIG_I2C_SLAVE)
 .reg_slave = gxp_i2c_reg_slave,
 .unreg_slave = gxp_i2c_unreg_slave,
#endif
};

static void gxp_i2c_stop(struct gxp_i2c_drvdata *drvdata)
{
 /* Clear event and send stop */
 writeb(MASTER_EVT_CLR | STOP_CMD, drvdata->base + GXP_I2CMCMD);

 complete(&drvdata->completion);
}

static void gxp_i2c_restart(struct gxp_i2c_drvdata *drvdata)
{
 u16 value;

 drvdata->buf = drvdata->curr_msg->buf;
 drvdata->buf_remaining = drvdata->curr_msg->len;

 value = drvdata->curr_msg->addr << 9;

 if (drvdata->curr_msg->flags & I2C_M_RD) {
  /* Read and clear master event */
  value |= MASTER_EVT_CLR | RW_CMD | START_CMD;
 } else {
  /* Write and clear master event */
  value |= MASTER_EVT_CLR | START_CMD;
 }

 drvdata->state = GXP_I2C_ADDR_PHASE;

 writew(value, drvdata->base + GXP_I2CMCMD);
}

static void gxp_i2c_chk_addr_ack(struct gxp_i2c_drvdata *drvdata)
{
 u16 value;

 value = readb(drvdata->base + GXP_I2CSTAT);
 if (!(value & MASK_ACK)) {
  /* Got no ack, stop */
  drvdata->state = GXP_I2C_ADDR_NACK;
  gxp_i2c_stop(drvdata);
  return;
 }

 if (drvdata->curr_msg->flags & I2C_M_RD) {
  /* Start to read data from slave */
  if (drvdata->buf_remaining == 0) {
   /* No more data to read, stop */
   drvdata->msgs_remaining--;
   drvdata->state = GXP_I2C_COMP;
   gxp_i2c_stop(drvdata);
   return;
  }
  drvdata->state = GXP_I2C_RDATA_PHASE;

  if (drvdata->buf_remaining == 1) {
   /* The last data, do not ack */
   writeb(MASTER_EVT_CLR | RW_CMD,
          drvdata->base + GXP_I2CMCMD);
  } else {
   /* Read data and ack it */
   writeb(MASTER_EVT_CLR | MASTER_ACK_ENAB |
          RW_CMD, drvdata->base + GXP_I2CMCMD);
  }
 } else {
  /* Start to write first data to slave */
  if (drvdata->buf_remaining == 0) {
   /* No more data to write, stop */
   drvdata->msgs_remaining--;
   drvdata->state = GXP_I2C_COMP;
   gxp_i2c_stop(drvdata);
   return;
  }
  value = *drvdata->buf;
  value = value << 8;
  /* Clear master event */
  value |= MASTER_EVT_CLR;
  drvdata->buf++;
  drvdata->buf_remaining--;
  drvdata->state = GXP_I2C_WDATA_PHASE;
  writew(value, drvdata->base + GXP_I2CMCMD);
 }
}

static void gxp_i2c_ack_data(struct gxp_i2c_drvdata *drvdata)
{
 u8 value;

 /* Store the data returned */
 value = readb(drvdata->base + GXP_I2CSNPDAT);
 *drvdata->buf = value;
 drvdata->buf++;
 drvdata->buf_remaining--;

 if (drvdata->buf_remaining == 0) {
  /* No more data, this message is completed. */
  drvdata->msgs_remaining--;

  if (drvdata->msgs_remaining == 0) {
   /* No more messages, stop */
   drvdata->state = GXP_I2C_COMP;
   gxp_i2c_stop(drvdata);
   return;
  }
  /* Move to next message and start transfer */
  drvdata->curr_msg++;
  gxp_i2c_restart(drvdata);
  return;
 }

 /* Ack the slave to make it send next byte */
 drvdata->state = GXP_I2C_RDATA_PHASE;
 if (drvdata->buf_remaining == 1) {
  /* The last data, do not ack */
  writeb(MASTER_EVT_CLR | RW_CMD,
         drvdata->base + GXP_I2CMCMD);
 } else {
  /* Read data and ack it */
  writeb(MASTER_EVT_CLR | MASTER_ACK_ENAB |
         RW_CMD, drvdata->base + GXP_I2CMCMD);
 }
}

static void gxp_i2c_chk_data_ack(struct gxp_i2c_drvdata *drvdata)
{
 u16 value;

 value = readb(drvdata->base + GXP_I2CSTAT);
 if (!(value & MASK_ACK)) {
  /* Received No ack, stop */
  drvdata->state = GXP_I2C_DATA_NACK;
  gxp_i2c_stop(drvdata);
  return;
 }

 /* Got ack, check if there is more data to write */
 if (drvdata->buf_remaining == 0) {
  /* No more data, this message is completed */
  drvdata->msgs_remaining--;

  if (drvdata->msgs_remaining == 0) {
   /* No more messages, stop */
   drvdata->state = GXP_I2C_COMP;
   gxp_i2c_stop(drvdata);
   return;
  }
  /* Move to next message and start transfer */
  drvdata->curr_msg++;
  gxp_i2c_restart(drvdata);
  return;
 }

 /* Write data to slave */
 value = *drvdata->buf;
 value = value << 8;

 /* Clear master event */
 value |= MASTER_EVT_CLR;
 drvdata->buf++;
 drvdata->buf_remaining--;
 drvdata->state = GXP_I2C_WDATA_PHASE;
 writew(value, drvdata->base + GXP_I2CMCMD);
}

static bool gxp_i2c_slave_irq_handler(struct gxp_i2c_drvdata *drvdata)
{
 u8 value;
 u8 buf;
 int ret;

 value = readb(drvdata->base + GXP_I2CEVTERR);

 /* Received start or stop event */
 if (value & MASK_SLAVE_CMD_EVENT) {
  value = readb(drvdata->base + GXP_I2CSTAT);
  /* Master sent stop */
  if (value & MASK_STOP_EVENT) {
   if (drvdata->stopped == 0)
    i2c_slave_event(drvdata->slave, I2C_SLAVE_STOP, &buf);
   writeb(SLAVE_EVT_CLR | SNOOP_EVT_MASK |
          SLAVE_ACK_ENAB | SLAVE_EVT_STALL, drvdata->base + GXP_I2CSCMD);
   drvdata->stopped = 1;
  } else {
   /* Master sent start and  wants to read */
   drvdata->stopped = 0;
   if (value & MASK_RW) {
    i2c_slave_event(drvdata->slave,
      I2C_SLAVE_READ_REQUESTED, &buf);
    value = buf << 8 | (SLAVE_EVT_CLR | SNOOP_EVT_MASK |
          SLAVE_EVT_STALL);
    writew(value, drvdata->base + GXP_I2CSCMD);
   } else {
    /* Master wants to write to us */
    ret = i2c_slave_event(drvdata->slave,
            I2C_SLAVE_WRITE_REQUESTED, &buf);
    if (!ret) {
     /* Ack next byte from master */
     writeb(SLAVE_EVT_CLR | SNOOP_EVT_MASK |
            SLAVE_ACK_ENAB | SLAVE_EVT_STALL,
            drvdata->base + GXP_I2CSCMD);
    } else {
     /* Nack next byte from master */
     writeb(SLAVE_EVT_CLR | SNOOP_EVT_MASK |
            SLAVE_EVT_STALL, drvdata->base + GXP_I2CSCMD);
    }
   }
  }
 } else if (value & MASK_SLAVE_DATA_EVENT) {
  value = readb(drvdata->base + GXP_I2CSTAT);
  /* Master wants to read */
  if (value & MASK_RW) {
   /* Master wants another byte */
   if (value & MASK_ACK) {
    i2c_slave_event(drvdata->slave,
      I2C_SLAVE_READ_PROCESSED, &buf);
    value = buf << 8 | (SLAVE_EVT_CLR | SNOOP_EVT_MASK |
          SLAVE_EVT_STALL);
    writew(value, drvdata->base + GXP_I2CSCMD);
   } else {
    /* No more bytes needed */
    writew(SLAVE_EVT_CLR | SNOOP_EVT_MASK |
           SLAVE_ACK_ENAB | SLAVE_EVT_STALL,
           drvdata->base + GXP_I2CSCMD);
   }
  } else {
   /* Master wants to write to us */
   value = readb(drvdata->base + GXP_I2CSNPDAT);
   buf = (uint8_t)value;
   ret = i2c_slave_event(drvdata->slave,
           I2C_SLAVE_WRITE_RECEIVED, &buf);
   if (!ret) {
    /* Ack next byte from master */
    writeb(SLAVE_EVT_CLR | SNOOP_EVT_MASK |
           SLAVE_ACK_ENAB | SLAVE_EVT_STALL,
           drvdata->base + GXP_I2CSCMD);
   } else {
    /* Nack next byte from master */
    writeb(SLAVE_EVT_CLR | SNOOP_EVT_MASK |
           SLAVE_EVT_STALL, drvdata->base + GXP_I2CSCMD);
   }
  }
 } else {
  return false;
 }

 return true;
}

static irqreturn_t gxp_i2c_irq_handler(int irq, void *_drvdata)
{
 struct gxp_i2c_drvdata *drvdata = (struct gxp_i2c_drvdata *)_drvdata;
 u32 value;

 /* Check if the interrupt is for the current engine */
 regmap_read(i2cg_map, GXP_I2CINTSTAT, &value);
 if (!(value & BIT(drvdata->engine)))
  return IRQ_NONE;

 value = readb(drvdata->base + GXP_I2CEVTERR);

 /* Error */
 if (value & ~(MASK_MASTER_EVENT | MASK_SLAVE_CMD_EVENT |
    MASK_SLAVE_DATA_EVENT)) {
  /* Clear all events */
  writeb(0x00, drvdata->base + GXP_I2CEVTERR);
  drvdata->state = GXP_I2C_ERROR;
  gxp_i2c_stop(drvdata);
  return IRQ_HANDLED;
 }

 if (IS_ENABLED(CONFIG_I2C_SLAVE)) {
  /* Slave mode */
  if (value & (MASK_SLAVE_CMD_EVENT | MASK_SLAVE_DATA_EVENT)) {
   if (gxp_i2c_slave_irq_handler(drvdata))
    return IRQ_HANDLED;
   return IRQ_NONE;
  }
 }

 /*  Master mode */
 switch (drvdata->state) {
 case GXP_I2C_ADDR_PHASE:
  gxp_i2c_chk_addr_ack(drvdata);
  break;

 case GXP_I2C_RDATA_PHASE:
  gxp_i2c_ack_data(drvdata);
  break;

 case GXP_I2C_WDATA_PHASE:
  gxp_i2c_chk_data_ack(drvdata);
  break;
 }

 return IRQ_HANDLED;
}

static void gxp_i2c_init(struct gxp_i2c_drvdata *drvdata)
{
 drvdata->state = GXP_I2C_IDLE;
 writeb(2000000 / drvdata->t.bus_freq_hz,
        drvdata->base + GXP_I2CFREQDIV);
 writeb(FILTER_CNT | FAIRNESS_CNT,
        drvdata->base + GXP_I2CFLTFAIR);
 writeb(GXP_DATA_EDGE_RST_CTRL, drvdata->base + GXP_I2CTMOEDG);
 writeb(0x00, drvdata->base + GXP_I2CCYCTIM);
 writeb(0x00, drvdata->base + GXP_I2CSNPAA);
 writeb(0x00, drvdata->base + GXP_I2CADVFEAT);
 writeb(SNOOP_EVT_CLR | SLAVE_EVT_CLR | SNOOP_EVT_MASK |
        SLAVE_EVT_MASK, drvdata->base + GXP_I2CSCMD);
 writeb(MASTER_EVT_CLR, drvdata->base + GXP_I2CMCMD);
 writeb(0x00, drvdata->base + GXP_I2CEVTERR);
 writeb(0x00, drvdata->base + GXP_I2COWNADR);
}

static int gxp_i2c_probe(struct platform_device *pdev)
{
 struct gxp_i2c_drvdata *drvdata;
 int rc;
 struct i2c_adapter *adapter;

 if (!i2cg_map) {
  i2cg_map = syscon_regmap_lookup_by_phandle(pdev->dev.of_node,
          "hpe,sysreg");
  if (IS_ERR(i2cg_map)) {
   return dev_err_probe(&pdev->dev, PTR_ERR(i2cg_map),
          "failed to map i2cg_handle\n");
  }

  /* Disable interrupt */
  regmap_update_bits(i2cg_map, GXP_I2CINTEN, 0x00000FFF, 0);
 }

 drvdata = devm_kzalloc(&pdev->dev, sizeof(*drvdata),
          GFP_KERNEL);
 if (!drvdata)
  return -ENOMEM;

 platform_set_drvdata(pdev, drvdata);
 drvdata->dev = &pdev->dev;
 init_completion(&drvdata->completion);

 drvdata->base = devm_platform_ioremap_resource(pdev, 0);
 if (IS_ERR(drvdata->base))
  return PTR_ERR(drvdata->base);

 /* Use physical memory address to determine which I2C engine this is. */
 drvdata->engine = ((size_t)drvdata->base & 0xf00) >> 8;

 if (drvdata->engine >= GXP_MAX_I2C_ENGINE) {
  return dev_err_probe(&pdev->dev, -EINVAL, "i2c engine% is unsupported\n",
   drvdata->engine);
 }

 rc = platform_get_irq(pdev, 0);
 if (rc < 0)
  return rc;

 drvdata->irq = rc;
 rc = devm_request_irq(&pdev->dev, drvdata->irq, gxp_i2c_irq_handler,
         IRQF_SHARED, gxp_i2c_name[drvdata->engine], drvdata);
 if (rc < 0)
  return dev_err_probe(&pdev->dev, rc, "irq request failed\n");

 i2c_parse_fw_timings(&pdev->dev, &drvdata->t, true);

 gxp_i2c_init(drvdata);

 /* Enable interrupt */
 regmap_update_bits(i2cg_map, GXP_I2CINTEN, BIT(drvdata->engine),
      BIT(drvdata->engine));

 adapter = &drvdata->adapter;
 i2c_set_adapdata(adapter, drvdata);

 adapter->owner = THIS_MODULE;
 strscpy(adapter->name, "HPE GXP I2C adapter"sizeof(adapter->name));
 adapter->algo = &gxp_i2c_algo;
 adapter->dev.parent = &pdev->dev;
 adapter->dev.of_node = pdev->dev.of_node;

 rc = i2c_add_adapter(adapter);
 if (rc)
  return dev_err_probe(&pdev->dev, rc, "i2c add adapter failed\n");

 return 0;
}

static void gxp_i2c_remove(struct platform_device *pdev)
{
 struct gxp_i2c_drvdata *drvdata = platform_get_drvdata(pdev);

 /* Disable interrupt */
 regmap_update_bits(i2cg_map, GXP_I2CINTEN, BIT(drvdata->engine), 0);
 i2c_del_adapter(&drvdata->adapter);
}

static const struct of_device_id gxp_i2c_of_match[] = {
 { .compatible = "hpe,gxp-i2c" },
 {},
};
MODULE_DEVICE_TABLE(of, gxp_i2c_of_match);

static struct platform_driver gxp_i2c_driver = {
 .probe = gxp_i2c_probe,
 .remove = gxp_i2c_remove,
 .driver = {
  .name = "gxp-i2c",
  .of_match_table = gxp_i2c_of_match,
 },
};
module_platform_driver(gxp_i2c_driver);

MODULE_AUTHOR("Nick Hawkins ");
MODULE_DESCRIPTION("HPE GXP I2C bus driver");
MODULE_LICENSE("GPL");

Messung V0.5
C=89 H=100 G=94

¤ 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.