/**
 * @file
 *
 * @ingroup libi2c
 *
 * @brief Legacy I2C Library
 */

#ifndef _RTEMS_LIBI2C_H
#define _RTEMS_LIBI2C_H

/*
 * Authorship
 * ----------
 * This software was created by
 *     Till Straumann <strauman@slac.stanford.edu>, 2005,
 * 	   Stanford Linear Accelerator Center, Stanford University.
 *
 * Acknowledgement of sponsorship
 * ------------------------------
 * This software was produced by
 *     the Stanford Linear Accelerator Center, Stanford University,
 * 	   under Contract DE-AC03-76SFO0515 with the Department of Energy.
 *
 * Government disclaimer of liability
 * ----------------------------------
 * Neither the United States nor the United States Department of Energy,
 * nor any of their employees, makes any warranty, express or implied, or
 * assumes any legal liability or responsibility for the accuracy,
 * completeness, or usefulness of any data, apparatus, product, or process
 * disclosed, or represents that its use would not infringe privately owned
 * rights.
 *
 * Stanford disclaimer of liability
 * --------------------------------
 * Stanford University makes no representations or warranties, express or
 * implied, nor assumes any liability for the use of this software.
 *
 * Stanford disclaimer of copyright
 * --------------------------------
 * Stanford University, owner of the copyright, hereby disclaims its
 * copyright and all other rights in this software.  Hence, anyone may
 * freely use it for any purpose without restriction.
 *
 * Maintenance of notices
 * ----------------------
 * In the interest of clarity regarding the origin and status of this
 * SLAC software, this and all the preceding Stanford University notices
 * are to remain affixed to any copy or derivative of this software made
 * or distributed by the recipient and are to be affixed to any copy of
 * software made or distributed by the recipient that contains a copy or
 * derivative of this software.
 *
 * ------------------ SLAC Software Notices, Set 4 OTT.002a, 2004 FEB 03
 */

#include <rtems.h>

#include <rtems/io.h>

