diff --git a/.buildbot/openwrt/build.sh b/.buildbot/openwrt/build.sh index d088bb5..a5513d1 100755 --- a/.buildbot/openwrt/build.sh +++ b/.buildbot/openwrt/build.sh @@ -28,6 +28,8 @@ make defconfig # Enable collectd network encryption 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/collectd/compile # no signing key @@ -55,8 +57,9 @@ echo "CONFIG_TARGET_ROOTFS_PARTSIZE=308" >> .config # Disable ext4 images echo "CONFIG_TARGET_ROOTFS_EXT4FS=n" >> .config -PACKAGES="kmod-i2c-bcm2835 kmod-i2c-mux kmod-nf-nathelper-extra \ - kmod-rtc-ds1307 kmod-rtc-pcf8563 \ +PACKAGES="kmod-i2c-bcm2835 kmod-i2c-mux kmod-i2c-mux-pinctrl \ + kmod-nf-nathelper-extra \ + kmod-rtc-ds1307 kmod-rtc-pcf8563 kmod-rtc-pcf85063 \ kmod-usb-audio kmod-usb-net-rtl8152 \ alsa-lib pciutils usbutils \ kmod-usb-net-qmi-wwan libqmi qmi-utils uqmi luci-proto-qmi \ diff --git a/feed/kmod-i2c-mux-pinctrl/Makefile b/feed/kmod-i2c-mux-pinctrl/Makefile new file mode 100644 index 0000000..b5a0279 --- /dev/null +++ b/feed/kmod-i2c-mux-pinctrl/Makefile @@ -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))) diff --git a/feed/kmod-i2c-mux-pinctrl/src/Makefile b/feed/kmod-i2c-mux-pinctrl/src/Makefile new file mode 100644 index 0000000..60a8ef4 --- /dev/null +++ b/feed/kmod-i2c-mux-pinctrl/src/Makefile @@ -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 diff --git a/feed/kmod-i2c-mux-pinctrl/src/gpiolib.h b/feed/kmod-i2c-mux-pinctrl/src/gpiolib.h new file mode 100644 index 0000000..b674b5b --- /dev/null +++ b/feed/kmod-i2c-mux-pinctrl/src/gpiolib.h @@ -0,0 +1,184 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Internal GPIO functions. + * + * Copyright (C) 2013, Intel Corporation + * Author: Mika Westerberg + */ + +#ifndef GPIOLIB_H +#define GPIOLIB_H + +#include +#include /* for enum gpiod_flags */ +#include +#include +#include +#include + +#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 */ diff --git a/feed/kmod-i2c-mux-pinctrl/src/i2c-mux-gpio.c b/feed/kmod-i2c-mux-pinctrl/src/i2c-mux-gpio.c new file mode 100644 index 0000000..f974e97 --- /dev/null +++ b/feed/kmod-i2c-mux-pinctrl/src/i2c-mux-gpio.c @@ -0,0 +1,243 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * I2C multiplexer using GPIO API + * + * Peter Korsgaard + */ + +#include +#include +#include +#include +#include +#include +#include +#include +/* 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 "); +MODULE_LICENSE("GPL"); +MODULE_ALIAS("platform:i2c-mux-gpio"); diff --git a/feed/kmod-i2c-mux-pinctrl/src/i2c-mux-pinctrl.c b/feed/kmod-i2c-mux-pinctrl/src/i2c-mux-pinctrl.c new file mode 100644 index 0000000..406ab2d --- /dev/null +++ b/feed/kmod-i2c-mux-pinctrl/src/i2c-mux-pinctrl.c @@ -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 +#include +#include +#include +#include +#include +#include +#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 "); +MODULE_LICENSE("GPL v2"); +MODULE_ALIAS("platform:i2c-mux-pinctrl"); diff --git a/feed/kmod-i2c-mux-pinctrl/src/pinctrl_core.h b/feed/kmod-i2c-mux-pinctrl/src/pinctrl_core.h new file mode 100644 index 0000000..840103c --- /dev/null +++ b/feed/kmod-i2c-mux-pinctrl/src/pinctrl_core.h @@ -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 + */ + +#include +#include +#include +#include +#include + +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_]) diff --git a/feed/kmod-rtc-pcf85063/Makefile b/feed/kmod-rtc-pcf85063/Makefile new file mode 100644 index 0000000..3b29c61 --- /dev/null +++ b/feed/kmod-rtc-pcf85063/Makefile @@ -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))) diff --git a/feed/kmod-rtc-pcf85063/src/Makefile b/feed/kmod-rtc-pcf85063/src/Makefile new file mode 100644 index 0000000..cac932e --- /dev/null +++ b/feed/kmod-rtc-pcf85063/src/Makefile @@ -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 diff --git a/feed/kmod-rtc-pcf85063/src/rtc-pcf85063.c b/feed/kmod-rtc-pcf85063/src/rtc-pcf85063.c new file mode 100644 index 0000000..62684ca --- /dev/null +++ b/feed/kmod-rtc-pcf85063/src/rtc-pcf85063.c @@ -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 + * Maintainers: http://www.nslu2-linux.org/ + * + * Copyright (C) 2019 Micro Crystal AG + * Author: Alexandre Belloni + */ +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * 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 "); +MODULE_DESCRIPTION("PCF85063 RTC driver"); +MODULE_LICENSE("GPL");