/*
 * Common Cumulus platform module APIs.
 *
 * Copyright (C) 2017 Cumulus Networks, Inc.
 * Author: Curt Brune <curt@cumulusnetworks.com>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; version 2.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * https://www.gnu.org/licenses/gpl-2.0-standalone.html
 */

/**
 * This module acts as a library of common routines, used by Cumulus
 * platform kernel modules.
 *
 * This module is not a device driver.
 */

#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/delay.h>
#include <linux/cumulus-platform.h>

#define CUMULUS_PLATFORM_MODULE_VERSION "1.0"

/**
 * cumulus_i2c_find_adapter() - look-up i2c adapter
 * @name: name of i2c adapter to find
 *
 * Attempt to find the i2c adapter with the name @name.  Returns the
 * non-negative adapter index when found.  Returns -ENODEV if not
 * found.
 */
int cumulus_i2c_find_adapter(const char *name)
{
	int i;
	int rc = -ENODEV;
	struct i2c_adapter *adapter;

	for (i = 0; i < 50; i++) {
		adapter = i2c_get_adapter(i);
		if (adapter) {
			if (!strncmp(adapter->name, name, strlen(name)))
				rc = i;
			i2c_put_adapter(adapter);
		}
		if (rc >= 0)
			break;
	}

	return rc;
}
EXPORT_SYMBOL_GPL(cumulus_i2c_find_adapter);

/**
 * cumulus_i2c_add_client()
 * @adapter: i2c adapter number
 * @info: i2c_board_info
 *
 * Attempt to create a new i2c_client, attached to i2c @adapter.
 * @info contains specific information about the device to create.
 *
 * On succes returns a newly allocated i2c_client structure.  On
 * failure returns ERR_PTR(-ENODEV).
 */
struct i2c_client *
cumulus_i2c_add_client(int bus, struct i2c_board_info *info)
{
	struct i2c_adapter *adapter;
	struct i2c_client *client;
	int i = 15;

	/* Some i2c mux/switch adapters are slow to arrive in the
	 * device model. Also depends on whether the device driver is
	 * loaded via hotplug or not.  Allow up to 1500 msec for the
	 * adapter to show up.
	 */
	do {
		adapter = i2c_get_adapter(bus);
		if (adapter)
			break;
		msleep(100);
	} while (i--);

	if (!adapter)
		return ERR_PTR(-ENXIO);

	client = i2c_new_device(adapter, info);
	if (!client)
		client = ERR_PTR(-ENODEV);

	i2c_put_adapter(adapter);
	return client;
}
EXPORT_SYMBOL_GPL(cumulus_i2c_add_client);

/**
 * cumulus_gpio_map_show()
 * @dev: device driver object
 * @dattr: device attribute object
 * @buf: character buffer filled on output
 *
 * Return an array of GPIO signal names seperated by a newline
 * character.  The GPIO names are ordered by the GPIO number.
 *
 * Format of output:
 * line 1     -- base GPIO number
 * line 2     -- 1st GPIO name
 * ....
 * line n + 1 -- Nth GPIO name
 *
 */
ssize_t cumulus_gpio_map_show(struct device *dev,
			      struct gpio_chip *chip,
			      char *buf)
{
	int i;
	int length = PAGE_SIZE, size;
	char *p = buf;

	size = snprintf(p, length, "%u\n", chip->base);
	length -= size;
	p += size;

	for (i = 0; i < chip->ngpio; i++) {
		size = snprintf(p, length, "%s\n", chip->names[i]);
		if (size > length) {
			dev_warn(dev,
				 "GPIO map string larger than PAGE_SIZE, output truncated.\n");
			break;
		}
		length -= size;
		p += size;
	}

	return p - buf;
}
EXPORT_SYMBOL_GPL(cumulus_gpio_map_show);

MODULE_AUTHOR("Curt Brune <curt@cumulusnetworks.com");
MODULE_DESCRIPTION("Cumulus Platform Module Library");
MODULE_LICENSE("GPL");
MODULE_VERSION(CUMULUS_PLATFORM_MODULE_VERSION);
