Make packages for kmod-i2c-mux-pinctrl and kmod-rtc-pcf85063 using sources
taken from linux-5.10.146 and include them into the image.
This commit is contained in:
parent
18475fca74
commit
0a0851250a
|
@ -28,6 +28,8 @@ make defconfig
|
||||||
# Enable collectd network encryption
|
# Enable collectd network encryption
|
||||||
echo "CONFIG_PACKAGE_COLLECTD_ENCRYPTED_NETWORK=y" >> .config
|
echo "CONFIG_PACKAGE_COLLECTD_ENCRYPTED_NETWORK=y" >> .config
|
||||||
|
|
||||||
|
make package/kmod-i2c-mux-pinctrl/compile
|
||||||
|
make package/kmod-rtc-pcf85063/compile
|
||||||
make package/asterisk-chan-quectel/compile
|
make package/asterisk-chan-quectel/compile
|
||||||
make package/collectd/compile
|
make package/collectd/compile
|
||||||
# no signing key
|
# no signing key
|
||||||
|
@ -55,8 +57,9 @@ echo "CONFIG_TARGET_ROOTFS_PARTSIZE=308" >> .config
|
||||||
# Disable ext4 images
|
# Disable ext4 images
|
||||||
echo "CONFIG_TARGET_ROOTFS_EXT4FS=n" >> .config
|
echo "CONFIG_TARGET_ROOTFS_EXT4FS=n" >> .config
|
||||||
|
|
||||||
PACKAGES="kmod-i2c-bcm2835 kmod-i2c-mux kmod-nf-nathelper-extra \
|
PACKAGES="kmod-i2c-bcm2835 kmod-i2c-mux kmod-i2c-mux-pinctrl \
|
||||||
kmod-rtc-ds1307 kmod-rtc-pcf8563 \
|
kmod-nf-nathelper-extra \
|
||||||
|
kmod-rtc-ds1307 kmod-rtc-pcf8563 kmod-rtc-pcf85063 \
|
||||||
kmod-usb-audio kmod-usb-net-rtl8152 \
|
kmod-usb-audio kmod-usb-net-rtl8152 \
|
||||||
alsa-lib pciutils usbutils \
|
alsa-lib pciutils usbutils \
|
||||||
kmod-usb-net-qmi-wwan libqmi qmi-utils uqmi luci-proto-qmi \
|
kmod-usb-net-qmi-wwan libqmi qmi-utils uqmi luci-proto-qmi \
|
||||||
|
|
41
feed/kmod-i2c-mux-pinctrl/Makefile
Normal file
41
feed/kmod-i2c-mux-pinctrl/Makefile
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
include $(INCLUDE_DIR)/kernel.mk
|
||||||
|
|
||||||
|
PKG_NAME:=i2c-mux-pinctrl
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define KernelPackage/$(PKG_NAME)
|
||||||
|
SUBMENU:=I2C support
|
||||||
|
TITLE:=pinctrl-based I2C multiplexer
|
||||||
|
DEPENDS:=+kmod-i2c-mux
|
||||||
|
AUTOLOAD:=$(call AutoLoad,51,i2c-mux-pinctrl,1)
|
||||||
|
FILES:=$(PKG_BUILD_DIR)/i2c-mux-pinctrl.ko
|
||||||
|
KCONFIG:=
|
||||||
|
endef
|
||||||
|
|
||||||
|
define KernelPackage/$(PKG_NAME)/description
|
||||||
|
Kernel modules for GENERIC_PINCTRL I2C bus mux/switching devices
|
||||||
|
endef
|
||||||
|
|
||||||
|
EXTRA_KCONFIG:= \
|
||||||
|
CONFIG_I2C_MUX_PINCTRL=m
|
||||||
|
|
||||||
|
EXTRA_CFLAGS:= \
|
||||||
|
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=m,%,$(filter %=m,$(EXTRA_KCONFIG)))) \
|
||||||
|
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=y,%,$(filter %=y,$(EXTRA_KCONFIG)))) \
|
||||||
|
|
||||||
|
MAKE_OPTS:= \
|
||||||
|
$(KERNEL_MAKE_FLAGS) \
|
||||||
|
M="$(PKG_BUILD_DIR)" \
|
||||||
|
EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
|
||||||
|
$(EXTRA_KCONFIG)
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
$(MAKE) -C "$(LINUX_DIR)" \
|
||||||
|
$(MAKE_OPTS) \
|
||||||
|
modules
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call KernelPackage,$(PKG_NAME)))
|
8
feed/kmod-i2c-mux-pinctrl/src/Makefile
Normal file
8
feed/kmod-i2c-mux-pinctrl/src/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
#
|
||||||
|
# Makefile for multiplexer I2C chip drivers.
|
||||||
|
|
||||||
|
obj-$(CONFIG_I2C_MUX_GPIO) += i2c-mux-gpio.o
|
||||||
|
obj-$(CONFIG_I2C_MUX_PINCTRL) += i2c-mux-pinctrl.o
|
||||||
|
|
||||||
|
ccflags-$(CONFIG_I2C_DEBUG_BUS) := -DDEBUG
|
184
feed/kmod-i2c-mux-pinctrl/src/gpiolib.h
Normal file
184
feed/kmod-i2c-mux-pinctrl/src/gpiolib.h
Normal file
|
@ -0,0 +1,184 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0 */
|
||||||
|
/*
|
||||||
|
* Internal GPIO functions.
|
||||||
|
*
|
||||||
|
* Copyright (C) 2013, Intel Corporation
|
||||||
|
* Author: Mika Westerberg <mika.westerberg@linux.intel.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef GPIOLIB_H
|
||||||
|
#define GPIOLIB_H
|
||||||
|
|
||||||
|
#include <linux/gpio/driver.h>
|
||||||
|
#include <linux/gpio/consumer.h> /* for enum gpiod_flags */
|
||||||
|
#include <linux/err.h>
|
||||||
|
#include <linux/device.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/cdev.h>
|
||||||
|
|
||||||
|
#define GPIOCHIP_NAME "gpiochip"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct gpio_device - internal state container for GPIO devices
|
||||||
|
* @id: numerical ID number for the GPIO chip
|
||||||
|
* @dev: the GPIO device struct
|
||||||
|
* @chrdev: character device for the GPIO device
|
||||||
|
* @mockdev: class device used by the deprecated sysfs interface (may be
|
||||||
|
* NULL)
|
||||||
|
* @owner: helps prevent removal of modules exporting active GPIOs
|
||||||
|
* @chip: pointer to the corresponding gpiochip, holding static
|
||||||
|
* data for this device
|
||||||
|
* @descs: array of ngpio descriptors.
|
||||||
|
* @ngpio: the number of GPIO lines on this GPIO device, equal to the size
|
||||||
|
* of the @descs array.
|
||||||
|
* @base: GPIO base in the DEPRECATED global Linux GPIO numberspace, assigned
|
||||||
|
* at device creation time.
|
||||||
|
* @label: a descriptive name for the GPIO device, such as the part number
|
||||||
|
* or name of the IP component in a System on Chip.
|
||||||
|
* @data: per-instance data assigned by the driver
|
||||||
|
* @list: links gpio_device:s together for traversal
|
||||||
|
*
|
||||||
|
* This state container holds most of the runtime variable data
|
||||||
|
* for a GPIO device and can hold references and live on after the
|
||||||
|
* GPIO chip has been removed, if it is still being used from
|
||||||
|
* userspace.
|
||||||
|
*/
|
||||||
|
struct gpio_device {
|
||||||
|
int id;
|
||||||
|
struct device dev;
|
||||||
|
struct cdev chrdev;
|
||||||
|
struct device *mockdev;
|
||||||
|
struct module *owner;
|
||||||
|
struct gpio_chip *chip;
|
||||||
|
struct gpio_desc *descs;
|
||||||
|
int base;
|
||||||
|
u16 ngpio;
|
||||||
|
const char *label;
|
||||||
|
void *data;
|
||||||
|
struct list_head list;
|
||||||
|
struct blocking_notifier_head notifier;
|
||||||
|
|
||||||
|
#ifdef CONFIG_PINCTRL
|
||||||
|
/*
|
||||||
|
* If CONFIG_PINCTRL is enabled, then gpio controllers can optionally
|
||||||
|
* describe the actual pin range which they serve in an SoC. This
|
||||||
|
* information would be used by pinctrl subsystem to configure
|
||||||
|
* corresponding pins for gpio usage.
|
||||||
|
*/
|
||||||
|
struct list_head pin_ranges;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/* gpio suffixes used for ACPI and device tree lookup */
|
||||||
|
static __maybe_unused const char * const gpio_suffixes[] = { "gpios", "gpio" };
|
||||||
|
|
||||||
|
struct gpio_array {
|
||||||
|
struct gpio_desc **desc;
|
||||||
|
unsigned int size;
|
||||||
|
struct gpio_chip *chip;
|
||||||
|
unsigned long *get_mask;
|
||||||
|
unsigned long *set_mask;
|
||||||
|
unsigned long invert_mask[];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct gpio_desc *gpiochip_get_desc(struct gpio_chip *gc, unsigned int hwnum);
|
||||||
|
int gpiod_get_array_value_complex(bool raw, bool can_sleep,
|
||||||
|
unsigned int array_size,
|
||||||
|
struct gpio_desc **desc_array,
|
||||||
|
struct gpio_array *array_info,
|
||||||
|
unsigned long *value_bitmap);
|
||||||
|
int gpiod_set_array_value_complex(bool raw, bool can_sleep,
|
||||||
|
unsigned int array_size,
|
||||||
|
struct gpio_desc **desc_array,
|
||||||
|
struct gpio_array *array_info,
|
||||||
|
unsigned long *value_bitmap);
|
||||||
|
|
||||||
|
extern spinlock_t gpio_lock;
|
||||||
|
extern struct list_head gpio_devices;
|
||||||
|
|
||||||
|
struct gpio_desc {
|
||||||
|
struct gpio_device *gdev;
|
||||||
|
unsigned long flags;
|
||||||
|
/* flag symbols are bit numbers */
|
||||||
|
#define FLAG_REQUESTED 0
|
||||||
|
#define FLAG_IS_OUT 1
|
||||||
|
#define FLAG_EXPORT 2 /* protected by sysfs_lock */
|
||||||
|
#define FLAG_SYSFS 3 /* exported via /sys/class/gpio/control */
|
||||||
|
#define FLAG_ACTIVE_LOW 6 /* value has active low */
|
||||||
|
#define FLAG_OPEN_DRAIN 7 /* Gpio is open drain type */
|
||||||
|
#define FLAG_OPEN_SOURCE 8 /* Gpio is open source type */
|
||||||
|
#define FLAG_USED_AS_IRQ 9 /* GPIO is connected to an IRQ */
|
||||||
|
#define FLAG_IRQ_IS_ENABLED 10 /* GPIO is connected to an enabled IRQ */
|
||||||
|
#define FLAG_IS_HOGGED 11 /* GPIO is hogged */
|
||||||
|
#define FLAG_TRANSITORY 12 /* GPIO may lose value in sleep or reset */
|
||||||
|
#define FLAG_PULL_UP 13 /* GPIO has pull up enabled */
|
||||||
|
#define FLAG_PULL_DOWN 14 /* GPIO has pull down enabled */
|
||||||
|
#define FLAG_BIAS_DISABLE 15 /* GPIO has pull disabled */
|
||||||
|
#define FLAG_EDGE_RISING 16 /* GPIO CDEV detects rising edge events */
|
||||||
|
#define FLAG_EDGE_FALLING 17 /* GPIO CDEV detects falling edge events */
|
||||||
|
|
||||||
|
/* Connection label */
|
||||||
|
const char *label;
|
||||||
|
/* Name of the GPIO */
|
||||||
|
const char *name;
|
||||||
|
#ifdef CONFIG_OF_DYNAMIC
|
||||||
|
struct device_node *hog;
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_GPIO_CDEV
|
||||||
|
/* debounce period in microseconds */
|
||||||
|
unsigned int debounce_period_us;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
int gpiod_request(struct gpio_desc *desc, const char *label);
|
||||||
|
void gpiod_free(struct gpio_desc *desc);
|
||||||
|
int gpiod_configure_flags(struct gpio_desc *desc, const char *con_id,
|
||||||
|
unsigned long lflags, enum gpiod_flags dflags);
|
||||||
|
int gpiod_hog(struct gpio_desc *desc, const char *name,
|
||||||
|
unsigned long lflags, enum gpiod_flags dflags);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Return the GPIO number of the passed descriptor relative to its chip
|
||||||
|
*/
|
||||||
|
static inline int gpio_chip_hwgpio(const struct gpio_desc *desc)
|
||||||
|
{
|
||||||
|
return desc - &desc->gdev->descs[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
/* With descriptor prefix */
|
||||||
|
|
||||||
|
#define gpiod_emerg(desc, fmt, ...) \
|
||||||
|
pr_emerg("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?",\
|
||||||
|
##__VA_ARGS__)
|
||||||
|
#define gpiod_crit(desc, fmt, ...) \
|
||||||
|
pr_crit("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
|
||||||
|
##__VA_ARGS__)
|
||||||
|
#define gpiod_err(desc, fmt, ...) \
|
||||||
|
pr_err("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
|
||||||
|
##__VA_ARGS__)
|
||||||
|
#define gpiod_warn(desc, fmt, ...) \
|
||||||
|
pr_warn("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
|
||||||
|
##__VA_ARGS__)
|
||||||
|
#define gpiod_info(desc, fmt, ...) \
|
||||||
|
pr_info("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?", \
|
||||||
|
##__VA_ARGS__)
|
||||||
|
#define gpiod_dbg(desc, fmt, ...) \
|
||||||
|
pr_debug("gpio-%d (%s): " fmt, desc_to_gpio(desc), desc->label ? : "?",\
|
||||||
|
##__VA_ARGS__)
|
||||||
|
|
||||||
|
/* With chip prefix */
|
||||||
|
|
||||||
|
#define chip_emerg(gc, fmt, ...) \
|
||||||
|
dev_emerg(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
|
||||||
|
#define chip_crit(gc, fmt, ...) \
|
||||||
|
dev_crit(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
|
||||||
|
#define chip_err(gc, fmt, ...) \
|
||||||
|
dev_err(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
|
||||||
|
#define chip_warn(gc, fmt, ...) \
|
||||||
|
dev_warn(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
|
||||||
|
#define chip_info(gc, fmt, ...) \
|
||||||
|
dev_info(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
|
||||||
|
#define chip_dbg(gc, fmt, ...) \
|
||||||
|
dev_dbg(&gc->gpiodev->dev, "(%s): " fmt, gc->label, ##__VA_ARGS__)
|
||||||
|
|
||||||
|
#endif /* GPIOLIB_H */
|
243
feed/kmod-i2c-mux-pinctrl/src/i2c-mux-gpio.c
Normal file
243
feed/kmod-i2c-mux-pinctrl/src/i2c-mux-gpio.c
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* I2C multiplexer using GPIO API
|
||||||
|
*
|
||||||
|
* Peter Korsgaard <peter.korsgaard@barco.com>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/i2c-mux.h>
|
||||||
|
#include <linux/platform_data/i2c-mux-gpio.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/bits.h>
|
||||||
|
#include <linux/gpio/consumer.h>
|
||||||
|
/* FIXME: stop poking around inside gpiolib */
|
||||||
|
#include "gpiolib.h"
|
||||||
|
|
||||||
|
struct gpiomux {
|
||||||
|
struct i2c_mux_gpio_platform_data data;
|
||||||
|
int ngpios;
|
||||||
|
struct gpio_desc **gpios;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void i2c_mux_gpio_set(const struct gpiomux *mux, unsigned val)
|
||||||
|
{
|
||||||
|
DECLARE_BITMAP(values, BITS_PER_TYPE(val));
|
||||||
|
|
||||||
|
values[0] = val;
|
||||||
|
|
||||||
|
gpiod_set_array_value_cansleep(mux->ngpios, mux->gpios, NULL, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int i2c_mux_gpio_select(struct i2c_mux_core *muxc, u32 chan)
|
||||||
|
{
|
||||||
|
struct gpiomux *mux = i2c_mux_priv(muxc);
|
||||||
|
|
||||||
|
i2c_mux_gpio_set(mux, chan);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int i2c_mux_gpio_deselect(struct i2c_mux_core *muxc, u32 chan)
|
||||||
|
{
|
||||||
|
struct gpiomux *mux = i2c_mux_priv(muxc);
|
||||||
|
|
||||||
|
i2c_mux_gpio_set(mux, mux->data.idle);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static int i2c_mux_gpio_probe_dt(struct gpiomux *mux,
|
||||||
|
struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device_node *np = pdev->dev.of_node;
|
||||||
|
struct device_node *adapter_np, *child;
|
||||||
|
struct i2c_adapter *adapter;
|
||||||
|
unsigned *values;
|
||||||
|
int i = 0;
|
||||||
|
|
||||||
|
if (!np)
|
||||||
|
return -ENODEV;
|
||||||
|
|
||||||
|
adapter_np = of_parse_phandle(np, "i2c-parent", 0);
|
||||||
|
if (!adapter_np) {
|
||||||
|
dev_err(&pdev->dev, "Cannot parse i2c-parent\n");
|
||||||
|
return -ENODEV;
|
||||||
|
}
|
||||||
|
adapter = of_find_i2c_adapter_by_node(adapter_np);
|
||||||
|
of_node_put(adapter_np);
|
||||||
|
if (!adapter)
|
||||||
|
return -EPROBE_DEFER;
|
||||||
|
|
||||||
|
mux->data.parent = i2c_adapter_id(adapter);
|
||||||
|
put_device(&adapter->dev);
|
||||||
|
|
||||||
|
mux->data.n_values = of_get_child_count(np);
|
||||||
|
|
||||||
|
values = devm_kcalloc(&pdev->dev,
|
||||||
|
mux->data.n_values, sizeof(*mux->data.values),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!values) {
|
||||||
|
dev_err(&pdev->dev, "Cannot allocate values array");
|
||||||
|
return -ENOMEM;
|
||||||
|
}
|
||||||
|
|
||||||
|
for_each_child_of_node(np, child) {
|
||||||
|
of_property_read_u32(child, "reg", values + i);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
mux->data.values = values;
|
||||||
|
|
||||||
|
if (of_property_read_u32(np, "idle-state", &mux->data.idle))
|
||||||
|
mux->data.idle = I2C_MUX_GPIO_NO_IDLE;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
static int i2c_mux_gpio_probe_dt(struct gpiomux *mux,
|
||||||
|
struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static int i2c_mux_gpio_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct i2c_mux_core *muxc;
|
||||||
|
struct gpiomux *mux;
|
||||||
|
struct i2c_adapter *parent;
|
||||||
|
struct i2c_adapter *root;
|
||||||
|
unsigned initial_state;
|
||||||
|
int i, ngpios, ret;
|
||||||
|
|
||||||
|
mux = devm_kzalloc(&pdev->dev, sizeof(*mux), GFP_KERNEL);
|
||||||
|
if (!mux)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (!dev_get_platdata(&pdev->dev)) {
|
||||||
|
ret = i2c_mux_gpio_probe_dt(mux, pdev);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
} else {
|
||||||
|
memcpy(&mux->data, dev_get_platdata(&pdev->dev),
|
||||||
|
sizeof(mux->data));
|
||||||
|
}
|
||||||
|
|
||||||
|
ngpios = gpiod_count(&pdev->dev, "mux");
|
||||||
|
if (ngpios <= 0) {
|
||||||
|
dev_err(&pdev->dev, "no valid gpios provided\n");
|
||||||
|
return ngpios ?: -EINVAL;
|
||||||
|
}
|
||||||
|
mux->ngpios = ngpios;
|
||||||
|
|
||||||
|
parent = i2c_get_adapter(mux->data.parent);
|
||||||
|
if (!parent)
|
||||||
|
return -EPROBE_DEFER;
|
||||||
|
|
||||||
|
muxc = i2c_mux_alloc(parent, &pdev->dev, mux->data.n_values,
|
||||||
|
ngpios * sizeof(*mux->gpios), 0,
|
||||||
|
i2c_mux_gpio_select, NULL);
|
||||||
|
if (!muxc) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto alloc_failed;
|
||||||
|
}
|
||||||
|
mux->gpios = muxc->priv;
|
||||||
|
muxc->priv = mux;
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, muxc);
|
||||||
|
|
||||||
|
root = i2c_root_adapter(&parent->dev);
|
||||||
|
|
||||||
|
muxc->mux_locked = true;
|
||||||
|
|
||||||
|
if (mux->data.idle != I2C_MUX_GPIO_NO_IDLE) {
|
||||||
|
initial_state = mux->data.idle;
|
||||||
|
muxc->deselect = i2c_mux_gpio_deselect;
|
||||||
|
} else {
|
||||||
|
initial_state = mux->data.values[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < ngpios; i++) {
|
||||||
|
struct device *gpio_dev;
|
||||||
|
struct gpio_desc *gpiod;
|
||||||
|
enum gpiod_flags flag;
|
||||||
|
|
||||||
|
if (initial_state & BIT(i))
|
||||||
|
flag = GPIOD_OUT_HIGH;
|
||||||
|
else
|
||||||
|
flag = GPIOD_OUT_LOW;
|
||||||
|
gpiod = devm_gpiod_get_index(&pdev->dev, "mux", i, flag);
|
||||||
|
if (IS_ERR(gpiod)) {
|
||||||
|
ret = PTR_ERR(gpiod);
|
||||||
|
goto alloc_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
mux->gpios[i] = gpiod;
|
||||||
|
|
||||||
|
if (!muxc->mux_locked)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
/* FIXME: find a proper way to access the GPIO device */
|
||||||
|
gpio_dev = &gpiod->gdev->dev;
|
||||||
|
muxc->mux_locked = i2c_root_adapter(gpio_dev) == root;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (muxc->mux_locked)
|
||||||
|
dev_info(&pdev->dev, "mux-locked i2c mux\n");
|
||||||
|
|
||||||
|
for (i = 0; i < mux->data.n_values; i++) {
|
||||||
|
u32 nr = mux->data.base_nr ? (mux->data.base_nr + i) : 0;
|
||||||
|
unsigned int class = mux->data.classes ? mux->data.classes[i] : 0;
|
||||||
|
|
||||||
|
ret = i2c_mux_add_adapter(muxc, nr, mux->data.values[i], class);
|
||||||
|
if (ret)
|
||||||
|
goto add_adapter_failed;
|
||||||
|
}
|
||||||
|
|
||||||
|
dev_info(&pdev->dev, "%d port mux on %s adapter\n",
|
||||||
|
mux->data.n_values, parent->name);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
add_adapter_failed:
|
||||||
|
i2c_mux_del_adapters(muxc);
|
||||||
|
alloc_failed:
|
||||||
|
i2c_put_adapter(parent);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int i2c_mux_gpio_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct i2c_mux_core *muxc = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
i2c_mux_del_adapters(muxc);
|
||||||
|
i2c_put_adapter(muxc->parent);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id i2c_mux_gpio_of_match[] = {
|
||||||
|
{ .compatible = "i2c-mux-gpio", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, i2c_mux_gpio_of_match);
|
||||||
|
|
||||||
|
static struct platform_driver i2c_mux_gpio_driver = {
|
||||||
|
.probe = i2c_mux_gpio_probe,
|
||||||
|
.remove = i2c_mux_gpio_remove,
|
||||||
|
.driver = {
|
||||||
|
.name = "i2c-mux-gpio",
|
||||||
|
.of_match_table = i2c_mux_gpio_of_match,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module_platform_driver(i2c_mux_gpio_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("GPIO-based I2C multiplexer driver");
|
||||||
|
MODULE_AUTHOR("Peter Korsgaard <peter.korsgaard@barco.com>");
|
||||||
|
MODULE_LICENSE("GPL");
|
||||||
|
MODULE_ALIAS("platform:i2c-mux-gpio");
|
198
feed/kmod-i2c-mux-pinctrl/src/i2c-mux-pinctrl.c
Normal file
198
feed/kmod-i2c-mux-pinctrl/src/i2c-mux-pinctrl.c
Normal file
|
@ -0,0 +1,198 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only
|
||||||
|
/*
|
||||||
|
* I2C multiplexer using pinctrl API
|
||||||
|
*
|
||||||
|
* Copyright (c) 2012, NVIDIA CORPORATION. All rights reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/i2c-mux.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/pinctrl/consumer.h>
|
||||||
|
#include <linux/platform_device.h>
|
||||||
|
#include <linux/slab.h>
|
||||||
|
#include <linux/of.h>
|
||||||
|
#include "pinctrl_core.h"
|
||||||
|
|
||||||
|
struct i2c_mux_pinctrl {
|
||||||
|
struct pinctrl *pinctrl;
|
||||||
|
struct pinctrl_state *states[];
|
||||||
|
};
|
||||||
|
|
||||||
|
static int i2c_mux_pinctrl_select(struct i2c_mux_core *muxc, u32 chan)
|
||||||
|
{
|
||||||
|
struct i2c_mux_pinctrl *mux = i2c_mux_priv(muxc);
|
||||||
|
|
||||||
|
return pinctrl_select_state(mux->pinctrl, mux->states[chan]);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int i2c_mux_pinctrl_deselect(struct i2c_mux_core *muxc, u32 chan)
|
||||||
|
{
|
||||||
|
return i2c_mux_pinctrl_select(muxc, muxc->num_adapters);
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct i2c_adapter *i2c_mux_pinctrl_root_adapter(
|
||||||
|
struct pinctrl_state *state)
|
||||||
|
{
|
||||||
|
struct i2c_adapter *root = NULL;
|
||||||
|
struct pinctrl_setting *setting;
|
||||||
|
struct i2c_adapter *pin_root;
|
||||||
|
|
||||||
|
list_for_each_entry(setting, &state->settings, node) {
|
||||||
|
pin_root = i2c_root_adapter(setting->pctldev->dev);
|
||||||
|
if (!pin_root)
|
||||||
|
return NULL;
|
||||||
|
if (!root)
|
||||||
|
root = pin_root;
|
||||||
|
else if (root != pin_root)
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct i2c_adapter *i2c_mux_pinctrl_parent_adapter(struct device *dev)
|
||||||
|
{
|
||||||
|
struct device_node *np = dev->of_node;
|
||||||
|
struct device_node *parent_np;
|
||||||
|
struct i2c_adapter *parent;
|
||||||
|
|
||||||
|
parent_np = of_parse_phandle(np, "i2c-parent", 0);
|
||||||
|
if (!parent_np) {
|
||||||
|
dev_err(dev, "Cannot parse i2c-parent\n");
|
||||||
|
return ERR_PTR(-ENODEV);
|
||||||
|
}
|
||||||
|
parent = of_find_i2c_adapter_by_node(parent_np);
|
||||||
|
of_node_put(parent_np);
|
||||||
|
if (!parent)
|
||||||
|
return ERR_PTR(-EPROBE_DEFER);
|
||||||
|
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int i2c_mux_pinctrl_probe(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct device *dev = &pdev->dev;
|
||||||
|
struct device_node *np = dev->of_node;
|
||||||
|
struct i2c_mux_core *muxc;
|
||||||
|
struct i2c_mux_pinctrl *mux;
|
||||||
|
struct i2c_adapter *parent;
|
||||||
|
struct i2c_adapter *root;
|
||||||
|
int num_names, i, ret;
|
||||||
|
const char *name;
|
||||||
|
|
||||||
|
num_names = of_property_count_strings(np, "pinctrl-names");
|
||||||
|
if (num_names < 0) {
|
||||||
|
dev_err(dev, "Cannot parse pinctrl-names: %d\n",
|
||||||
|
num_names);
|
||||||
|
return num_names;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent = i2c_mux_pinctrl_parent_adapter(dev);
|
||||||
|
if (IS_ERR(parent))
|
||||||
|
return PTR_ERR(parent);
|
||||||
|
|
||||||
|
muxc = i2c_mux_alloc(parent, dev, num_names,
|
||||||
|
struct_size(mux, states, num_names),
|
||||||
|
0, i2c_mux_pinctrl_select, NULL);
|
||||||
|
if (!muxc) {
|
||||||
|
ret = -ENOMEM;
|
||||||
|
goto err_put_parent;
|
||||||
|
}
|
||||||
|
mux = i2c_mux_priv(muxc);
|
||||||
|
|
||||||
|
platform_set_drvdata(pdev, muxc);
|
||||||
|
|
||||||
|
mux->pinctrl = devm_pinctrl_get(dev);
|
||||||
|
if (IS_ERR(mux->pinctrl)) {
|
||||||
|
ret = PTR_ERR(mux->pinctrl);
|
||||||
|
dev_err(dev, "Cannot get pinctrl: %d\n", ret);
|
||||||
|
goto err_put_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (i = 0; i < num_names; i++) {
|
||||||
|
ret = of_property_read_string_index(np, "pinctrl-names", i,
|
||||||
|
&name);
|
||||||
|
if (ret < 0) {
|
||||||
|
dev_err(dev, "Cannot parse pinctrl-names: %d\n", ret);
|
||||||
|
goto err_put_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
mux->states[i] = pinctrl_lookup_state(mux->pinctrl, name);
|
||||||
|
if (IS_ERR(mux->states[i])) {
|
||||||
|
ret = PTR_ERR(mux->states[i]);
|
||||||
|
dev_err(dev, "Cannot look up pinctrl state %s: %d\n",
|
||||||
|
name, ret);
|
||||||
|
goto err_put_parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strcmp(name, "idle"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (i != num_names - 1) {
|
||||||
|
dev_err(dev, "idle state must be last\n");
|
||||||
|
ret = -EINVAL;
|
||||||
|
goto err_put_parent;
|
||||||
|
}
|
||||||
|
muxc->deselect = i2c_mux_pinctrl_deselect;
|
||||||
|
}
|
||||||
|
|
||||||
|
root = i2c_root_adapter(&muxc->parent->dev);
|
||||||
|
|
||||||
|
muxc->mux_locked = true;
|
||||||
|
for (i = 0; i < num_names; i++) {
|
||||||
|
if (root != i2c_mux_pinctrl_root_adapter(mux->states[i])) {
|
||||||
|
muxc->mux_locked = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (muxc->mux_locked)
|
||||||
|
dev_info(dev, "mux-locked i2c mux\n");
|
||||||
|
|
||||||
|
/* Do not add any adapter for the idle state (if it's there at all). */
|
||||||
|
for (i = 0; i < num_names - !!muxc->deselect; i++) {
|
||||||
|
ret = i2c_mux_add_adapter(muxc, 0, i, 0);
|
||||||
|
if (ret)
|
||||||
|
goto err_del_adapter;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_del_adapter:
|
||||||
|
i2c_mux_del_adapters(muxc);
|
||||||
|
err_put_parent:
|
||||||
|
i2c_put_adapter(parent);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int i2c_mux_pinctrl_remove(struct platform_device *pdev)
|
||||||
|
{
|
||||||
|
struct i2c_mux_core *muxc = platform_get_drvdata(pdev);
|
||||||
|
|
||||||
|
i2c_mux_del_adapters(muxc);
|
||||||
|
i2c_put_adapter(muxc->parent);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct of_device_id i2c_mux_pinctrl_of_match[] = {
|
||||||
|
{ .compatible = "i2c-mux-pinctrl", },
|
||||||
|
{},
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, i2c_mux_pinctrl_of_match);
|
||||||
|
|
||||||
|
static struct platform_driver i2c_mux_pinctrl_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "i2c-mux-pinctrl",
|
||||||
|
.of_match_table = of_match_ptr(i2c_mux_pinctrl_of_match),
|
||||||
|
},
|
||||||
|
.probe = i2c_mux_pinctrl_probe,
|
||||||
|
.remove = i2c_mux_pinctrl_remove,
|
||||||
|
};
|
||||||
|
module_platform_driver(i2c_mux_pinctrl_driver);
|
||||||
|
|
||||||
|
MODULE_DESCRIPTION("pinctrl-based I2C multiplexer driver");
|
||||||
|
MODULE_AUTHOR("Stephen Warren <swarren@nvidia.com>");
|
||||||
|
MODULE_LICENSE("GPL v2");
|
||||||
|
MODULE_ALIAS("platform:i2c-mux-pinctrl");
|
249
feed/kmod-i2c-mux-pinctrl/src/pinctrl_core.h
Normal file
249
feed/kmod-i2c-mux-pinctrl/src/pinctrl_core.h
Normal file
|
@ -0,0 +1,249 @@
|
||||||
|
/* SPDX-License-Identifier: GPL-2.0-only */
|
||||||
|
/*
|
||||||
|
* Core private header for the pin control subsystem
|
||||||
|
*
|
||||||
|
* Copyright (C) 2011 ST-Ericsson SA
|
||||||
|
* Written on behalf of Linaro for ST-Ericsson
|
||||||
|
*
|
||||||
|
* Author: Linus Walleij <linus.walleij@linaro.org>
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <linux/kref.h>
|
||||||
|
#include <linux/mutex.h>
|
||||||
|
#include <linux/radix-tree.h>
|
||||||
|
#include <linux/pinctrl/pinconf.h>
|
||||||
|
#include <linux/pinctrl/machine.h>
|
||||||
|
|
||||||
|
struct pinctrl_gpio_range;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pinctrl_dev - pin control class device
|
||||||
|
* @node: node to include this pin controller in the global pin controller list
|
||||||
|
* @desc: the pin controller descriptor supplied when initializing this pin
|
||||||
|
* controller
|
||||||
|
* @pin_desc_tree: each pin descriptor for this pin controller is stored in
|
||||||
|
* this radix tree
|
||||||
|
* @pin_group_tree: optionally each pin group can be stored in this radix tree
|
||||||
|
* @num_groups: optionally number of groups can be kept here
|
||||||
|
* @pin_function_tree: optionally each function can be stored in this radix tree
|
||||||
|
* @num_functions: optionally number of functions can be kept here
|
||||||
|
* @gpio_ranges: a list of GPIO ranges that is handled by this pin controller,
|
||||||
|
* ranges are added to this list at runtime
|
||||||
|
* @dev: the device entry for this pin controller
|
||||||
|
* @owner: module providing the pin controller, used for refcounting
|
||||||
|
* @driver_data: driver data for drivers registering to the pin controller
|
||||||
|
* subsystem
|
||||||
|
* @p: result of pinctrl_get() for this device
|
||||||
|
* @hog_default: default state for pins hogged by this device
|
||||||
|
* @hog_sleep: sleep state for pins hogged by this device
|
||||||
|
* @mutex: mutex taken on each pin controller specific action
|
||||||
|
* @device_root: debugfs root for this device
|
||||||
|
*/
|
||||||
|
struct pinctrl_dev {
|
||||||
|
struct list_head node;
|
||||||
|
struct pinctrl_desc *desc;
|
||||||
|
struct radix_tree_root pin_desc_tree;
|
||||||
|
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
|
||||||
|
struct radix_tree_root pin_group_tree;
|
||||||
|
unsigned int num_groups;
|
||||||
|
#endif
|
||||||
|
#ifdef CONFIG_GENERIC_PINMUX_FUNCTIONS
|
||||||
|
struct radix_tree_root pin_function_tree;
|
||||||
|
unsigned int num_functions;
|
||||||
|
#endif
|
||||||
|
struct list_head gpio_ranges;
|
||||||
|
struct device *dev;
|
||||||
|
struct module *owner;
|
||||||
|
void *driver_data;
|
||||||
|
struct pinctrl *p;
|
||||||
|
struct pinctrl_state *hog_default;
|
||||||
|
struct pinctrl_state *hog_sleep;
|
||||||
|
struct mutex mutex;
|
||||||
|
#ifdef CONFIG_DEBUG_FS
|
||||||
|
struct dentry *device_root;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pinctrl - per-device pin control state holder
|
||||||
|
* @node: global list node
|
||||||
|
* @dev: the device using this pin control handle
|
||||||
|
* @states: a list of states for this device
|
||||||
|
* @state: the current state
|
||||||
|
* @dt_maps: the mapping table chunks dynamically parsed from device tree for
|
||||||
|
* this device, if any
|
||||||
|
* @users: reference count
|
||||||
|
*/
|
||||||
|
struct pinctrl {
|
||||||
|
struct list_head node;
|
||||||
|
struct device *dev;
|
||||||
|
struct list_head states;
|
||||||
|
struct pinctrl_state *state;
|
||||||
|
struct list_head dt_maps;
|
||||||
|
struct kref users;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pinctrl_state - a pinctrl state for a device
|
||||||
|
* @node: list node for struct pinctrl's @states field
|
||||||
|
* @name: the name of this state
|
||||||
|
* @settings: a list of settings for this state
|
||||||
|
*/
|
||||||
|
struct pinctrl_state {
|
||||||
|
struct list_head node;
|
||||||
|
const char *name;
|
||||||
|
struct list_head settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pinctrl_setting_mux - setting data for MAP_TYPE_MUX_GROUP
|
||||||
|
* @group: the group selector to program
|
||||||
|
* @func: the function selector to program
|
||||||
|
*/
|
||||||
|
struct pinctrl_setting_mux {
|
||||||
|
unsigned group;
|
||||||
|
unsigned func;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pinctrl_setting_configs - setting data for MAP_TYPE_CONFIGS_*
|
||||||
|
* @group_or_pin: the group selector or pin ID to program
|
||||||
|
* @configs: a pointer to an array of config parameters/values to program into
|
||||||
|
* hardware. Each individual pin controller defines the format and meaning
|
||||||
|
* of config parameters.
|
||||||
|
* @num_configs: the number of entries in array @configs
|
||||||
|
*/
|
||||||
|
struct pinctrl_setting_configs {
|
||||||
|
unsigned group_or_pin;
|
||||||
|
unsigned long *configs;
|
||||||
|
unsigned num_configs;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pinctrl_setting - an individual mux or config setting
|
||||||
|
* @node: list node for struct pinctrl_settings's @settings field
|
||||||
|
* @type: the type of setting
|
||||||
|
* @pctldev: pin control device handling to be programmed. Not used for
|
||||||
|
* PIN_MAP_TYPE_DUMMY_STATE.
|
||||||
|
* @dev_name: the name of the device using this state
|
||||||
|
* @data: Data specific to the setting type
|
||||||
|
*/
|
||||||
|
struct pinctrl_setting {
|
||||||
|
struct list_head node;
|
||||||
|
enum pinctrl_map_type type;
|
||||||
|
struct pinctrl_dev *pctldev;
|
||||||
|
const char *dev_name;
|
||||||
|
union {
|
||||||
|
struct pinctrl_setting_mux mux;
|
||||||
|
struct pinctrl_setting_configs configs;
|
||||||
|
} data;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pin_desc - pin descriptor for each physical pin in the arch
|
||||||
|
* @pctldev: corresponding pin control device
|
||||||
|
* @name: a name for the pin, e.g. the name of the pin/pad/finger on a
|
||||||
|
* datasheet or such
|
||||||
|
* @dynamic_name: if the name of this pin was dynamically allocated
|
||||||
|
* @drv_data: driver-defined per-pin data. pinctrl core does not touch this
|
||||||
|
* @mux_usecount: If zero, the pin is not claimed, and @owner should be NULL.
|
||||||
|
* If non-zero, this pin is claimed by @owner. This field is an integer
|
||||||
|
* rather than a boolean, since pinctrl_get() might process multiple
|
||||||
|
* mapping table entries that refer to, and hence claim, the same group
|
||||||
|
* or pin, and each of these will increment the @usecount.
|
||||||
|
* @mux_owner: The name of device that called pinctrl_get().
|
||||||
|
* @mux_setting: The most recent selected mux setting for this pin, if any.
|
||||||
|
* @gpio_owner: If pinctrl_gpio_request() was called for this pin, this is
|
||||||
|
* the name of the GPIO that "owns" this pin.
|
||||||
|
*/
|
||||||
|
struct pin_desc {
|
||||||
|
struct pinctrl_dev *pctldev;
|
||||||
|
const char *name;
|
||||||
|
bool dynamic_name;
|
||||||
|
void *drv_data;
|
||||||
|
/* These fields only added when supporting pinmux drivers */
|
||||||
|
#ifdef CONFIG_PINMUX
|
||||||
|
unsigned mux_usecount;
|
||||||
|
const char *mux_owner;
|
||||||
|
const struct pinctrl_setting_mux *mux_setting;
|
||||||
|
const char *gpio_owner;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct pinctrl_maps - a list item containing part of the mapping table
|
||||||
|
* @node: mapping table list node
|
||||||
|
* @maps: array of mapping table entries
|
||||||
|
* @num_maps: the number of entries in @maps
|
||||||
|
*/
|
||||||
|
struct pinctrl_maps {
|
||||||
|
struct list_head node;
|
||||||
|
const struct pinctrl_map *maps;
|
||||||
|
unsigned num_maps;
|
||||||
|
};
|
||||||
|
|
||||||
|
#ifdef CONFIG_GENERIC_PINCTRL_GROUPS
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct group_desc - generic pin group descriptor
|
||||||
|
* @name: name of the pin group
|
||||||
|
* @pins: array of pins that belong to the group
|
||||||
|
* @num_pins: number of pins in the group
|
||||||
|
* @data: pin controller driver specific data
|
||||||
|
*/
|
||||||
|
struct group_desc {
|
||||||
|
const char *name;
|
||||||
|
int *pins;
|
||||||
|
int num_pins;
|
||||||
|
void *data;
|
||||||
|
};
|
||||||
|
|
||||||
|
int pinctrl_generic_get_group_count(struct pinctrl_dev *pctldev);
|
||||||
|
|
||||||
|
const char *pinctrl_generic_get_group_name(struct pinctrl_dev *pctldev,
|
||||||
|
unsigned int group_selector);
|
||||||
|
|
||||||
|
int pinctrl_generic_get_group_pins(struct pinctrl_dev *pctldev,
|
||||||
|
unsigned int group_selector,
|
||||||
|
const unsigned int **pins,
|
||||||
|
unsigned int *npins);
|
||||||
|
|
||||||
|
struct group_desc *pinctrl_generic_get_group(struct pinctrl_dev *pctldev,
|
||||||
|
unsigned int group_selector);
|
||||||
|
|
||||||
|
int pinctrl_generic_add_group(struct pinctrl_dev *pctldev, const char *name,
|
||||||
|
int *gpins, int ngpins, void *data);
|
||||||
|
|
||||||
|
int pinctrl_generic_remove_group(struct pinctrl_dev *pctldev,
|
||||||
|
unsigned int group_selector);
|
||||||
|
|
||||||
|
#endif /* CONFIG_GENERIC_PINCTRL_GROUPS */
|
||||||
|
|
||||||
|
struct pinctrl_dev *get_pinctrl_dev_from_devname(const char *dev_name);
|
||||||
|
struct pinctrl_dev *get_pinctrl_dev_from_of_node(struct device_node *np);
|
||||||
|
int pin_get_from_name(struct pinctrl_dev *pctldev, const char *name);
|
||||||
|
const char *pin_get_name(struct pinctrl_dev *pctldev, const unsigned pin);
|
||||||
|
int pinctrl_get_group_selector(struct pinctrl_dev *pctldev,
|
||||||
|
const char *pin_group);
|
||||||
|
|
||||||
|
static inline struct pin_desc *pin_desc_get(struct pinctrl_dev *pctldev,
|
||||||
|
unsigned int pin)
|
||||||
|
{
|
||||||
|
return radix_tree_lookup(&pctldev->pin_desc_tree, pin);
|
||||||
|
}
|
||||||
|
|
||||||
|
extern struct pinctrl_gpio_range *
|
||||||
|
pinctrl_find_gpio_range_from_pin_nolock(struct pinctrl_dev *pctldev,
|
||||||
|
unsigned int pin);
|
||||||
|
|
||||||
|
extern int pinctrl_force_sleep(struct pinctrl_dev *pctldev);
|
||||||
|
extern int pinctrl_force_default(struct pinctrl_dev *pctldev);
|
||||||
|
|
||||||
|
extern struct mutex pinctrl_maps_mutex;
|
||||||
|
extern struct list_head pinctrl_maps;
|
||||||
|
|
||||||
|
#define for_each_maps(_maps_node_, _i_, _map_) \
|
||||||
|
list_for_each_entry(_maps_node_, &pinctrl_maps, node) \
|
||||||
|
for (_i_ = 0, _map_ = &_maps_node_->maps[_i_]; \
|
||||||
|
_i_ < _maps_node_->num_maps; \
|
||||||
|
_i_++, _map_ = &_maps_node_->maps[_i_])
|
42
feed/kmod-rtc-pcf85063/Makefile
Normal file
42
feed/kmod-rtc-pcf85063/Makefile
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
include $(TOPDIR)/rules.mk
|
||||||
|
include $(INCLUDE_DIR)/kernel.mk
|
||||||
|
|
||||||
|
PKG_NAME:=rtc-pcf85063
|
||||||
|
PKG_RELEASE:=1
|
||||||
|
|
||||||
|
include $(INCLUDE_DIR)/package.mk
|
||||||
|
|
||||||
|
define KernelPackage/$(PKG_NAME)
|
||||||
|
SUBMENU:=Other modules
|
||||||
|
TITLE:=Philips PCF85063 RTC support
|
||||||
|
DEPENDS:=+kmod-i2c-core +kmod-regmap-i2c
|
||||||
|
AUTOLOAD:=$(call AutoProbe,rtc-pcf85063)
|
||||||
|
FILES:=$(PKG_BUILD_DIR)/rtc-pcf85063.ko
|
||||||
|
KCONFIG:=
|
||||||
|
endef
|
||||||
|
|
||||||
|
define KernelPackage/$(PKG_NAME)/description
|
||||||
|
Kernel module for Philips PCF85063 RTC chip.
|
||||||
|
endef
|
||||||
|
|
||||||
|
EXTRA_KCONFIG:= \
|
||||||
|
CONFIG_RTC_DRV_PCF85063=m \
|
||||||
|
CONFIG_RTC_CLASS=y
|
||||||
|
|
||||||
|
EXTRA_CFLAGS:= \
|
||||||
|
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=m,%,$(filter %=m,$(EXTRA_KCONFIG)))) \
|
||||||
|
$(patsubst CONFIG_%, -DCONFIG_%=1, $(patsubst %=y,%,$(filter %=y,$(EXTRA_KCONFIG)))) \
|
||||||
|
|
||||||
|
MAKE_OPTS:= \
|
||||||
|
$(KERNEL_MAKE_FLAGS) \
|
||||||
|
M="$(PKG_BUILD_DIR)" \
|
||||||
|
EXTRA_CFLAGS="$(EXTRA_CFLAGS)" \
|
||||||
|
$(EXTRA_KCONFIG)
|
||||||
|
|
||||||
|
define Build/Compile
|
||||||
|
$(MAKE) -C "$(LINUX_DIR)" \
|
||||||
|
$(MAKE_OPTS) \
|
||||||
|
modules
|
||||||
|
endef
|
||||||
|
|
||||||
|
$(eval $(call KernelPackage,$(PKG_NAME)))
|
8
feed/kmod-rtc-pcf85063/src/Makefile
Normal file
8
feed/kmod-rtc-pcf85063/src/Makefile
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
# SPDX-License-Identifier: GPL-2.0
|
||||||
|
#
|
||||||
|
# Makefile for RTC class/drivers.
|
||||||
|
#
|
||||||
|
|
||||||
|
ccflags-$(CONFIG_RTC_DEBUG) := -DDEBUG
|
||||||
|
|
||||||
|
obj-$(CONFIG_RTC_DRV_PCF85063) += rtc-pcf85063.o
|
642
feed/kmod-rtc-pcf85063/src/rtc-pcf85063.c
Normal file
642
feed/kmod-rtc-pcf85063/src/rtc-pcf85063.c
Normal file
|
@ -0,0 +1,642 @@
|
||||||
|
// SPDX-License-Identifier: GPL-2.0
|
||||||
|
/*
|
||||||
|
* An I2C driver for the PCF85063 RTC
|
||||||
|
* Copyright 2014 Rose Technology
|
||||||
|
*
|
||||||
|
* Author: Søren Andersen <san@rosetechnology.dk>
|
||||||
|
* Maintainers: http://www.nslu2-linux.org/
|
||||||
|
*
|
||||||
|
* Copyright (C) 2019 Micro Crystal AG
|
||||||
|
* Author: Alexandre Belloni <alexandre.belloni@bootlin.com>
|
||||||
|
*/
|
||||||
|
#include <linux/clk-provider.h>
|
||||||
|
#include <linux/i2c.h>
|
||||||
|
#include <linux/bcd.h>
|
||||||
|
#include <linux/rtc.h>
|
||||||
|
#include <linux/module.h>
|
||||||
|
#include <linux/of_device.h>
|
||||||
|
#include <linux/pm_wakeirq.h>
|
||||||
|
#include <linux/regmap.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Information for this driver was pulled from the following datasheets.
|
||||||
|
*
|
||||||
|
* https://www.nxp.com/documents/data_sheet/PCF85063A.pdf
|
||||||
|
* https://www.nxp.com/documents/data_sheet/PCF85063TP.pdf
|
||||||
|
*
|
||||||
|
* PCF85063A -- Rev. 6 — 18 November 2015
|
||||||
|
* PCF85063TP -- Rev. 4 — 6 May 2015
|
||||||
|
*
|
||||||
|
* https://www.microcrystal.com/fileadmin/Media/Products/RTC/App.Manual/RV-8263-C7_App-Manual.pdf
|
||||||
|
* RV8263 -- Rev. 1.0 — January 2019
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define PCF85063_REG_CTRL1 0x00 /* status */
|
||||||
|
#define PCF85063_REG_CTRL1_CAP_SEL BIT(0)
|
||||||
|
#define PCF85063_REG_CTRL1_STOP BIT(5)
|
||||||
|
|
||||||
|
#define PCF85063_REG_CTRL2 0x01
|
||||||
|
#define PCF85063_CTRL2_AF BIT(6)
|
||||||
|
#define PCF85063_CTRL2_AIE BIT(7)
|
||||||
|
|
||||||
|
#define PCF85063_REG_OFFSET 0x02
|
||||||
|
#define PCF85063_OFFSET_SIGN_BIT 6 /* 2's complement sign bit */
|
||||||
|
#define PCF85063_OFFSET_MODE BIT(7)
|
||||||
|
#define PCF85063_OFFSET_STEP0 4340
|
||||||
|
#define PCF85063_OFFSET_STEP1 4069
|
||||||
|
|
||||||
|
#define PCF85063_REG_CLKO_F_MASK 0x07 /* frequency mask */
|
||||||
|
#define PCF85063_REG_CLKO_F_32768HZ 0x00
|
||||||
|
#define PCF85063_REG_CLKO_F_OFF 0x07
|
||||||
|
|
||||||
|
#define PCF85063_REG_RAM 0x03
|
||||||
|
|
||||||
|
#define PCF85063_REG_SC 0x04 /* datetime */
|
||||||
|
#define PCF85063_REG_SC_OS 0x80
|
||||||
|
|
||||||
|
#define PCF85063_REG_ALM_S 0x0b
|
||||||
|
#define PCF85063_AEN BIT(7)
|
||||||
|
|
||||||
|
struct pcf85063_config {
|
||||||
|
struct regmap_config regmap;
|
||||||
|
unsigned has_alarms:1;
|
||||||
|
unsigned force_cap_7000:1;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct pcf85063 {
|
||||||
|
struct rtc_device *rtc;
|
||||||
|
struct regmap *regmap;
|
||||||
|
#ifdef CONFIG_COMMON_CLK
|
||||||
|
struct clk_hw clkout_hw;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
static int pcf85063_rtc_read_time(struct device *dev, struct rtc_time *tm)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
int rc;
|
||||||
|
u8 regs[7];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* while reading, the time/date registers are blocked and not updated
|
||||||
|
* anymore until the access is finished. To not lose a second
|
||||||
|
* event, the access must be finished within one second. So, read all
|
||||||
|
* time/date registers in one turn.
|
||||||
|
*/
|
||||||
|
rc = regmap_bulk_read(pcf85063->regmap, PCF85063_REG_SC, regs,
|
||||||
|
sizeof(regs));
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/* if the clock has lost its power it makes no sense to use its time */
|
||||||
|
if (regs[0] & PCF85063_REG_SC_OS) {
|
||||||
|
dev_warn(&pcf85063->rtc->dev, "Power loss detected, invalid time\n");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
tm->tm_sec = bcd2bin(regs[0] & 0x7F);
|
||||||
|
tm->tm_min = bcd2bin(regs[1] & 0x7F);
|
||||||
|
tm->tm_hour = bcd2bin(regs[2] & 0x3F); /* rtc hr 0-23 */
|
||||||
|
tm->tm_mday = bcd2bin(regs[3] & 0x3F);
|
||||||
|
tm->tm_wday = regs[4] & 0x07;
|
||||||
|
tm->tm_mon = bcd2bin(regs[5] & 0x1F) - 1; /* rtc mn 1-12 */
|
||||||
|
tm->tm_year = bcd2bin(regs[6]);
|
||||||
|
tm->tm_year += 100;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_rtc_set_time(struct device *dev, struct rtc_time *tm)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
int rc;
|
||||||
|
u8 regs[7];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* to accurately set the time, reset the divider chain and keep it in
|
||||||
|
* reset state until all time/date registers are written
|
||||||
|
*/
|
||||||
|
rc = regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL1,
|
||||||
|
PCF85063_REG_CTRL1_STOP,
|
||||||
|
PCF85063_REG_CTRL1_STOP);
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/* hours, minutes and seconds */
|
||||||
|
regs[0] = bin2bcd(tm->tm_sec) & 0x7F; /* clear OS flag */
|
||||||
|
|
||||||
|
regs[1] = bin2bcd(tm->tm_min);
|
||||||
|
regs[2] = bin2bcd(tm->tm_hour);
|
||||||
|
|
||||||
|
/* Day of month, 1 - 31 */
|
||||||
|
regs[3] = bin2bcd(tm->tm_mday);
|
||||||
|
|
||||||
|
/* Day, 0 - 6 */
|
||||||
|
regs[4] = tm->tm_wday & 0x07;
|
||||||
|
|
||||||
|
/* month, 1 - 12 */
|
||||||
|
regs[5] = bin2bcd(tm->tm_mon + 1);
|
||||||
|
|
||||||
|
/* year and century */
|
||||||
|
regs[6] = bin2bcd(tm->tm_year - 100);
|
||||||
|
|
||||||
|
/* write all registers at once */
|
||||||
|
rc = regmap_bulk_write(pcf85063->regmap, PCF85063_REG_SC,
|
||||||
|
regs, sizeof(regs));
|
||||||
|
if (rc)
|
||||||
|
return rc;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the control register as a separate action since the size of
|
||||||
|
* the register space is different between the PCF85063TP and
|
||||||
|
* PCF85063A devices. The rollover point can not be used.
|
||||||
|
*/
|
||||||
|
return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL1,
|
||||||
|
PCF85063_REG_CTRL1_STOP, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_rtc_read_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
u8 buf[4];
|
||||||
|
unsigned int val;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_bulk_read(pcf85063->regmap, PCF85063_REG_ALM_S,
|
||||||
|
buf, sizeof(buf));
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
alrm->time.tm_sec = bcd2bin(buf[0]);
|
||||||
|
alrm->time.tm_min = bcd2bin(buf[1]);
|
||||||
|
alrm->time.tm_hour = bcd2bin(buf[2]);
|
||||||
|
alrm->time.tm_mday = bcd2bin(buf[3]);
|
||||||
|
|
||||||
|
ret = regmap_read(pcf85063->regmap, PCF85063_REG_CTRL2, &val);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
alrm->enabled = !!(val & PCF85063_CTRL2_AIE);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_rtc_set_alarm(struct device *dev, struct rtc_wkalrm *alrm)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
u8 buf[5];
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
buf[0] = bin2bcd(alrm->time.tm_sec);
|
||||||
|
buf[1] = bin2bcd(alrm->time.tm_min);
|
||||||
|
buf[2] = bin2bcd(alrm->time.tm_hour);
|
||||||
|
buf[3] = bin2bcd(alrm->time.tm_mday);
|
||||||
|
buf[4] = PCF85063_AEN; /* Do not match on week day */
|
||||||
|
|
||||||
|
ret = regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2,
|
||||||
|
PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF, 0);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
ret = regmap_bulk_write(pcf85063->regmap, PCF85063_REG_ALM_S,
|
||||||
|
buf, sizeof(buf));
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2,
|
||||||
|
PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF,
|
||||||
|
alrm->enabled ? PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF : PCF85063_CTRL2_AF);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_rtc_alarm_irq_enable(struct device *dev,
|
||||||
|
unsigned int enabled)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
|
||||||
|
return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2,
|
||||||
|
PCF85063_CTRL2_AIE,
|
||||||
|
enabled ? PCF85063_CTRL2_AIE : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static irqreturn_t pcf85063_rtc_handle_irq(int irq, void *dev_id)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_id;
|
||||||
|
unsigned int val;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
err = regmap_read(pcf85063->regmap, PCF85063_REG_CTRL2, &val);
|
||||||
|
if (err)
|
||||||
|
return IRQ_NONE;
|
||||||
|
|
||||||
|
if (val & PCF85063_CTRL2_AF) {
|
||||||
|
rtc_update_irq(pcf85063->rtc, 1, RTC_IRQF | RTC_AF);
|
||||||
|
regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2,
|
||||||
|
PCF85063_CTRL2_AIE | PCF85063_CTRL2_AF,
|
||||||
|
0);
|
||||||
|
return IRQ_HANDLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
return IRQ_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_read_offset(struct device *dev, long *offset)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
long val;
|
||||||
|
u32 reg;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_read(pcf85063->regmap, PCF85063_REG_OFFSET, ®);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
val = sign_extend32(reg & ~PCF85063_OFFSET_MODE,
|
||||||
|
PCF85063_OFFSET_SIGN_BIT);
|
||||||
|
|
||||||
|
if (reg & PCF85063_OFFSET_MODE)
|
||||||
|
*offset = val * PCF85063_OFFSET_STEP1;
|
||||||
|
else
|
||||||
|
*offset = val * PCF85063_OFFSET_STEP0;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_set_offset(struct device *dev, long offset)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
s8 mode0, mode1, reg;
|
||||||
|
unsigned int error0, error1;
|
||||||
|
|
||||||
|
if (offset > PCF85063_OFFSET_STEP0 * 63)
|
||||||
|
return -ERANGE;
|
||||||
|
if (offset < PCF85063_OFFSET_STEP0 * -64)
|
||||||
|
return -ERANGE;
|
||||||
|
|
||||||
|
mode0 = DIV_ROUND_CLOSEST(offset, PCF85063_OFFSET_STEP0);
|
||||||
|
mode1 = DIV_ROUND_CLOSEST(offset, PCF85063_OFFSET_STEP1);
|
||||||
|
|
||||||
|
error0 = abs(offset - (mode0 * PCF85063_OFFSET_STEP0));
|
||||||
|
error1 = abs(offset - (mode1 * PCF85063_OFFSET_STEP1));
|
||||||
|
if (mode1 > 63 || mode1 < -64 || error0 < error1)
|
||||||
|
reg = mode0 & ~PCF85063_OFFSET_MODE;
|
||||||
|
else
|
||||||
|
reg = mode1 | PCF85063_OFFSET_MODE;
|
||||||
|
|
||||||
|
return regmap_write(pcf85063->regmap, PCF85063_REG_OFFSET, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_ioctl(struct device *dev, unsigned int cmd,
|
||||||
|
unsigned long arg)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = dev_get_drvdata(dev);
|
||||||
|
int status, ret = 0;
|
||||||
|
|
||||||
|
switch (cmd) {
|
||||||
|
case RTC_VL_READ:
|
||||||
|
ret = regmap_read(pcf85063->regmap, PCF85063_REG_SC, &status);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
|
||||||
|
status = status & PCF85063_REG_SC_OS ? RTC_VL_DATA_INVALID : 0;
|
||||||
|
|
||||||
|
return put_user(status, (unsigned int __user *)arg);
|
||||||
|
|
||||||
|
default:
|
||||||
|
return -ENOIOCTLCMD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct rtc_class_ops pcf85063_rtc_ops = {
|
||||||
|
.read_time = pcf85063_rtc_read_time,
|
||||||
|
.set_time = pcf85063_rtc_set_time,
|
||||||
|
.read_offset = pcf85063_read_offset,
|
||||||
|
.set_offset = pcf85063_set_offset,
|
||||||
|
.ioctl = pcf85063_ioctl,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct rtc_class_ops pcf85063_rtc_ops_alarm = {
|
||||||
|
.read_time = pcf85063_rtc_read_time,
|
||||||
|
.set_time = pcf85063_rtc_set_time,
|
||||||
|
.read_offset = pcf85063_read_offset,
|
||||||
|
.set_offset = pcf85063_set_offset,
|
||||||
|
.read_alarm = pcf85063_rtc_read_alarm,
|
||||||
|
.set_alarm = pcf85063_rtc_set_alarm,
|
||||||
|
.alarm_irq_enable = pcf85063_rtc_alarm_irq_enable,
|
||||||
|
.ioctl = pcf85063_ioctl,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int pcf85063_nvmem_read(void *priv, unsigned int offset,
|
||||||
|
void *val, size_t bytes)
|
||||||
|
{
|
||||||
|
return regmap_read(priv, PCF85063_REG_RAM, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_nvmem_write(void *priv, unsigned int offset,
|
||||||
|
void *val, size_t bytes)
|
||||||
|
{
|
||||||
|
return regmap_write(priv, PCF85063_REG_RAM, *(u8 *)val);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_load_capacitance(struct pcf85063 *pcf85063,
|
||||||
|
const struct device_node *np,
|
||||||
|
unsigned int force_cap)
|
||||||
|
{
|
||||||
|
u32 load = 7000;
|
||||||
|
u8 reg = 0;
|
||||||
|
|
||||||
|
if (force_cap)
|
||||||
|
load = force_cap;
|
||||||
|
else
|
||||||
|
of_property_read_u32(np, "quartz-load-femtofarads", &load);
|
||||||
|
|
||||||
|
switch (load) {
|
||||||
|
default:
|
||||||
|
dev_warn(&pcf85063->rtc->dev, "Unknown quartz-load-femtofarads value: %d. Assuming 7000",
|
||||||
|
load);
|
||||||
|
fallthrough;
|
||||||
|
case 7000:
|
||||||
|
break;
|
||||||
|
case 12500:
|
||||||
|
reg = PCF85063_REG_CTRL1_CAP_SEL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL1,
|
||||||
|
PCF85063_REG_CTRL1_CAP_SEL, reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_COMMON_CLK
|
||||||
|
/*
|
||||||
|
* Handling of the clkout
|
||||||
|
*/
|
||||||
|
|
||||||
|
#define clkout_hw_to_pcf85063(_hw) container_of(_hw, struct pcf85063, clkout_hw)
|
||||||
|
|
||||||
|
static int clkout_rates[] = {
|
||||||
|
32768,
|
||||||
|
16384,
|
||||||
|
8192,
|
||||||
|
4096,
|
||||||
|
2048,
|
||||||
|
1024,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
|
static unsigned long pcf85063_clkout_recalc_rate(struct clk_hw *hw,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = clkout_hw_to_pcf85063(hw);
|
||||||
|
unsigned int buf;
|
||||||
|
int ret = regmap_read(pcf85063->regmap, PCF85063_REG_CTRL2, &buf);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
buf &= PCF85063_REG_CLKO_F_MASK;
|
||||||
|
return clkout_rates[buf];
|
||||||
|
}
|
||||||
|
|
||||||
|
static long pcf85063_clkout_round_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long *prate)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
|
||||||
|
if (clkout_rates[i] <= rate)
|
||||||
|
return clkout_rates[i];
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_clkout_set_rate(struct clk_hw *hw, unsigned long rate,
|
||||||
|
unsigned long parent_rate)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = clkout_hw_to_pcf85063(hw);
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; i < ARRAY_SIZE(clkout_rates); i++)
|
||||||
|
if (clkout_rates[i] == rate)
|
||||||
|
return regmap_update_bits(pcf85063->regmap,
|
||||||
|
PCF85063_REG_CTRL2,
|
||||||
|
PCF85063_REG_CLKO_F_MASK, i);
|
||||||
|
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_clkout_control(struct clk_hw *hw, bool enable)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = clkout_hw_to_pcf85063(hw);
|
||||||
|
unsigned int buf;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
ret = regmap_read(pcf85063->regmap, PCF85063_REG_OFFSET, &buf);
|
||||||
|
if (ret < 0)
|
||||||
|
return ret;
|
||||||
|
buf &= PCF85063_REG_CLKO_F_MASK;
|
||||||
|
|
||||||
|
if (enable) {
|
||||||
|
if (buf == PCF85063_REG_CLKO_F_OFF)
|
||||||
|
buf = PCF85063_REG_CLKO_F_32768HZ;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
if (buf != PCF85063_REG_CLKO_F_OFF)
|
||||||
|
buf = PCF85063_REG_CLKO_F_OFF;
|
||||||
|
else
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return regmap_update_bits(pcf85063->regmap, PCF85063_REG_CTRL2,
|
||||||
|
PCF85063_REG_CLKO_F_MASK, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_clkout_prepare(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
return pcf85063_clkout_control(hw, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pcf85063_clkout_unprepare(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
pcf85063_clkout_control(hw, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int pcf85063_clkout_is_prepared(struct clk_hw *hw)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063 = clkout_hw_to_pcf85063(hw);
|
||||||
|
unsigned int buf;
|
||||||
|
int ret = regmap_read(pcf85063->regmap, PCF85063_REG_CTRL2, &buf);
|
||||||
|
|
||||||
|
if (ret < 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return (buf & PCF85063_REG_CLKO_F_MASK) != PCF85063_REG_CLKO_F_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct clk_ops pcf85063_clkout_ops = {
|
||||||
|
.prepare = pcf85063_clkout_prepare,
|
||||||
|
.unprepare = pcf85063_clkout_unprepare,
|
||||||
|
.is_prepared = pcf85063_clkout_is_prepared,
|
||||||
|
.recalc_rate = pcf85063_clkout_recalc_rate,
|
||||||
|
.round_rate = pcf85063_clkout_round_rate,
|
||||||
|
.set_rate = pcf85063_clkout_set_rate,
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct clk *pcf85063_clkout_register_clk(struct pcf85063 *pcf85063)
|
||||||
|
{
|
||||||
|
struct clk *clk;
|
||||||
|
struct clk_init_data init;
|
||||||
|
struct device_node *node = pcf85063->rtc->dev.parent->of_node;
|
||||||
|
|
||||||
|
init.name = "pcf85063-clkout";
|
||||||
|
init.ops = &pcf85063_clkout_ops;
|
||||||
|
init.flags = 0;
|
||||||
|
init.parent_names = NULL;
|
||||||
|
init.num_parents = 0;
|
||||||
|
pcf85063->clkout_hw.init = &init;
|
||||||
|
|
||||||
|
/* optional override of the clockname */
|
||||||
|
of_property_read_string(node, "clock-output-names", &init.name);
|
||||||
|
|
||||||
|
/* register the clock */
|
||||||
|
clk = devm_clk_register(&pcf85063->rtc->dev, &pcf85063->clkout_hw);
|
||||||
|
|
||||||
|
if (!IS_ERR(clk))
|
||||||
|
of_clk_add_provider(node, of_clk_src_simple_get, clk);
|
||||||
|
|
||||||
|
return clk;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static const struct pcf85063_config pcf85063a_config = {
|
||||||
|
.regmap = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 8,
|
||||||
|
.max_register = 0x11,
|
||||||
|
},
|
||||||
|
.has_alarms = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pcf85063_config pcf85063tp_config = {
|
||||||
|
.regmap = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 8,
|
||||||
|
.max_register = 0x0a,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct pcf85063_config rv8263_config = {
|
||||||
|
.regmap = {
|
||||||
|
.reg_bits = 8,
|
||||||
|
.val_bits = 8,
|
||||||
|
.max_register = 0x11,
|
||||||
|
},
|
||||||
|
.has_alarms = 1,
|
||||||
|
.force_cap_7000 = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
static int pcf85063_probe(struct i2c_client *client)
|
||||||
|
{
|
||||||
|
struct pcf85063 *pcf85063;
|
||||||
|
unsigned int tmp;
|
||||||
|
int err;
|
||||||
|
const struct pcf85063_config *config = &pcf85063tp_config;
|
||||||
|
const void *data = of_device_get_match_data(&client->dev);
|
||||||
|
struct nvmem_config nvmem_cfg = {
|
||||||
|
.name = "pcf85063_nvram",
|
||||||
|
.reg_read = pcf85063_nvmem_read,
|
||||||
|
.reg_write = pcf85063_nvmem_write,
|
||||||
|
.type = NVMEM_TYPE_BATTERY_BACKED,
|
||||||
|
.size = 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
dev_dbg(&client->dev, "%s\n", __func__);
|
||||||
|
|
||||||
|
pcf85063 = devm_kzalloc(&client->dev, sizeof(struct pcf85063),
|
||||||
|
GFP_KERNEL);
|
||||||
|
if (!pcf85063)
|
||||||
|
return -ENOMEM;
|
||||||
|
|
||||||
|
if (data)
|
||||||
|
config = data;
|
||||||
|
|
||||||
|
pcf85063->regmap = devm_regmap_init_i2c(client, &config->regmap);
|
||||||
|
if (IS_ERR(pcf85063->regmap))
|
||||||
|
return PTR_ERR(pcf85063->regmap);
|
||||||
|
|
||||||
|
i2c_set_clientdata(client, pcf85063);
|
||||||
|
|
||||||
|
err = regmap_read(pcf85063->regmap, PCF85063_REG_CTRL1, &tmp);
|
||||||
|
if (err) {
|
||||||
|
dev_err(&client->dev, "RTC chip is not present\n");
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
|
||||||
|
pcf85063->rtc = devm_rtc_allocate_device(&client->dev);
|
||||||
|
if (IS_ERR(pcf85063->rtc))
|
||||||
|
return PTR_ERR(pcf85063->rtc);
|
||||||
|
|
||||||
|
err = pcf85063_load_capacitance(pcf85063, client->dev.of_node,
|
||||||
|
config->force_cap_7000 ? 7000 : 0);
|
||||||
|
if (err < 0)
|
||||||
|
dev_warn(&client->dev, "failed to set xtal load capacitance: %d",
|
||||||
|
err);
|
||||||
|
|
||||||
|
pcf85063->rtc->ops = &pcf85063_rtc_ops;
|
||||||
|
pcf85063->rtc->range_min = RTC_TIMESTAMP_BEGIN_2000;
|
||||||
|
pcf85063->rtc->range_max = RTC_TIMESTAMP_END_2099;
|
||||||
|
pcf85063->rtc->uie_unsupported = 1;
|
||||||
|
|
||||||
|
if (config->has_alarms && client->irq > 0) {
|
||||||
|
err = devm_request_threaded_irq(&client->dev, client->irq,
|
||||||
|
NULL, pcf85063_rtc_handle_irq,
|
||||||
|
IRQF_TRIGGER_LOW | IRQF_ONESHOT,
|
||||||
|
"pcf85063", pcf85063);
|
||||||
|
if (err) {
|
||||||
|
dev_warn(&pcf85063->rtc->dev,
|
||||||
|
"unable to request IRQ, alarms disabled\n");
|
||||||
|
} else {
|
||||||
|
pcf85063->rtc->ops = &pcf85063_rtc_ops_alarm;
|
||||||
|
device_init_wakeup(&client->dev, true);
|
||||||
|
err = dev_pm_set_wake_irq(&client->dev, client->irq);
|
||||||
|
if (err)
|
||||||
|
dev_err(&pcf85063->rtc->dev,
|
||||||
|
"failed to enable irq wake\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nvmem_cfg.priv = pcf85063->regmap;
|
||||||
|
rtc_nvmem_register(pcf85063->rtc, &nvmem_cfg);
|
||||||
|
|
||||||
|
#ifdef CONFIG_COMMON_CLK
|
||||||
|
/* register clk in common clk framework */
|
||||||
|
pcf85063_clkout_register_clk(pcf85063);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return rtc_register_device(pcf85063->rtc);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef CONFIG_OF
|
||||||
|
static const struct of_device_id pcf85063_of_match[] = {
|
||||||
|
{ .compatible = "nxp,pcf85063", .data = &pcf85063tp_config },
|
||||||
|
{ .compatible = "nxp,pcf85063tp", .data = &pcf85063tp_config },
|
||||||
|
{ .compatible = "nxp,pcf85063a", .data = &pcf85063a_config },
|
||||||
|
{ .compatible = "microcrystal,rv8263", .data = &rv8263_config },
|
||||||
|
{}
|
||||||
|
};
|
||||||
|
MODULE_DEVICE_TABLE(of, pcf85063_of_match);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static struct i2c_driver pcf85063_driver = {
|
||||||
|
.driver = {
|
||||||
|
.name = "rtc-pcf85063",
|
||||||
|
.of_match_table = of_match_ptr(pcf85063_of_match),
|
||||||
|
},
|
||||||
|
.probe_new = pcf85063_probe,
|
||||||
|
};
|
||||||
|
|
||||||
|
module_i2c_driver(pcf85063_driver);
|
||||||
|
|
||||||
|
MODULE_AUTHOR("Søren Andersen <san@rosetechnology.dk>");
|
||||||
|
MODULE_DESCRIPTION("PCF85063 RTC driver");
|
||||||
|
MODULE_LICENSE("GPL");
|
Loading…
Reference in New Issue
Block a user