#ifdef __cplusplus
extern "C" {
#endif

/**
 * @defgroup libi2c Legacy I2C Library
 *
 * @ingroup RTEMSDeviceDrivers
 *
 * @brief Legacy I2C library.
 *
 */
/**@{**/

/* Simple I2C driver API */

/* Initialize the libary - may fail if no semaphore or no driver slot is available */
extern int rtems_libi2c_initialize (void);

/* Alternatively to rtems_libi2c_initialize() the library can also be
 * initialized by means of a traditional driver table entry containing
 * the following entry points:
 */
extern rtems_status_code
rtems_i2c_init (
	rtems_device_major_number major,
	rtems_device_minor_number minor,
    void *arg);

extern rtems_status_code
rtems_i2c_open (
	rtems_device_major_number major,
	rtems_device_minor_number minor,
    void *arg);

extern rtems_status_code
rtems_i2c_close (
	rtems_device_major_number major,
	rtems_device_minor_number minor,
    void *arg);

extern rtems_status_code
rtems_i2c_read (
	rtems_device_major_number major,
	rtems_device_minor_number minor,
    void *arg);

extern rtems_status_code
rtems_i2c_write (
	rtems_device_major_number major,
	rtems_device_minor_number minor,
    void *arg);

extern rtems_status_code
rtems_i2c_ioctl (
	rtems_device_major_number major,
	rtems_device_minor_number minor,
    void *arg);

extern const rtems_driver_address_table rtems_libi2c_io_ops;

/* Unfortunately, if you want to add this driver to
 * a RTEMS configuration table then you need all the
 * members explicitly :-( (Device_driver_table should
 * hold pointers to rtems_driver_address_tables rather
 * than full structs).
 *
 * The difficulty is that adding this driver to the
 * configuration table is not enough; you still need
 * to populate the framework with low-level bus-driver(s)
 * and high-level drivers and/or device-files...
 *
 * Currently the preferred way is having the BSP
 * call 'rtems_libi2c_initialize' followed by
 * 'rtems_libi2c_register_bus' and
 * 'rtems_libi2c_register_drv' and/or
 * 'mknod' (for 'raw' device nodes)
 * from the 'bsp_predriver_hook'.
 */
#define RTEMS_LIBI2C_DRIVER_TABLE_ENTRY   \
{                                         \
  initialization_entry:  rtems_i2c_init,  \
  open_entry:            rtems_i2c_open,  \
  close_entry:           rtems_i2c_close, \
  read_entry:            rtems_i2c_read,  \
  write_entry:           rtems_i2c_write, \
  control_entry:         rtems_i2c_ioctl, \
}

/* Bus Driver API
 *
 * Bus drivers provide access to low-level i2c functions
 * such as 'send start', 'send address', 'get bytes' etc.
 */

/* first field must be a pointer to ops; driver
 * may add its own fields after this.
 * the struct that is registered with the library
 * is not copied; a pointer will we passed
 * to the callback functions (ops).
 */
typedef struct rtems_libi2c_bus_t_
{
  const struct rtems_libi2c_bus_ops_ *ops;
  int size;                     /* size of whole structure */
} rtems_libi2c_bus_t;

/* Access functions a low level driver must provide;
 *
 * All of these, except read_bytes and write_bytes
 * return RTEMS_SUCCESSFUL on success and an error status
 * otherwise. The read and write ops return the number
 * of chars read/written or -(status code) on error.
 */
typedef struct rtems_libi2c_bus_ops_
{
  /* Initialize the bus; might be called again to reset the bus driver */
  rtems_status_code (*init) (rtems_libi2c_bus_t * bushdl);
  /* Send start condition */
  rtems_status_code (*send_start) (rtems_libi2c_bus_t * bushdl);
  /* Send stop  condition */
  rtems_status_code (*send_stop) (rtems_libi2c_bus_t * bushdl);
  /* initiate transfer from (rw!=0) or to a device */
  rtems_status_code (*send_addr) (rtems_libi2c_bus_t * bushdl,
                                  uint32_t addr, int rw);
  /* read a number of bytes */
  int (*read_bytes) (rtems_libi2c_bus_t * bushdl, unsigned char *bytes,
                     int nbytes);
  /* write a number of bytes */
  int (*write_bytes) (rtems_libi2c_bus_t * bushdl, unsigned char *bytes,
                      int nbytes);
  /* ioctl misc functions */
  int (*ioctl) (rtems_libi2c_bus_t * bushdl,
		int   cmd,
		void *buffer
		);
} rtems_libi2c_bus_ops_t;


/*
 * Register a lowlevel driver
 *
 * TODO: better description
 *
 * This  allocates a major number identifying *this* driver
 * (i.e., libi2c) and the minor number encodes a bus# and a i2c address.
 *
 * The name will be registered in the filesystem (parent
 * directories must exist, also IMFS filesystem must exist). It may be NULL in
 * which case the library will pick a default.
 *
 * RETURNS: bus # (>=0) or -1 on error (errno set).
 */

extern int rtems_libi2c_register_bus (const char *name, rtems_libi2c_bus_t * bus);

extern rtems_device_major_number rtems_libi2c_major;

#define RTEMS_LIBI2C_MAKE_MINOR(busno, i2caddr)	\
	((((busno)&((1<<3)-1))<<10) | ((i2caddr)&((1<<10)-1)))

/* After the library is initialized, a major number is available.
 * As soon as a low-level bus driver is registered (above routine
 * returns a 'busno'), a device node can be created in the filesystem
 * with a major/minor number pair of
 *
 *    rtems_libi2c_major / RTEMS_LIBI2C_MAKE_MINOR(busno, i2caddr)
 *
 * and a 'raw' hi-level driver is then attached to this device
 * node.
 * This 'raw' driver has very simple semantics:
 *
 *   'open'         sends a start condition
 *   'read'/'write' address the device identified by the i2c bus# and address
 *                  encoded in the minor number and read or write, respectively
 *                  a stream of bytes from or to the device. Every time the
 *                  direction is changed, a 're-start' condition followed by
 *                  an 'address' cycle is generated on the i2c bus.
 *   'close'        sends a stop condition.
 *
 * Hence, using the 'raw' driver, e.g., 100 bytes at offset 0x200 can be
 * read from an EEPROM by the following pseudo-code:
 *
 *   mknod("/dev/i2c-54", mode, MKDEV(rtems_libi2c_major, RTEMS_LIBI2C_MAKE_MINOR(0,0x54)))
 *
 *   int fd;
 *   char off[2]={0x02,0x00};
 *
 *   fd = open("/dev/i2c-54",O_RDWR);
 *   write(fd,off,2);
 *   read(fd,buf,100);
 *   close(fd);
 *
 */

/* Higher Level Driver API
 *
 * Higher level drivers know how to deal with specific i2c
 * devices (independent of the bus interface chip) and provide
 * an abstraction, i.e., the usual read/write/ioctl access.
 *
 * Using the above example, such a high level driver could
 * prevent the user from issuing potentially destructive write
 * operations (the aforementioned EEPROM interprets any 3rd
 * and following byte written to the device as data, i.e., the
 * contents could easily be changed!).
 * The correct 'read-pointer offset' programming could be
 * implemented in 'open' and 'ioctl' of a high-level driver and
 * the user would then only have to perform harmless read
 * operations, e.g.,
 *
 *    fd = open("/dev/i2c.eeprom",O_RDONLY) / * opens and sets EEPROM read pointer * /
 *    ioctl(fd, IOCTL_SEEK, 0x200)			/ * repositions the read pointer       * /
 *    read(fd, buf, 100)
 *    close(fd)
 *
 */

/* struct provided at driver registration. The driver may store
 * private data behind the mandatory first fields but the size
 * must be set to the size of the entire struct, e.g.,
 *
 * struct driver_pvt {
 * 	rtems_libi2c_drv_t pub;
 * 	struct {  ...    } pvt;
 * } my_driver = {
 * 		{  ops: my_ops,
 * 		  size: sizeof(my_driver)
 * 		},
 * 		{ ...};
 * };
 *
 * A pointer to this struct is passed to the callback ops.
 */

typedef struct rtems_libi2c_drv_t_
{
  const rtems_driver_address_table *ops;      /* the driver ops */
  int size;                     /* size of whole structure (including appended private data) */
} rtems_libi2c_drv_t;

/*
 * The high level driver must be registered with a particular
 * bus number and i2c address.
 *
 * The registration procedure also creates a filesystem node,
 * i.e., the returned minor number is not really needed.
 *
 * If the 'name' argument is NULL, no filesystem node is
 * created (but this can be done 'manually' using rtems_libi2c_major
 * and the return value of this routine).
 *
 * RETURNS minor number (FYI) or -1 on failure
 */
extern int
rtems_libi2c_register_drv (const char *name, rtems_libi2c_drv_t * drvtbl,
                           unsigned bus, unsigned i2caddr);

/* Operations available to high level drivers */

/* NOTES: The bus a device is attached to is LOCKED from the first send_start
 *        until send_stop is executed!
 *
 *        Bus tenure MUST NOT span multiple system calls - otherwise, a single
 *        thread could get into the protected sections (or would deadlock if the
 *		  mutex was not nestable).
 *		  E.g., consider what happens if 'open' sends a 'start' and 'close'
 *		  sends a 'stop' (i.e., the bus mutex would be locked in 'open' and
 *        released in 'close'. A single thread could try to open two devices
 *        on the same bus and would either deadlock or nest into the bus mutex
 *        and potentially mess up the i2c messages.
 *
 *        The correct way is to *always* relinquish the i2c bus (i.e., send 'stop'
 *		  from any driver routine prior to returning control to the caller.
 *		  Consult the implementation of the generic driver routines (open, close, ...)
 *		  below or the examples in i2c-2b-eeprom.c and i2c-2b-ds1621.c
 *
 * Drivers just pass the minor number on to these routines...
 */
extern rtems_status_code rtems_libi2c_send_start (rtems_device_minor_number minor);

extern rtems_status_code rtems_libi2c_send_stop (rtems_device_minor_number minor);

extern rtems_status_code
rtems_libi2c_send_addr (rtems_device_minor_number minor, int rw);

/* the read/write routines return the number of bytes transferred
 * or -(status_code) on error.
 */
extern int
rtems_libi2c_read_bytes (rtems_device_minor_number minor,
                         unsigned char *bytes, int nbytes);

extern int
rtems_libi2c_write_bytes (rtems_device_minor_number minor,
                          const unsigned char *bytes, int nbytes);

/* Send start, send address and read bytes */
extern int
rtems_libi2c_start_read_bytes (rtems_device_minor_number minor,
			       unsigned char *bytes,
                               int nbytes);

/* Send start, send address and write bytes */
extern int
rtems_libi2c_start_write_bytes (rtems_device_minor_number minor,
				const unsigned char *bytes,
                                int nbytes);


/* call misc iocontrol function */
extern int
rtems_libi2c_ioctl (rtems_device_minor_number minor,
		    int cmd,
		    ...);
/*
 * NOTE: any low-level driver ioctl returning a negative
 * result for release the bus (perform a STOP condition)
 */
/*******************************
 * defined IOCTLs:
 *******************************/
#define RTEMS_LIBI2C_IOCTL_READ_WRITE  1
/*
 * retval = rtems_libi2c_ioctl(rtems_device_minor_number minor,
 *                             RTEMS_LIBI2C_IOCTL_READ_WRITE,
 *                              rtems_libi2c_read_write_t *arg);
 *
 * This call performs a simultanous read/write transfer,
 * which is possible (and sometimes needed) for SPI devices
 *
 *   arg is a pointer to a rd_wr info data structure
 *
 * This call is only needed for SPI devices
 */
#define RTEMS_LIBI2C_IOCTL_START_TFM_READ_WRITE  2
/*
 * retval = rtems_libi2c_ioctl(rtems_device_minor_number minor,
 *                             RTEMS_LIBI2C_IOCTL_START_READ_WRITE,
 *                             unsigned char *rd_buffer,
 *                             const unsigned char *wr_buffer,
 *                             int byte_cnt,
 *                             const rtems_libi2c_tfr_mode_t *tfr_mode_ptr);
 *
 * This call addresses a slave and then:
 * - sets the proper transfer mode,
 *  - performs a simultanous  read/write transfer,
 *    (which is possible and sometimes needed for SPI devices)
 *    NOTE: - if rd_buffer is NULL, receive data will be dropped
 *          - if wr_buffer is NULL, bytes with content 0 will transmitted
 *
 *   rd_buffer is a pointer to a receive buffer (or NULL)
 *   wr_buffer is a pointer to the data to be sent (or NULL)
 *
 * This call is only needed for SPI devices
 */

#define RTEMS_LIBI2C_IOCTL_SET_TFRMODE 3
/*
 * retval = rtems_libi2c_ioctl(rtems_device_minor_number minor,
 *                             RTEMS_LIBI2C_IOCTL_SET_TFRMODE,
 *                             const rtems_libi2c_tfr_mode_t *tfr_mode_ptr);
 *
 * This call sets an SPI device to the transfer mode needed (baudrate etc.)
 *
 *   tfr_mode is a pointer to a structure defining the SPI transfer mode needed
 *   (see below).
 *
 * This call is only needed for SPI devices
 */

#define RTEMS_LIBI2C_IOCTL_GET_DRV_T 4

/*
 * retval = rtems_libi2c_ioctl(rtems_device_minor_number minor,
 *                             RTEMS_LIBI2C_IOCTL_GET_DRV_T,
 *                             const rtems_libi2c_drv_t *drv_t_ptr);
 *
 * This call allows the a high-level driver to query its driver table entry,
 * including its private data appended to it during creation of the entry
 *
 */

/**
 * @brief IO control command for asynchronous read and write.
 *
 * @see rtems_libi2c_read_write_done_t and rtems_libi2c_read_write_async_t.
 *
 * @warning This is work in progress!
 */
#define RTEMS_LIBI2C_IOCTL_READ_WRITE_ASYNC 5

/*
 * argument data structures for IOCTLs defined above
 */
typedef struct {
  unsigned char       *rd_buf;
  const unsigned char *wr_buf;
  int                  byte_cnt;
} rtems_libi2c_read_write_t;

typedef struct {
  uint32_t baudrate;       /* maximum bits per second               */
                           /* only valid for SPI drivers:           */
  uint8_t  bits_per_char;  /* how many bits per byte/word/longword? */
  bool     lsb_first;      /* true: send LSB first                  */
  bool     clock_inv;      /* true: inverted clock (high active)    */
  bool     clock_phs;      /* true: clock starts toggling at start of data tfr */
  uint32_t  idle_char;     /* This character will be continuously transmitted in read only functions */
} rtems_libi2c_tfr_mode_t;

typedef struct {
  rtems_libi2c_tfr_mode_t   tfr_mode;
  rtems_libi2c_read_write_t rd_wr;
} rtems_libi2c_tfm_read_write_t;

/**
 * @brief Notification function type for asynchronous read and write.
 *
 * @see RTEMS_LIBI2C_IOCTL_READ_WRITE_ASYNC and
 * rtems_libi2c_read_write_async_t.
 *
 * @warning This is work in progress!
 */
typedef void (*rtems_libi2c_read_write_done_t) \
  ( int /* return value */, int /* nbytes */, void * /* arg */);

/**
 * @brief IO command data for asynchronous read and write.
 *
 * @see RTEMS_LIBI2C_IOCTL_READ_WRITE_ASYNC and
 * rtems_libi2c_read_write_done_t.
 *
 * @warning This is work in progress!
 */
typedef struct {
  unsigned char                 *rd_buf;
  const unsigned char           *wr_buf;
  int                            byte_cnt;
  rtems_libi2c_read_write_done_t done;
  void                          *arg;
} rtems_libi2c_read_write_async_t;

/** @} */

#ifdef __cplusplus
}
#endif

#endif
